diff --git a/Commands/Chat/AcCommand.cs b/Commands/Chat/AcCommand.cs new file mode 100644 index 0000000..0c2f14a --- /dev/null +++ b/Commands/Chat/AcCommand.cs @@ -0,0 +1,44 @@ +using CommandSystem; +using EasyTools.Configs; +using EasyTools.Events; +using EasyTools.Utils; +using LabApi.Features.Wrappers; +using System; +using Log = LabApi.Features.Console.Logger; + +namespace EasyTools.Commands.Chat +{ + [CommandHandler(typeof(ClientCommandHandler))] + public class AcCommand : ICommand + { + public string Command => "ac"; + + public string[] Aliases => []; + + public string Description => "私聊管理-Talk to Admin"; + + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + TranslateConfig TranslateConfig = CustomEventHandler.TranslateConfig; + Player player; + + if (sender is null || (player = Player.Get(sender)) is null) + { + response = TranslateConfig.ChatCommandError; + return false; + } + + if (arguments.Count == 0 || player.IsMuted || !CustomEventHandler.Config.EnableAcSystem) + { + response = TranslateConfig.ChatCommandFailed; + return false; + } + + ChatUtils.SendMessage(player, ChatMessage.MessageType.AdminPrivateChat, $"{string.Join(" ", arguments)}"); + + Log.Info(player.Nickname + " 发送了 " + arguments.At(0)); + response = TranslateConfig.ChatCommandOk; + return true; + } + } +} diff --git a/Commands/Chat/BcCommand.cs b/Commands/Chat/BcCommand.cs new file mode 100644 index 0000000..e1c77ec --- /dev/null +++ b/Commands/Chat/BcCommand.cs @@ -0,0 +1,44 @@ +using CommandSystem; +using EasyTools.Configs; +using EasyTools.Events; +using EasyTools.Utils; +using LabApi.Features.Wrappers; +using System; +using Log = LabApi.Features.Console.Logger; + +namespace EasyTools.Commands.Chat +{ + [CommandHandler(typeof(ClientCommandHandler))] + public class BcCommand : ICommand + { + public string Command => "BC"; + + public string[] Aliases => []; + + public string Description => "全服聊天-PublicChat"; + + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + TranslateConfig TranslateConfig = CustomEventHandler.TranslateConfig; + Player player; + + if (sender is null || (player = Player.Get(sender)) is null) + { + response = TranslateConfig.ChatCommandError; + return false; + } + + if (arguments.Count == 0 || player.IsMuted || !CustomEventHandler.Config.EnableChatSystem) + { + response = TranslateConfig.ChatCommandFailed; + return false; + } + + ChatUtils.SendMessage(player, ChatMessage.MessageType.BroadcastChat, $"{string.Join(" ", arguments)}"); + + Log.Info(player.Nickname + " 发送了 " + arguments.At(0)); + response = TranslateConfig.RescueCommandOk; + return true; + } + } +} diff --git a/Commands/Chat/CCommand.cs b/Commands/Chat/CCommand.cs new file mode 100644 index 0000000..2f77026 --- /dev/null +++ b/Commands/Chat/CCommand.cs @@ -0,0 +1,45 @@ +using CommandSystem; +using EasyTools.Configs; +using EasyTools.Events; +using EasyTools.Utils; +using LabApi.Features.Wrappers; +using System; +using Log = LabApi.Features.Console.Logger; + +namespace EasyTools.Commands.Chat +{ + [CommandHandler(typeof(ClientCommandHandler))] + public class CCommand : ICommand + { + public string Command => "C"; + + public string[] Aliases => ["CC"]; + + public string Description => "队伍聊天-TeamChat"; + + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + TranslateConfig TranslateConfig = CustomEventHandler.TranslateConfig; + Player player; + + if (sender is null || (player = Player.Get(sender)) is null) + { + response = TranslateConfig.ChatCommandError; + return false; + } + + if (arguments.Count == 0 || player.IsMuted || !CustomEventHandler.Config.EnableChatSystem) + { + response = TranslateConfig.ChatCommandFailed; + return false; + } + + ChatUtils.SendMessage(player, ChatMessage.MessageType.TeamChat, $"{string.Join(" ", arguments)}"); + + Log.Info(player.Nickname + " 发送了 " + arguments.At(0)); + + response = TranslateConfig.ChatCommandOk; + return true; + } + } +} diff --git a/Commands/RescueCommand.cs b/Commands/RescueCommand.cs new file mode 100644 index 0000000..0659d4b --- /dev/null +++ b/Commands/RescueCommand.cs @@ -0,0 +1,48 @@ +using CommandSystem; +using EasyTools.Configs; +using EasyTools.Events; +using LabApi.Features.Wrappers; +using RelativePositioning; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EasyTools.Commands +{ + [CommandHandler(typeof(ClientCommandHandler))] + public class RescueCommand : ICommand + { + public string Command => "killme"; + + public string[] Aliases => ["suicide"]; + + public string Description => "防卡死命令"; + + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + Player player; + TranslateConfig TranslateConfig = CustomEventHandler.TranslateConfig; + + if (sender is null || (player = Player.Get(sender)) is null) + { + response = TranslateConfig.RescueCommandError; + return false; + } + + WaypointBase.GetRelativePosition(player.Position, out byte id, out _); + + if (!player.IsAlive || !CustomEventHandler.Config.KillMeCommand) + { + response = TranslateConfig.RescueCommandFailed; + return false; + } + + player.Kill(); + + response = TranslateConfig.RescueCommandOk; + return true; + } + } +} diff --git a/Configs/Config.cs b/Configs/Config.cs new file mode 100644 index 0000000..10667a3 --- /dev/null +++ b/Configs/Config.cs @@ -0,0 +1,89 @@ +using LabApi.Loader.Features.Paths; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EasyTools.Configs +{ + public class Config + { + [Description("是否打开开局给D级人员一张卡")] + public bool EnableRoundSupplies { get; set; } = true; + [Description("给D级人员什么卡?")] + public ItemType ClassDCard { get; set; } = ItemType.KeycardJanitor; + + /// ///////////////////////////////////////////////// + [Description("启用服务器定时广播")] + public bool EnableAutoServerMessage { get; set; } = true; + [Description("服务器定时广播多久一次")] + public int AutoServerMessageTime { get; set; } = 5; + [Description("服务器定时广播显示多久")] + public ushort AutoServerMessageTimer { get; set; } = 5; + + [Description("服务器广播文本")] + public List AutoServerMessageText { get; set; } = ["服务器广播1","服务器广播2"]; + + /// ///////////////////////////////////////////////// + [Description("启用.bc和.c聊天系统")] + public bool EnableChatSystem { get; set; } = true; + [Description("聊天系统大小,默认20")] + public int ChatSystemSize { get; set; } = 20; + + [Description("启用玩家反馈管理系统")] + public bool EnableAcSystem { get; set; } = true; + [Description("一个人的聊天信息显示多久,单位为秒")] + public int MessageTime { get; set; } = 10; + + [Description("聊天消息的格式,可用的标签为{Message},{MessageType}, {MessageTypeColor}, {SenderNickname},{SenderTeam},{SenderRole} ,{SenderTeamColor}, {CountDown}")] + public string MessageTemplate { get; set; } = "[{CountDown}][{SenderTeam}][{SenderRole}][{MessageType}]{SenderNickname}: {Message}"; + + // ///////////////////////////////////////////////// + [Description("防卡死命令")] + public bool KillMeCommand { get; set; } = true; + + /// ///////////////////////////////////////////////// + [Description("启用修改SCP血量系统")] + public bool EnableChangeSCPHPSystem { get; set; } = true; + [Description("SCP173,SCP939,SCP049,SCP049-2,SCP096,SCP106血量")] + public List SCPsHP { get; set; } = [4200, 2700, 2300, 400, 2500, 2300]; + + /// ///////////////////////////////////////////////// + [Description("是否开启保安下班")] + public bool GuardsCanEscape { get; set; } = false; + + [Description("选择保安下班变成的角色 (NtfSergeant, NtfCaptain, NtfPrivate, NtfSpecialist)")] + public string EscapedGuardRole { get; set; } = "NtfCaptain"; + + /// ///////////////////////////////////////////////// + [Description("是否开启粉糖")] + public bool EnablePinkCandy { get; set; } = false; + + [Description("粉糖生成概率(默认50%)")] + public int PinkCandyWeight { get; set; } = 2; + + [Description("人类阵营重生获得粉糖?(默认关闭)")] + public bool PinkCandyRespawn { get; set; } = false; + + /// ///////////////////////////////////////////////// + [Description("Logger module settings / 日志模块设置")] + public bool EnableLogger { get; set; } = true; + + [Description("Logger module settings / 日志保存路径")] + public string LoggerSavePath { get; set; } = Path.Combine(PathManager.Configs.FullName ?? Environment.CurrentDirectory, @"JoinLogs.txt"); + + [Description("管理日志地址")] + public string AdminLogPath { get; set; } = $"{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}\\Admins.txt"; + + /// ///////////////////////////////////////////////// + [Description("Is 207 harmless? / 是否开启207(可乐)无害?")] + public bool harmless_207 { get; set; } = true; + + + [Description("Is 1853 harmless? / 是否开启1853(洗手液)无害?")] + public bool harmless_1853 { get; set; } = true; + } +} diff --git a/Configs/TranslateConfig.cs b/Configs/TranslateConfig.cs new file mode 100644 index 0000000..9fbc179 --- /dev/null +++ b/Configs/TranslateConfig.cs @@ -0,0 +1,81 @@ +using EasyTools.Utils; +using PlayerRoles; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EasyTools.Configs +{ + public class TranslateConfig + { + [Description("卡虚空自救指令_错误")] + public string RescueCommandError { get; set; } = "执行指令时发生错误,请稍后再试"; + [Description("卡虚空自救指令_失败")] + public string RescueCommandFailed { get; set; } = "失败,可能指令未启用或者身份不允许等"; + [Description("卡虚空自救指令_成功")] + public string RescueCommandOk { get; set; } = "成功"; + [Description("聊天指令_错误")] + public string ChatCommandError { get; set; } = "发送消息时出现错误,请稍后重试"; + [Description("聊天指令_失败")] + public string ChatCommandFailed { get; set; } = "发送失败,你被禁言或者信息为空或者聊天系统未启用"; + [Description("聊天指令_成功")] + public string ChatCommandOk { get; set; } = "发送成功"; + /// + /// + /// + [Description("聊天中消息列表的标题")] + public string ChatMessageTitle { get; set; } = "消息列表:"; + + [Description("聊天中每种消息的名字")] + public Dictionary MessageTypeName { get; set; } = new() + { + { ChatMessage.MessageType.AdminPrivateChat, "管理私聊" }, + { ChatMessage.MessageType.BroadcastChat, "公共消息" }, + { ChatMessage.MessageType.TeamChat, "队友消息" }, + }; + /// + /// + /// + [Description("聊天系统自定义玩家角色名称")] + public Dictionary ChatSystemRoleTranslation { get; set; } = new Dictionary + { + {RoleTypeId.ClassD , "D级人员"}, + {RoleTypeId.FacilityGuard , "保安" }, + {RoleTypeId.ChaosConscript , "混沌征召兵"}, + {RoleTypeId.ChaosMarauder , "混沌掠夺者"}, + {RoleTypeId.ChaosRepressor , "混沌压制者"}, + {RoleTypeId.ChaosRifleman , "混沌步枪兵"}, + {RoleTypeId.NtfCaptain , "九尾狐指挥官"}, + {RoleTypeId.NtfPrivate , "九尾狐列兵"}, + {RoleTypeId.NtfSergeant , "九尾狐中士"}, + {RoleTypeId.NtfSpecialist , "九尾狐收容专家"}, + {RoleTypeId.Scientist , "科学家"}, + {RoleTypeId.Tutorial , "教程角色"}, + {RoleTypeId.Scp096 , "SCP-096" }, + {RoleTypeId.Scp049 , "SCP-049" }, + {RoleTypeId.Scp173 , "SCP-173" }, + {RoleTypeId.Scp939 , "SCP-939" }, + {RoleTypeId.Scp079 , "SCP-079" }, + {RoleTypeId.Scp0492 , "SCP-049-2" }, + {RoleTypeId.Scp106 , "SCP-106" }, + {RoleTypeId.Scp3114 , "SCP-3114" }, + {RoleTypeId.Spectator , "观察者" }, + {RoleTypeId.Overwatch , "监管" }, + {RoleTypeId.Filmmaker , "导演" } + }; + [Description("聊天系统自定义玩家团队名称")] + public Dictionary ChatSystemTeamTranslation { get; set; } = new Dictionary + { + {Team.Dead , "旁观者" }, + {Team.ClassD , "DD阵营" }, + {Team.OtherAlive , "神秘阵营" }, + {Team.Scientists , "博士阵营" }, + {Team.SCPs , "SCP阵营" }, + {Team.ChaosInsurgency , "混沌阵营" }, + {Team.FoundationForces , "九尾狐阵营" }, + }; + } +} diff --git a/EasyTools.csproj b/EasyTools.csproj new file mode 100644 index 0000000..a71817f --- /dev/null +++ b/EasyTools.csproj @@ -0,0 +1,136 @@ + + + + + Debug + AnyCPU + {A383B034-E591-41C9-8C3A-6CB08752DB5A} + Library + Properties + EasyTools + EasyTools + v4.8 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + 12.0 + prompt + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + 12.0 + prompt + + + + packages\Lib.Harmony.2.3.6\lib\net48\0Harmony.dll + + + D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll + + + D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp-firstpass.dll + + + ..\HelpSense-Github\lib\net48\Assembly-CSharp-Publicized.dll + + + D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\CommandSystem.Core.dll + + + packages\HintServiceMeow.5.4.1\lib\net48\HintServiceMeow.dll + + + packages\Northwood.LabAPI.1.1.4\lib\net48\LabApi.dll + + + False + D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll + + + D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\NorthwoodLib.dll + + + False + D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Pooling.dll + + + + + packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + True + True + + + False + D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\System.IO.Pipelines.dll + + + + + + + + + D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.dll + + + D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll + + + D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.Physics2DModule.dll + + + D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.PhysicsModule.dll + + + packages\YamlDotNet.11.0.1\lib\net45\YamlDotNet.dll + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EasyTools.sln b/EasyTools.sln new file mode 100644 index 0000000..1177f76 --- /dev/null +++ b/EasyTools.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35707.178 d17.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyTools", "EasyTools.csproj", "{A383B034-E591-41C9-8C3A-6CB08752DB5A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A383B034-E591-41C9-8C3A-6CB08752DB5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A383B034-E591-41C9-8C3A-6CB08752DB5A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A383B034-E591-41C9-8C3A-6CB08752DB5A}.Debug|x64.ActiveCfg = Debug|x64 + {A383B034-E591-41C9-8C3A-6CB08752DB5A}.Debug|x64.Build.0 = Debug|x64 + {A383B034-E591-41C9-8C3A-6CB08752DB5A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A383B034-E591-41C9-8C3A-6CB08752DB5A}.Release|Any CPU.Build.0 = Release|Any CPU + {A383B034-E591-41C9-8C3A-6CB08752DB5A}.Release|x64.ActiveCfg = Release|x64 + {A383B034-E591-41C9-8C3A-6CB08752DB5A}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Events/CustomEventHandler.cs b/Events/CustomEventHandler.cs new file mode 100644 index 0000000..3b28043 --- /dev/null +++ b/Events/CustomEventHandler.cs @@ -0,0 +1,227 @@ +using CommandSystem.Commands.RemoteAdmin.Decontamination; +using EasyTools.Configs; +using EasyTools.Utils; +using GameCore; +using InventorySystem.Items; +using LabApi.Events.Arguments.PlayerEvents; +using LabApi.Events.Arguments.ServerEvents; +using LabApi.Events.CustomHandlers; +using LabApi.Features.Wrappers; +using MEC; +using PlayerRoles; +using PlayerStatsSystem; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine.LowLevel; +using static Broadcast; +using Log = LabApi.Features.Console.Logger; + +namespace EasyTools.Events +{ + public class CustomEventHandler : CustomEventsHandler + { + public static Config Config; + + public static TranslateConfig TranslateConfig; + + public override void OnServerRoundStarted() + { + Timing.CallDelayed(10f, () => + { + if (Config.EnableAutoServerMessage) + { + Timing.RunCoroutine(Util.AutoServerBroadcast()); + } + }); + } + + public override void OnPlayerJoined(PlayerJoinedEventArgs ev) + { + base.OnPlayerJoined(ev); + + Player player = ev.Player; + + if (player == null || string.IsNullOrEmpty(player.UserId)) return; + + ChatUtils.InitForPlayer(player); + + if (Config.EnableLogger) + { + string playerIP = ev.Player.IpAddress; + string playerInfo = $"[JOIN] Date: {DateTime.Now} | Player: {ev.Player.Nickname} | IP: {playerIP} | Steam64ID: {ev.Player.UserId}"; + Log.Info(playerInfo); + + File.AppendAllText(Config.LoggerSavePath, playerInfo + Environment.NewLine); + } + } + + public override void OnPlayerHurting(PlayerHurtingEventArgs ev) + { + if (Config.harmless_207) + { + if (ev.DamageHandler is UniversalDamageHandler && ev.DamageHandler.DeathScreenText.Contains("SCP-207")) + { + ev.IsAllowed = false; + } + } + + if (Config.harmless_1853) + { + if (ev.DamageHandler is UniversalDamageHandler && ev.DamageHandler.DeathScreenText.Contains("poison")) + { + ev.IsAllowed = false; + } + } + } + + public override void OnPlayerLeft(PlayerLeftEventArgs ev) + { + Player player = ev.Player; + + if (player == null || string.IsNullOrEmpty(player.UserId)) return; + + if (Config.EnableLogger) + { + string playerIP = ev.Player.IpAddress; + string playerInfo = $"[EXIT] Date: {DateTime.Now} | Player: {ev.Player.Nickname} | IP: {playerIP} | Steam64ID: {ev.Player.UserId}"; + Log.Info(playerInfo); + + File.AppendAllText(Config.LoggerSavePath, playerInfo + Environment.NewLine); + } + } + + public override void OnPlayerEscaping(PlayerEscapingEventArgs ev) + { + if (Config.GuardsCanEscape) + { + RoleTypeId id; + switch (Config.EscapedGuardRole) + { + case "NtfSergeant": + id = RoleTypeId.NtfSergeant; + break; + case "NtfPrivate": + id = RoleTypeId.NtfPrivate; + break; + case "NtfSpecialist": + id = RoleTypeId.NtfSpecialist; + break; + default: + id = RoleTypeId.NtfCaptain; + break; + + } + + if (ev.Player.Role == RoleTypeId.FacilityGuard) + { + ev.Player.SetRole(id); + ev.IsAllowed = true; + } + } + } + + public override void OnPlayerSpawned(PlayerSpawnedEventArgs ev) + { + Player Player = ev.Player; + RoleTypeId Role = ev.Role.RoleTypeId; + + Dictionary healthDict = new() + { + [RoleTypeId.Scp173] = CustomEventHandler.Config.SCPsHP[0], + [RoleTypeId.Scp939] = CustomEventHandler.Config.SCPsHP[1], + [RoleTypeId.Scp049] = CustomEventHandler.Config.SCPsHP[2], + [RoleTypeId.Scp0492] = CustomEventHandler.Config.SCPsHP[3], + [RoleTypeId.Scp096] = CustomEventHandler.Config.SCPsHP[4], + [RoleTypeId.Scp106] = CustomEventHandler.Config.SCPsHP[5] + }; + + if (Config.EnableRoundSupplies) + { + if (Role is RoleTypeId.ClassD) + { + Timing.CallDelayed(0.5f, () => + { + Player.AddItem(Config.ClassDCard, ItemAddReason.AdminCommand); + }); + } + } + + if (Config.EnableChangeSCPHPSystem && Player.IsSCP) + { + Timing.CallDelayed(0.5f, () => + { + if (healthDict.TryGetValue(Role, out var health)) + { + Player.Health = health; + Player.MaxHealth = health; + } + }); + } + + if (Config.PinkCandyRespawn && Player.IsHuman) + { + Timing.CallDelayed(0.5f, () => + { + Player.GiveCandy(InventorySystem.Items.Usables.Scp330.CandyKindID.Pink, ItemAddReason.AdminCommand); + }); + } + + } + + public override void OnPlayerInteractingScp330(PlayerInteractingScp330EventArgs ev) + { + if (Config.EnablePinkCandy) + { + if (new System.Random().Next(1, Config.PinkCandyWeight + 1) == 1) + { + ev.CandyType = InventorySystem.Items.Usables.Scp330.CandyKindID.Pink; + ev.IsAllowed = true; + } + } + } + + public override void OnServerCommandExecuted(CommandExecutedEventArgs ev) + { + var sender = ev.Sender; + var command = ev.Command.Command; + + Player player = Player.Get(sender); + + if (player != null && !string.IsNullOrEmpty(command) && Config.EnableLogger) + { + if (!player.RemoteAdminAccess) return; + + + string note = $"Date: {DateTime.Now} | Player: {player.Nickname} | Command: {command} | Steam64ID: {player.UserId}"; + Log.Info(note); + try + { + if (!File.Exists(Config.AdminLogPath)) + { + FileStream fs1 = new(Config.AdminLogPath, FileMode.Create, FileAccess.Write); + StreamWriter sw = new(fs1); + sw.WriteLine(note); + sw.Close(); + fs1.Close(); + } + else + { + FileStream fs = new(Config.AdminLogPath, FileMode.Append, FileAccess.Write); + StreamWriter sr = new(fs); + sr.WriteLine(note); + sr.Close(); + fs.Close(); + } + } + catch (Exception e) + { + Log.Error(e.Message); + } + } + } + } +} diff --git a/Plugins.cs b/Plugins.cs new file mode 100644 index 0000000..3a8df1c --- /dev/null +++ b/Plugins.cs @@ -0,0 +1,58 @@ +using EasyTools.Configs; +using EasyTools.Events; +using LabApi.Events.CustomHandlers; +using LabApi.Features; +using LabApi.Loader; +using LabApi.Loader.Features.Plugins; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EasyTools +{ + public class Plugins : Plugin + { + + public CustomEventHandler Events { get; } = new(); + + public override void LoadConfigs() + { + base.LoadConfigs(); + + CustomEventHandler.Config = this.LoadConfig("config.yml"); + CustomEventHandler.TranslateConfig = this.LoadConfig("translateConfig.yml"); + } + + + public static System.Version RequiredGameVersion => new(14, 1, 1); + + public static Plugins Instance { get; private set; } + + public override string Name => "EasyTools"; + + public override string Description => "Server Tools"; + + public override string Author => "3cxc"; + + public override System.Version Version => new(1, 1, 0); + + public override System.Version RequiredApiVersion => new(LabApiProperties.CompiledVersion); + + public override void Enable() + { + Instance = this; + + CustomHandlersManager.RegisterEventsHandler(Events); + } + + public override void Disable() + { + CustomHandlersManager.UnregisterEventsHandler(Events); + + Instance = null; + } + + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0be70db --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("EasyTools")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("EasyTools")] +[assembly: AssemblyCopyright("Copyright © 2025")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("a383b034-e591-41c9-8c3a-6cb08752db5a")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Utils/ChatUtils.cs b/Utils/ChatUtils.cs new file mode 100644 index 0000000..852bb28 --- /dev/null +++ b/Utils/ChatUtils.cs @@ -0,0 +1,150 @@ +using EasyTools.Events; +using EasyTools.Utils.Pool; +using HintServiceMeow.Core.Utilities; +using LabApi.Features.Wrappers; +using MEC; +using PlayerRoles; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EasyTools.Utils +{ + + public class ChatMessage(Player sender, ChatMessage.MessageType type, string message) + { + public enum MessageType + { + /// + /// Chat privately with admins + /// + AdminPrivateChat, + /// + /// Chat with all players + /// + BroadcastChat, + /// + /// Chat with all teammates + /// + TeamChat, + } + + public DateTime TimeSent { get; } = DateTime.Now; + + public MessageType Type { get; } = type; + public string Message { get; } = message; + + public string SenderName { get; } = sender.DisplayName; + public Team SenderTeam { get; } = sender.Team; + public RoleTypeId SenderRole { get; } = sender.Role; + } + + public class ChatUtils + { + private static CoroutineHandle _coroutine; + + private static readonly LinkedList MessageList = new(); + + private static readonly Dictionary MessageSlot = []; + + private static bool HaveAccess(Player player, ChatMessage message) + { + if ((DateTime.Now - message.TimeSent).TotalSeconds > CustomEventHandler.Config.MessageTime) + return false; + + return message.Type switch + { + ChatMessage.MessageType.AdminPrivateChat => player.RemoteAdminAccess, + ChatMessage.MessageType.BroadcastChat => true, + ChatMessage.MessageType.TeamChat => player.Team == message.SenderTeam, + _ => false, + }; + } + + private static IEnumerator MessageCoroutineMethod() + { + while (true) + { + var sb = StringBuilderPool.Pool.Get(); + + foreach (var messageSlot in MessageSlot) + { + if (!MessageList.Any(x => HaveAccess(messageSlot.Key, x))) + { + messageSlot.Value.Text = string.Empty; + continue; + } + + sb.AppendLine(CustomEventHandler.TranslateConfig.ChatMessageTitle); + + foreach (var message in MessageList) + { + if (HaveAccess(messageSlot.Key, message)) + { + string messageStr = CustomEventHandler.Config.MessageTemplate + .Replace("{Message}", message.Message) + .Replace("{MessageType}", CustomEventHandler.TranslateConfig.MessageTypeName[message.Type]) + .Replace("{MessageTypeColor}", message.Type switch + { + ChatMessage.MessageType.AdminPrivateChat => "red", + _ => "{SenderTeamColor}",//Replace by sender's team color later + }) + .Replace("{SenderNickname}", message.SenderName) + .Replace("{SenderTeam}", CustomEventHandler.TranslateConfig.ChatSystemTeamTranslation[message.SenderTeam]) + .Replace("{SenderRole}", CustomEventHandler.TranslateConfig.ChatSystemRoleTranslation[message.SenderRole]) + .Replace("{SenderTeamColor}", message.SenderTeam switch + { + Team.SCPs => "red", + Team.ChaosInsurgency => "green", + Team.Scientists => "yellow", + Team.ClassD => "orange", + Team.Dead => "white", + Team.FoundationForces => "#4EFAFF", + _ => "white" + }) + .Replace("{CountDown}", (CustomEventHandler.Config.MessageTime - (int)(DateTime.Now - message.TimeSent).TotalSeconds).ToString()); + + + sb.AppendLine(messageStr); + } + } + + messageSlot.Value.Text = sb.ToString(); + sb.Clear(); + } + + yield return Timing.WaitForSeconds(0.5f); + } + } + + public static void InitForPlayer(Player player) + { + if (!_coroutine.IsRunning) + _coroutine = Timing.RunCoroutine(MessageCoroutineMethod()); + + if (MessageSlot.ContainsKey(player)) + { + return; + } + + MessageSlot[player] = new HintServiceMeow.Core.Models.Hints.Hint + { + Alignment = HintServiceMeow.Core.Enum.HintAlignment.Left, + YCoordinate = 250, + FontSize = CustomEventHandler.Config.ChatSystemSize, + LineHeight = 5 + }; + + PlayerDisplay.Get(player.ReferenceHub).AddHint(MessageSlot[player]); + } + + public static void SendMessage(Player sender, ChatMessage.MessageType type, string message) => SendMessage(new ChatMessage(sender, type, message)); + + public static void SendMessage(ChatMessage message) + { + MessageList.AddFirst(message); + } + } +} diff --git a/Utils/Pool/IPool.cs b/Utils/Pool/IPool.cs new file mode 100644 index 0000000..d9e16f6 --- /dev/null +++ b/Utils/Pool/IPool.cs @@ -0,0 +1,9 @@ +namespace EasyTools.Utils.Pool +{ + public interface IPool + { + public T Get(); + + public void Return(T obj); + } +} diff --git a/Utils/Pool/StringBuilderPool.cs b/Utils/Pool/StringBuilderPool.cs new file mode 100644 index 0000000..0d3def1 --- /dev/null +++ b/Utils/Pool/StringBuilderPool.cs @@ -0,0 +1,29 @@ +using System.Text; +using BasePools = NorthwoodLib.Pools; + +namespace EasyTools.Utils.Pool +{ + public class StringBuilderPool : IPool + { + private StringBuilderPool() + { + } + + public static StringBuilderPool Pool { get; } = new(); + + public StringBuilder Get() => BasePools.StringBuilderPool.Shared.Rent(); + + public StringBuilder Get(int capacity) => BasePools.StringBuilderPool.Shared.Rent(capacity); + + public void Return(StringBuilder obj) => BasePools.StringBuilderPool.Shared.Return(obj); + + public string ToStringReturn(StringBuilder obj) + { + string s = obj.ToString(); + + Return(obj); + + return s; + } + } +} diff --git a/Utils/Util.cs b/Utils/Util.cs new file mode 100644 index 0000000..b4e4b4d --- /dev/null +++ b/Utils/Util.cs @@ -0,0 +1,31 @@ +using EasyTools.Events; +using LabApi.Features.Wrappers; +using MEC; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EasyTools.Utils +{ + public class Util + { + public static IEnumerator AutoServerBroadcast() + { + while (true) + { + if (Round.IsRoundEnded || !Round.IsRoundStarted) + { + yield break; + } + + //随机公告 + int tmp = new Random().Next(0, CustomEventHandler.Config.AutoServerMessageText.Count - 1); + + Server.SendBroadcast(CustomEventHandler.Config.AutoServerMessageText[tmp], CustomEventHandler.Config.AutoServerMessageTimer, global::Broadcast.BroadcastFlags.Normal); + yield return Timing.WaitForSeconds(CustomEventHandler.Config.AutoServerMessageTime * 60f); + } + } + } +} diff --git a/packages.config b/packages.config new file mode 100644 index 0000000..3846e5b --- /dev/null +++ b/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file