diff --git a/src/CaiBotLite/CaiBotLite.cs b/src/CaiBotLite/CaiBotLite.cs index 945f02291..167cfd134 100644 --- a/src/CaiBotLite/CaiBotLite.cs +++ b/src/CaiBotLite/CaiBotLite.cs @@ -13,7 +13,7 @@ namespace CaiBotLite; // ReSharper disable once ClassNeverInstantiated.Global public class CaiBotLite(Main game) : TerrariaPlugin(game) { - public static readonly Version VersionNum = new (2026, 02, 14, 0); //日期+版本号(0,1,2...) + public static readonly Version VersionNum = new (2026, 02, 17, 0); //日期+版本号(0,1,2...) internal static int InitCode = -1; internal static bool DebugMode = Program.LaunchParameters.ContainsKey("-caidebug"); private const string CharacterInfoKey = "CaiBotLite.CharacterInfo"; @@ -39,7 +39,7 @@ public override void Initialize() GeneralHooks.ReloadEvent += GeneralHooksOnReloadEvent; PlayerHooks.PlayerPostLogin += PlayerHooksOnPlayerPostLogin; GetDataHandlers.KillMe.Register(KillMe, HandlerPriority.Highest); - MapGenerator.Init(); + MapGeneratorSupport.Init(); EconomicSupport.Init(); BossLockSupport.Init(); ProgressControlSupport.Init(); @@ -63,7 +63,6 @@ protected override void Dispose(bool disposing) GeneralHooks.ReloadEvent -= GeneralHooksOnReloadEvent; PlayerHooks.PlayerPostLogin -= PlayerHooksOnPlayerPostLogin; GetDataHandlers.KillMe.UnRegister(KillMe); - MapGenerator.Dispose(); WebsocketManager.StopWebsocket(); } diff --git a/src/CaiBotLite/CaiBotLite.csproj b/src/CaiBotLite/CaiBotLite.csproj index 9138cb47a..97bf73135 100644 --- a/src/CaiBotLite/CaiBotLite.csproj +++ b/src/CaiBotLite/CaiBotLite.csproj @@ -4,18 +4,14 @@ - - - SixLabors.ImageSharp.dll - - + diff --git a/src/CaiBotLite/Common/CaiBotApi.cs b/src/CaiBotLite/Common/CaiBotApi.cs index 3f60eed16..766182935 100644 --- a/src/CaiBotLite/Common/CaiBotApi.cs +++ b/src/CaiBotLite/Common/CaiBotApi.cs @@ -1,7 +1,6 @@ using CaiBotLite.Enums; using CaiBotLite.Models; using Microsoft.Xna.Framework; -using SixLabors.ImageSharp.Formats.Png; using Terraria; using TerrariaApi.Server; using TShockAPI; @@ -11,7 +10,7 @@ namespace CaiBotLite.Common; internal static class CaiBotApi { - internal static async Task HandleMessageAsync(string receivedData) + internal static void HandleMessage(string receivedData) { var package = Package.Parse(receivedData); var packetWriter = new PackageWriter(package.Type, package.IsRequest, package.RequestId); @@ -161,23 +160,18 @@ internal static async Task HandleMessageAsync(string receivedData) break; case PackageType.MapImage: - var bitmap = MapGenerator.CreateMapImg(); - using (MemoryStream ms = new ()) - { - await bitmap.SaveAsync(ms, new PngEncoder()); - var imageBytes = ms.ToArray(); - var base64 = Convert.ToBase64String(imageBytes); - packetWriter - .Write("base64", Utils.CompressBase64(base64)) - .Send(); - } + var imageBytes = MapGeneratorSupport.CreatMapImgBytes(); + packetWriter + .Write("base64", Utils.CompressBase64(Convert.ToBase64String(imageBytes))) + .Send(); + break; case PackageType.MapFile: - var mapFile = MapGenerator.CreateMapFile(); + var mapFile = MapGeneratorSupport.CreateMapFile(); packetWriter .Write("name", mapFile.Item2) - .Write("base64", Utils.CompressBase64(mapFile.Item1)) + .Write("base64", Utils.CompressBase64(Convert.ToBase64String(mapFile.Item1))) .Send(); break; @@ -339,7 +333,7 @@ internal static async Task HandleMessageAsync(string receivedData) $"源数据包: {receivedData}"); packetWriter.Package.Type = PackageType.Error; - packetWriter.Write("error", ex) + packetWriter.Write("error", ex.ToString()) .Send(); } } diff --git a/src/CaiBotLite/Common/MapGenerator.cs b/src/CaiBotLite/Common/MapGenerator.cs deleted file mode 100644 index 57c45de00..000000000 --- a/src/CaiBotLite/Common/MapGenerator.cs +++ /dev/null @@ -1,80 +0,0 @@ -using MonoMod.RuntimeDetour; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Terraria; -using Terraria.IO; -using Terraria.Map; -using Image = SixLabors.ImageSharp.Image; - -namespace CaiBotLite.Common; - -internal static class MapGenerator -{ - private static readonly Hook WorldMapIndexerHook = new (typeof(WorldMap).GetMethod("get_Item")!, NewWorldMapIndexer); - - internal static void Init() - { - WorldMapIndexerHook.Apply(); - MapHelper.Initialize(); - Main.mapEnabled = true; - Main.Map = new WorldMap(Main.maxTilesX, Main.maxTilesY) { _tiles = new MapTile[Main.maxTilesX, Main.maxTilesY] }; - Main.ActivePlayerFileData = new PlayerFileData { Name = "CaiBot", _path = Main.GetPlayerPathFromName("CaiBot", false) }; - Main.MapFileMetadata = FileMetadata.FromCurrentSettings(FileType.Map); - } - - internal static void Dispose() - { - WorldMapIndexerHook.Dispose(); - } - - private static MapTile NewWorldMapIndexer(Func orig, WorldMap self, int x, int y) - { - try - { - return self._tiles[x, y]; - } - catch (IndexOutOfRangeException) - { - return new MapTile(); - } - } - - private static void LightWholeMap() - { - Main.Map = new WorldMap(Main.maxTilesX, Main.maxTilesY) { _tiles = new MapTile[Main.maxTilesX, Main.maxTilesY] }; - for (var x = 0; x < Main.maxTilesX; x++) - { - for (var y = 0; y < Main.maxTilesY; y++) - { - Main.Map._tiles[x, y] = MapHelper.CreateMapTile(x, y, byte.MaxValue); - } - } - } - - internal static Image CreateMapImg() - { - Image image = new (Main.maxTilesX, Main.maxTilesY); - LightWholeMap(); - for (var x = 0; x < Main.maxTilesX; x++) - { - for (var y = 0; y < Main.maxTilesY; y++) - { - var tile = Main.Map[x, y]; - var col = MapHelper.GetMapTileXnaColor(tile); - image[x, y] = new Rgba32(col.R, col.G, col.B, col.A); - } - } - - return image; - } - - internal static (string, string) CreateMapFile() - { - LightWholeMap(); - MapHelper.SaveMap(); - var playerPath = Main.playerPathName[..^4] + Path.DirectorySeparatorChar; - var mapFileName = !Main.ActiveWorldFileData.UseGuidAsMapName ? Main.worldID + ".map" : Main.ActiveWorldFileData.UniqueId + ".map"; - var mapFilePath = Path.Combine(playerPath, mapFileName); - return (Utils.FileToBase64String(mapFilePath), mapFileName); - } -} \ No newline at end of file diff --git a/src/CaiBotLite/Common/MapGeneratorSupport.cs b/src/CaiBotLite/Common/MapGeneratorSupport.cs new file mode 100644 index 000000000..99eb103da --- /dev/null +++ b/src/CaiBotLite/Common/MapGeneratorSupport.cs @@ -0,0 +1,38 @@ +using TerrariaApi.Server; + +namespace CaiBotLite.Common; + +internal static class MapGeneratorSupport +{ + private static bool Support { get; set; } + internal static void Init() + { + var pluginContainer = ServerApi.Plugins.FirstOrDefault(x => x.Plugin.Name == "GenerateMap"); + if (pluginContainer is not null) + { + Support = true; + } + } + + private static void ThrowIfNotSupported() + { + if (!Support) + { + throw new NotSupportedException("需要安装GenerateMap插件"); + } + } + + + internal static byte[] CreatMapImgBytes() + { + ThrowIfNotSupported(); + return GenerateMap.MapGenerator.CreatMapImgBytes(); + } + + internal static (byte[], string) CreateMapFile() + { + ThrowIfNotSupported(); + var mapFile = GenerateMap.MapGenerator.CreatMapFile(); + return (mapFile.File, mapFile.Name); + } +} \ No newline at end of file diff --git a/src/CaiBotLite/Common/WebsocketManager.cs b/src/CaiBotLite/Common/WebsocketManager.cs index e5cd5b01d..cdbfb3db8 100644 --- a/src/CaiBotLite/Common/WebsocketManager.cs +++ b/src/CaiBotLite/Common/WebsocketManager.cs @@ -14,8 +14,7 @@ public static class WebsocketManager public static ClientWebSocket? WebSocket; private const string BotServerUrl = "api.terraria.ink:22338"; - - //private const string BotServerUrl = "127.0.0.1:8080"; + internal static bool IsWebsocketConnected => WebSocket?.State == WebSocketState.Open; private static bool _isStopWebsocket; @@ -137,7 +136,21 @@ public static void StopWebsocket() TShock.Log.ConsoleInfo($"[CaiBotLite]收到BOT数据包: {receivedData}"); } - await CaiBotApi.HandleMessageAsync(receivedData); + _ = Task.Run(() => + { + try + { + CaiBotApi.HandleMessage(receivedData); + } + catch (Exception e) + { + TShock.Log.ConsoleError("[CaiBotLite]处理消息时发生错误: \n" + + $"{e}"); + } + + }); + + } } catch (Exception ex) diff --git a/src/CaiBotLite/Models/BossKillInfo.cs b/src/CaiBotLite/Models/BossKillInfo.cs index 8a9d93e1c..a9b07f9cc 100644 --- a/src/CaiBotLite/Models/BossKillInfo.cs +++ b/src/CaiBotLite/Models/BossKillInfo.cs @@ -7,6 +7,7 @@ public class BossKillInfo { [PrimaryKey] [Identity] + [NotNull] [Column("id")] public int Id; diff --git a/src/CaiBotLite/Models/CaiCharacterInfo.cs b/src/CaiBotLite/Models/CaiCharacterInfo.cs index b8e42680c..dac29f7e6 100644 --- a/src/CaiBotLite/Models/CaiCharacterInfo.cs +++ b/src/CaiBotLite/Models/CaiCharacterInfo.cs @@ -7,6 +7,7 @@ namespace CaiBotLite.Models; public class CaiCharacterInfo { [PrimaryKey] + [NotNull] [Column("account_name")] public string AccountName = null!; diff --git a/src/CaiBotLite/Models/Mail.cs b/src/CaiBotLite/Models/Mail.cs index f07694d10..3258b5505 100644 --- a/src/CaiBotLite/Models/Mail.cs +++ b/src/CaiBotLite/Models/Mail.cs @@ -9,6 +9,7 @@ public class Mail { [PrimaryKey] [Identity] + [NotNull] [Column("id")] public int Id; diff --git a/src/CaiBotLite/README.md b/src/CaiBotLite/README.md index 24e23e0dc..fb6c82c09 100644 --- a/src/CaiBotLite/README.md +++ b/src/CaiBotLite/README.md @@ -33,6 +33,10 @@ https://docs.terraria.ink/zh/caibot/CaiBotLite.html ## 更新日志 +### v2026.02.17.0 + +- 改用GenerateMap生成地图,异步处理数据包 + ### v2026.02.15.0 - 适配Terraria 1.4.5 diff --git a/src/GenerateMap/MapGenerator.cs b/src/GenerateMap/MapGenerator.cs index 19b90ea83..28040c748 100644 --- a/src/GenerateMap/MapGenerator.cs +++ b/src/GenerateMap/MapGenerator.cs @@ -1,4 +1,3 @@ -using MonoMod.RuntimeDetour; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using System.IO.Streams; @@ -8,47 +7,58 @@ namespace GenerateMap; -internal static class MapGenerator +public static class MapGenerator { - private static readonly Hook WorldMapIndexerHook = new (typeof(WorldMap).GetMethod("get_Item")!, NewWorldMapIndexer); + private const string BasePath = "GenerateMap"; + private static readonly string MapsPath = Path.Combine(BasePath, "Maps"); + private static readonly string ImagesPath = Path.Combine(BasePath, "Images"); + private const int Edge = WorldMap.BlackEdgeWidth; - private const string MapsPath = @"GenerateMap"; - private const string ImagesPath = @"GenerateMap\Images"; internal static void Init() { - WorldMapIndexerHook.Apply(); MapHelper.Initialize(); Main.mapEnabled = true; - Main.Map = new WorldMap(Main.maxTilesX, Main.maxTilesY) { _tiles = new MapTile[Main.maxTilesX, Main.maxTilesY] }; + Main.Map = CreateWorkingMap(); Main.ActivePlayerFileData = new PlayerFileData { Name = "GenerateMap", _path = Main.GetPlayerPathFromName("GenerateMap", false) }; Main.MapFileMetadata = FileMetadata.FromCurrentSettings(FileType.Map); + Utils.TryCreatingDirectory(BasePath); Utils.TryCreatingDirectory(MapsPath); Utils.TryCreatingDirectory(ImagesPath); } - internal static void Dispose() + private static WorldMap CreateWorkingMap() { - WorldMapIndexerHook.Dispose(); - } - - private static MapTile NewWorldMapIndexer(Func orig, WorldMap self, int x, int y) - { - return self._tiles[x, y]; + return new WorldMap(Main.maxTilesX, Main.maxTilesY) { _tiles = new MapTile[Main.maxTilesX + (Edge * 2), Main.maxTilesY + (Edge * 2)] }; } private static void LightUpWholeMap() { - Main.Map = new WorldMap(Main.maxTilesX, Main.maxTilesY) { _tiles = new MapTile[Main.maxTilesX, Main.maxTilesY] }; + Main.Map = CreateWorkingMap(); + var width = Main.Map._tiles.GetLength(0); + var height = Main.Map._tiles.GetLength(1); for (var x = 0; x < Main.maxTilesX; x++) { for (var y = 0; y < Main.maxTilesY; y++) { - Main.Map._tiles[x, y] = MapHelper.CreateMapTile(x, y, byte.MaxValue); + var tile = MapHelper.CreateMapTile(x, y, byte.MaxValue); + + // 1.4.5+ on different runtimes may read either raw or edge-offset coordinates during save. + if ((uint) x < (uint) width && (uint) y < (uint) height) + { + Main.Map._tiles[x, y] = tile; + } + + var rawX = x + Edge; + var rawY = y + Edge; + if ((uint) rawX < (uint) width && (uint) rawY < (uint) height) + { + Main.Map._tiles[rawX, rawY] = tile; + } } } } - private static Image CreateMapImg() + private static Image CreateMapImg() { Image image = new (Main.maxTilesX, Main.maxTilesY); LightUpWholeMap(); @@ -56,7 +66,7 @@ private static Image CreateMapImg() { for (var y = 0; y < Main.maxTilesY; y++) { - var tile = Main.Map[x, y]; + var tile = Main.Map._tiles[x + Edge, y + Edge]; var col = MapHelper.GetMapTileXnaColor(tile); image[x, y] = new Rgba32(col.R, col.G, col.B, col.A); } @@ -65,18 +75,25 @@ private static Image CreateMapImg() return image; } - internal static byte[] CreatMapImgBytes() - { - return File.ReadAllBytes(CreatMapFile()); - } - - internal static byte[] CreatMapFileBytes() + public static byte[] CreatMapImgBytes() { var image = CreateMapImg(); using var stream = new MemoryStream(); image.SaveAsPng(stream); return stream.ToArray(); + } + + public class MapFile(byte[] file, string name) + { + public readonly byte[] File = file; + public readonly string Name = name; + } + public static MapFile CreatMapFile() + { + var mapFilePath = CreateMapFile(); + var mapFile = new MapFile(File.ReadAllBytes(mapFilePath), Path.GetFileName(mapFilePath)); + return mapFile; } internal static string SaveMapImg(string fileName) @@ -87,21 +104,28 @@ internal static string SaveMapImg(string fileName) return path; } - private static string CreatMapFile() + private static string CreateMapFile() { LightUpWholeMap(); MapHelper.SaveMap(); + var playerPath = Main.playerPathName[..^4] + Path.DirectorySeparatorChar; - var mapFileName = !Main.ActiveWorldFileData.UseGuidAsMapName ? Main.worldID + ".map" : Main.ActiveWorldFileData.UniqueId + ".map"; + var mapFileName = !Main.ActiveWorldFileData.UseGuidAsMapName + ? Main.worldID + ".map" + : Main.ActiveWorldFileData.UniqueId + ".map"; var mapFilePath = Path.Combine(playerPath, mapFileName); - return mapFilePath; + return !File.Exists(mapFilePath) ? throw new FileNotFoundException("Map file not found.", mapFilePath) : mapFilePath; } internal static string SaveMapFile() { - var mapPath = CreatMapFile(); - var path = Path.Combine(ImagesPath, Path.GetFileName(mapPath)); - File.Copy(mapPath, path); + var mapPath = CreateMapFile(); + var mapName = Path.GetFileNameWithoutExtension(mapPath); + var fileName = $"{mapName}.map"; + var mapPathWithTime = Path.Combine(MapsPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}"); + Utils.TryCreatingDirectory(mapPathWithTime); + var path = Path.Combine(mapPathWithTime, fileName); + File.Copy(mapPath, path, true); return path; } } \ No newline at end of file diff --git a/src/GenerateMap/Plugin.cs b/src/GenerateMap/Plugin.cs index 1d3d4ae7a..a4774d522 100644 --- a/src/GenerateMap/Plugin.cs +++ b/src/GenerateMap/Plugin.cs @@ -7,14 +7,15 @@ namespace GenerateMap; [ApiVersion(2, 1)] +// ReSharper disable once UnusedType.Global public class Plugin(Main game) : TerrariaPlugin(game) { - public override string Author => "少司命, Cai"; + public override string Author => "少司命, Cai, 千亦"; public override string Name => Assembly.GetExecutingAssembly().GetName().Name!; public override string Description => GetString("生成地图图片"); - public override Version Version => new (2, 0, 0); + public override Version Version => new (2, 1, 0); public override void Initialize() { @@ -25,39 +26,38 @@ public override void Initialize() TShock.RestApi.RegisterRedirect("/generatemap", "/generatemap/img"); Commands.ChatCommands.Add(new Command("generatemap", Generate, "map", "生成地图", "generatemap")); } - + protected override void Dispose(bool disposing) { if (disposing) { - MapGenerator.Dispose(); ((List) typeof(Rest) - .GetField("commands", BindingFlags.NonPublic | BindingFlags.Instance)! - .GetValue(TShock.RestApi)!) + .GetField("commands", BindingFlags.NonPublic | BindingFlags.Instance)! + .GetValue(TShock.RestApi)!) .RemoveAll(x => x.UriTemplate.Contains("generatemap")); - + Commands.ChatCommands.RemoveAll(x => x.CommandDelegate == Generate); AppDomain.CurrentDomain.AssemblyResolve -= this.CurrentDomain_AssemblyResolve; } + base.Dispose(disposing); } - + private static RestObject RestGenerateMapFile(RestRequestArgs args) { + var mapFile = MapGenerator.CreatMapFile(); + return new RestObject("200") { { "response", GetString("生成地图文件成功") }, - { "base64", Convert.ToBase64String(MapGenerator.CreatMapFileBytes()) } + { "map_name", mapFile.Name }, + { "base64", Convert.ToBase64String(mapFile.File) } }; } private static RestObject RestGenerateMapImg(RestRequestArgs args) { - return new RestObject("200") - { - { "response", GetString("生成地图图片成功") }, - { "base64", Convert.ToBase64String(MapGenerator.CreatMapImgBytes()) } - }; + return new RestObject("200") { { "response", GetString("生成地图图片成功") }, { "base64", Convert.ToBase64String(MapGenerator.CreatMapImgBytes()) } }; } private static void Generate(CommandArgs args) @@ -67,7 +67,7 @@ private static void Generate(CommandArgs args) ShowHelp(); return; } - + switch (args.Parameters[0]) { case "img": @@ -81,9 +81,8 @@ private static void Generate(CommandArgs args) } catch (Exception ex) { - TShock.Log.ConsoleError( GetString("[GenerateMap]生成地图出错: ") + ex); + TShock.Log.ConsoleError(GetString("[GenerateMap]生成地图出错: ") + ex); } - }); break; case "file": @@ -96,7 +95,7 @@ private static void Generate(CommandArgs args) } catch (Exception ex) { - TShock.Log.ConsoleError( GetString("[GenerateMap]生成地图出错: ") + ex); + TShock.Log.ConsoleError(GetString("[GenerateMap]生成地图出错: ") + ex); } }); break; @@ -111,11 +110,11 @@ private static void Generate(CommandArgs args) void ShowHelp() { args.Player.SendSuccessMessage(GetString("GenerateMap帮助: ")); - args.Player.SendSuccessMessage(GetString("/map file --- 生成地图文件")); + args.Player.SendSuccessMessage(GetString("/map file --- 生成地图文件(.map)")); args.Player.SendSuccessMessage(GetString("/map img --- 生成地图图片")); } } - + #region 加载前置 private Assembly? CurrentDomain_AssemblyResolve(object? sender, ResolveEventArgs args) diff --git a/src/GenerateMap/README.md b/src/GenerateMap/README.md index 9d1803dd9..9d82388db 100644 --- a/src/GenerateMap/README.md +++ b/src/GenerateMap/README.md @@ -1,6 +1,6 @@ # GenerateMap 将地图保存至图片 -- 作者: 少司命, Cai +- 作者: 少司命, Cai,千亦 - 出处: 无 - 生成地图图片 @@ -26,6 +26,10 @@ ## 更新日志 +### v2.1.0 +- 适配 1.4.5 +- 修复在 Linux, macOS 下的保存路径错误 + ### v2.0.0 - 移除CaiLib依赖,重构