diff --git a/AutoUpdatePlugin/AutoUpdatePlugin.csproj b/AutoUpdatePlugin/AutoUpdatePlugin.csproj new file mode 100644 index 000000000..54932bd7c --- /dev/null +++ b/AutoUpdatePlugin/AutoUpdatePlugin.csproj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/AutoUpdatePlugin/Plugin.cs b/AutoUpdatePlugin/Plugin.cs new file mode 100644 index 000000000..ea819cd56 --- /dev/null +++ b/AutoUpdatePlugin/Plugin.cs @@ -0,0 +1,157 @@ +using Newtonsoft.Json; +using System.IO.Compression; +using Terraria; +using TerrariaApi.Server; +using TShockAPI; + +namespace AutoUpdatePlugin; + +[ApiVersion(2, 1)] +public class Plugin : TerrariaPlugin +{ + public override string Name => "AutoUpdatePlugin"; + + public override Version Version => new(1, 6, 0, 2); + + public override string Author => "少司命,Cai"; + + public override string Description => "自动更新你的插件!"; + + private const string ReleaseUrl = "https://github.com/Controllerdestiny/TShockPlugin/releases/download/V1.0.0.0/Plugins.zip"; + + private const string PUrl = "https://github.moeyy.xyz/"; + + private const string PluginsUrl = "https://raw.githubusercontent.com/Controllerdestiny/TShockPlugin/master/Plugins.json"; + + private static readonly HttpClient _httpClient = new(); + + private const string TempSaveDir = "TempFile"; + + private const string TempZipName = "Plugins.zip"; + + public Plugin(Main game) : base(game) + { + + } + + public override void Initialize() + { + Commands.ChatCommands.Add(new("AutoUpdatePlugin", CheckCmd, "cplugin")); + Commands.ChatCommands.Add(new("AutoUpdatePlugin", UpdateCmd, "uplugin")); + } + + private void UpdateCmd(CommandArgs args) + { + try + { + var updates = GetUpdate(); + if (updates.Count == 0) + { + args.Player.SendSuccessMessage("你的插件全是最新版本,无需更新哦~"); + return; + } + args.Player.SendInfoMessage("正在下载最新插件包..."); + DownLoadPlugin(); + args.Player.SendInfoMessage("正在解压插件包..."); + ExtractDirectoryZip(); + args.Player.SendInfoMessage("正在升级插件..."); + UpdatePlugin(updates); + args.Player.SendSuccessMessage("[更新完成]\n" + string.Join("\n", updates.Select(i => $"[{i.Name}] V{i.OldVersion} >>> V{i.NewVersion}"))); + args.Player.SendSuccessMessage("重启服务器后插件生效!"); + } + catch (Exception ex) + { + args.Player.SendErrorMessage("自动更新出现错误:" + ex.Message); + return; + } + } + + private void CheckCmd(CommandArgs args) + { + try + { + var updates = GetUpdate(); + if (updates.Count == 0) + { + args.Player.SendSuccessMessage("你的插件全是最新版本,无需更新哦~"); + return; + } + args.Player.SendInfoMessage("[以下插件有新的版本更新]\n" + string.Join("\n", updates.Select(i => $"[{i.Name}] V{i.OldVersion} >>> V{i.NewVersion}"))); + } + catch (Exception ex) + { + args.Player.SendErrorMessage("无法获取更新:" + ex.Message); + return; + } + } + + #region 工具方法 + private static List GetUpdate() + { + var plugins = GetPlugins(); + HttpClient httpClient = new(); + var response = httpClient.GetAsync(PUrl + PluginsUrl).Result; + + if (!response.IsSuccessStatusCode) + throw new Exception("无法连接服务器"); + var json = response.Content.ReadAsStringAsync().Result; + var latestPluginList = JsonConvert.DeserializeObject>(json) ?? new(); + List pluginUpdateList = new(); + foreach (var latestPluginInfo in latestPluginList) + foreach (var plugin in plugins) + if (plugin.Name == latestPluginInfo.Name && plugin.Version != latestPluginInfo.Version) + pluginUpdateList.Add(new PluginUpdateInfo(plugin.Name, plugin.Author, latestPluginInfo.Version, plugin.Version, plugin.Path, latestPluginInfo.Path)); + return pluginUpdateList; + } + + private static List GetPlugins() + { + List plugins = new(); + foreach (var plugin in ServerApi.Plugins) + { + plugins.Add(new PluginVersionInfo() + { + AssemblyName = plugin.Plugin.GetType().Assembly.GetName().Name!, + Path = Path.Combine(ServerApi.ServerPluginsDirectoryPath, plugin.Plugin.GetType().Assembly.GetName().Name! + ".dll"), + Author = plugin.Plugin.Author, + Name = plugin.Plugin.Name, + Description = plugin.Plugin.Description, + Version = plugin.Plugin.Version.ToString() + }); + } + return plugins; + } + + + private static void DownLoadPlugin() + { + DirectoryInfo directoryInfo = new(TempSaveDir); + if (!directoryInfo.Exists) + directoryInfo.Create(); + HttpClient httpClient = new(); + var zipBytes = httpClient.GetByteArrayAsync(PUrl + ReleaseUrl).Result; + File.WriteAllBytes(Path.Combine(directoryInfo.FullName, TempZipName), zipBytes); + } + + private static void ExtractDirectoryZip() + { + DirectoryInfo directoryInfo = new(TempSaveDir); + ZipFile.ExtractToDirectory(Path.Combine(directoryInfo.FullName, TempZipName), Path.Combine(directoryInfo.FullName, "Plugins"), true); + } + + private static void UpdatePlugin(List pluginUpdateInfos) + { + foreach (var pluginUpdateInfo in pluginUpdateInfos) + { + string sourcePath = Path.Combine(TempSaveDir, "Plugins", pluginUpdateInfo.RemotePath); + string destinationPath = Path.Combine(ServerApi.ServerPluginsDirectoryPath, pluginUpdateInfo.LocalPath); + // 确保目标目录存在 + string destinationDirectory = Path.GetDirectoryName(destinationPath)!; + // 复制并覆盖文件 + File.Copy(sourcePath, destinationPath, true); + } + if (Directory.Exists(TempSaveDir)) + Directory.Delete(TempSaveDir, true); + } + #endregion +} diff --git a/AutoUpdatePlugin/PluginUpdateInfo.cs b/AutoUpdatePlugin/PluginUpdateInfo.cs new file mode 100644 index 000000000..9938f5662 --- /dev/null +++ b/AutoUpdatePlugin/PluginUpdateInfo.cs @@ -0,0 +1,21 @@ +namespace AutoUpdatePlugin; + +public class PluginUpdateInfo +{ + public PluginUpdateInfo(string name, string author, string newVersion, string oldVersion, string localPath, string remotePath) + { + NewVersion = newVersion; + OldVersion = oldVersion; + Author = author; + Name = name; + LocalPath = localPath; + RemotePath = remotePath; + } + public string NewVersion { get; set; } + public string OldVersion { get; set; } + public string Author { get; set; } + public string Name { get; set; } + public string LocalPath { get; set; } + public string RemotePath { get; set; } + +} diff --git a/AutoUpdatePlugin/PluginVersionInfo.cs b/AutoUpdatePlugin/PluginVersionInfo.cs new file mode 100644 index 000000000..e0ffe5bc0 --- /dev/null +++ b/AutoUpdatePlugin/PluginVersionInfo.cs @@ -0,0 +1,16 @@ +namespace AutoUpdatePlugin; + +public class PluginVersionInfo +{ + public string Version { get; set; } = string.Empty; + + public string Author { get; set; } = string.Empty; + + public string Name { get; set; } = string.Empty; + + public string Description { get; set; } = string.Empty; + + public string Path { get; set; } = string.Empty; + + public string AssemblyName { get; set; } = string.Empty; +} diff --git a/AutoUpdatePlugin/README.md b/AutoUpdatePlugin/README.md new file mode 100644 index 000000000..d44575000 --- /dev/null +++ b/AutoUpdatePlugin/README.md @@ -0,0 +1,26 @@ +# AutoUpdatePlugin 自动更新插件 + +- 作者: 少司命,Cai +- 出处: 本仓库 +- 使用指令自动更新服务器的插件(仅本仓库) + +## 更新日志 + +``` +暂无 +``` + +## 指令 + +| 语法 | 权限 | 说明 | +| -------------- | :-----------------: | :------: | +| /cplugin | AutoUpdatePlugin | 检查插件更新| +| /uplugin | AutoUpdatePlugin | 一键升级插件(需要重启服务器)| +## 配置 + +```json +暂无 +``` +## 反馈 +- 共同维护的插件库:https://github.com/Controllerdestiny/TShockPlugin +- 国内社区trhub.cn 或 TShock官方群等 \ No newline at end of file diff --git a/Plugin.sln b/Plugin.sln index 9f02c861d..1d81342bc 100644 --- a/Plugin.sln +++ b/Plugin.sln @@ -184,6 +184,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TimerKeeper", "TimerKeeper\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chameleon", "Chameleon\Chameleon.csproj", "{53CD6498-27DE-4C8C-A706-3CD2BD106D9E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdatePlugin", "AutoUpdatePlugin\AutoUpdatePlugin.csproj", "{9B88AC5E-E11C-4424-A5DC-5C1D094860B9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -896,6 +898,14 @@ Global {53CD6498-27DE-4C8C-A706-3CD2BD106D9E}.Release|Any CPU.Build.0 = Release|Any CPU {53CD6498-27DE-4C8C-A706-3CD2BD106D9E}.Release|x64.ActiveCfg = Release|Any CPU {53CD6498-27DE-4C8C-A706-3CD2BD106D9E}.Release|x64.Build.0 = Release|Any CPU + {9B88AC5E-E11C-4424-A5DC-5C1D094860B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B88AC5E-E11C-4424-A5DC-5C1D094860B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B88AC5E-E11C-4424-A5DC-5C1D094860B9}.Debug|x64.ActiveCfg = Debug|Any CPU + {9B88AC5E-E11C-4424-A5DC-5C1D094860B9}.Debug|x64.Build.0 = Debug|Any CPU + {9B88AC5E-E11C-4424-A5DC-5C1D094860B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B88AC5E-E11C-4424-A5DC-5C1D094860B9}.Release|Any CPU.Build.0 = Release|Any CPU + {9B88AC5E-E11C-4424-A5DC-5C1D094860B9}.Release|x64.ActiveCfg = Release|Any CPU + {9B88AC5E-E11C-4424-A5DC-5C1D094860B9}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index 9619e2028..673236c5f 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ | [Musicplayer](Musicplayer/README.md) | 简易音乐播放器 | 无 | | [TimerKeeper](TimerKeeper/README.md) | 保存计时器状态 | 无 | | [Chameleon](Chameleon/README.md) | 进服前登录 | 无 | - +| [AutoUpdatePlugin](AutoUpdatePlugin/README.md) | 一键自动更新插件 | 无 | ## 代码贡献 diff --git a/ServerTools/ClearPlayersItem.cs b/ServerTools/ClearPlayersItem.cs deleted file mode 100644 index c31a022e5..000000000 --- a/ServerTools/ClearPlayersItem.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Terraria; -using Microsoft.Xna.Framework; -using TShockAPI; - -namespace ServerTools -{ - internal class ClearPlayersItem - { - #region 清理盔甲组逻辑 - public void ClearItem(Item[] items, TSPlayer tSPlayer) - { - for (int i = 0; i < 10; i++) - { - foreach (Item item in items) - { - if (!tSPlayer.TPlayer.armor[i].IsAir && tSPlayer.TPlayer.armor[i].type == item.type) - { - tSPlayer.TPlayer.armor[i].TurnToAir(); - tSPlayer.SendData(PacketTypes.PlayerSlot, "", tSPlayer.Index, Terraria.ID.PlayerItemSlotID.Armor0 + i); - } - } - } - } - #endregion - } -} diff --git a/ServerTools/Plugin.cs b/ServerTools/Plugin.cs index e08b5926a..80f5b1f39 100644 --- a/ServerTools/Plugin.cs +++ b/ServerTools/Plugin.cs @@ -1,10 +1,6 @@ using Microsoft.Xna.Framework; using MonoMod.RuntimeDetour; using Newtonsoft.Json; -using NuGet.Configuration; -using NuGet.Protocol.Plugins; -using System.Linq; -using Terraria.DataStructures; using Terraria; using Terraria.GameContent.Creative; using TerrariaApi.Server; @@ -22,7 +18,7 @@ public partial class Plugin : TerrariaPlugin public override string Name => "ServerTools";// 插件名字 - public override Version Version => new(1, 1, 7, 0);// 插件版本 + public override Version Version => new(1, 1, 7, 1);// 插件版本 private static Config Config = new(); @@ -38,8 +34,6 @@ public partial class Plugin : TerrariaPlugin public static Hook CmdHook; - private static ClearPlayersItem clear = new(); - public Plugin(Main game) : base(game) { @@ -112,30 +106,11 @@ private void OnUpdate(object? sender, GetDataHandlers.PlayerUpdateEventArgs e) e.Player.SetBuff(156, 180, true); TShock.Utils.Broadcast($"[ServerTools] 玩家 [{e.Player.Name}] 因多饰品被冻结3秒,自动施行清理多饰品装备[i:{keepArmor.netID}]", Color.DarkRed); } - if (ArmorGroup.Any() && TimerCount % 20 == 0) - clear.ClearItem(ArmorGroup.ToArray(), e.Player); - - if (Config.KeepArmor2 && !Main.hardMode) { Clear7Item(e.Player); } - } + if (ArmorGroup.Any()) + Utils.ClearItem(ArmorGroup.ToArray(), e.Player); - private static void Clear7Item(TSPlayer args) - { - if (!args.TPlayer.armor[8].IsAir && TimerCount % 20 == 0) - { - Item i = args.TPlayer.armor[8]; - GiveItem(args, i.type, i.stack, i.prefix); - args.TPlayer.armor[8].TurnToAir(); - args.SendData(PacketTypes.PlayerSlot, "", args.Index, Terraria.ID.PlayerItemSlotID.Armor0 + 8); - TShock.Utils.Broadcast($"[ServerTools] 世界未开启困难模式,禁止玩家 [{args.Name}]使用恶魔心饰品栏", Color.DarkRed); - } - } - - private static void GiveItem(TSPlayer p, int type, int stack, int prefix = 0) - { - int num = Item.NewItem(new EntitySource_DebugCommand(), (int)p.TPlayer.Center.X, (int)p.TPlayer.Center.Y, p.TPlayer.width, p.TPlayer.height, type, stack, true, prefix, true, false); - Main.item[num].playerIndexTheItemIsReservedFor = p.Index; - p.SendData(PacketTypes.ItemDrop, "", num, 1f, 0f, 0f, 0); - p.SendData(PacketTypes.ItemOwner, null, num, 0f, 0f, 0f, 0); + if (Config.KeepArmor2 && !Main.hardMode) + Utils.Clear7Item(e.Player); } #endregion diff --git a/ServerTools/Utils.cs b/ServerTools/Utils.cs new file mode 100644 index 000000000..f32625131 --- /dev/null +++ b/ServerTools/Utils.cs @@ -0,0 +1,38 @@ +using Terraria; +using Microsoft.Xna.Framework; +using TShockAPI; + +namespace ServerTools; + +internal class Utils +{ + public static void Clear7Item(TSPlayer Player) + { + if (!Player.TPlayer.armor[8].IsAir) + { + Item item = Player.TPlayer.armor[8]; + Player.GiveItem(item.type, item.stack, item.prefix); + Player.TPlayer.armor[8].TurnToAir(); + Player.SendData(PacketTypes.PlayerSlot, "", Player.Index, Terraria.ID.PlayerItemSlotID.Armor0 + 8); + TShock.Utils.Broadcast($"[ServerTools] 世界未开启困难模式,禁止玩家 [{Player.Name}]使用恶魔心饰品栏", Color.DarkRed); + } + } + + + #region 清理盔甲组逻辑 + public static void ClearItem(Item[] items, TSPlayer tSPlayer) + { + for (int i = 0; i < 10; i++) + { + foreach (Item item in items) + { + if (!tSPlayer.TPlayer.armor[i].IsAir && tSPlayer.TPlayer.armor[i].type == item.type) + { + tSPlayer.TPlayer.armor[i].TurnToAir(); + tSPlayer.SendData(PacketTypes.PlayerSlot, "", tSPlayer.Index, Terraria.ID.PlayerItemSlotID.Armor0 + i); + } + } + } + } + #endregion +}