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依赖,重构