This commit is contained in:
3cxc
2025-12-25 18:21:46 +08:00
parent 990bd00aba
commit 9ab2bd7c86
16 changed files with 1060 additions and 0 deletions

View File

@@ -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<string> 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, $"<noparse>{string.Join(" ", arguments)}</noparse>");
Log.Info(player.Nickname + " 发送了 " + arguments.At(0));
response = TranslateConfig.ChatCommandOk;
return true;
}
}
}

View File

@@ -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<string> 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, $"<noparse>{string.Join(" ", arguments)}</noparse>");
Log.Info(player.Nickname + " 发送了 " + arguments.At(0));
response = TranslateConfig.RescueCommandOk;
return true;
}
}
}

45
Commands/Chat/CCommand.cs Normal file
View File

@@ -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<string> 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, $"<noparse>{string.Join(" ", arguments)}</noparse>");
Log.Info(player.Nickname + " 发送了 " + arguments.At(0));
response = TranslateConfig.ChatCommandOk;
return true;
}
}
}

48
Commands/RescueCommand.cs Normal file
View File

@@ -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<string> 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;
}
}
}

89
Configs/Config.cs Normal file
View File

@@ -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<string> 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}]<color={{SenderTeamColor}}>[{SenderTeam}][{SenderRole}]</color><color={MessageTypeColor}>[{MessageType}]</color>{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<float> 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;
}
}

View File

@@ -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; } = "发送成功";
/// <summary>
///
/// </summary>
[Description("聊天中消息列表的标题")]
public string ChatMessageTitle { get; set; } = "消息列表:";
[Description("聊天中每种消息的名字")]
public Dictionary<ChatMessage.MessageType, string> MessageTypeName { get; set; } = new()
{
{ ChatMessage.MessageType.AdminPrivateChat, "管理私聊" },
{ ChatMessage.MessageType.BroadcastChat, "公共消息" },
{ ChatMessage.MessageType.TeamChat, "队友消息" },
};
/// <summary>
///
/// </summary>
[Description("聊天系统自定义玩家角色名称")]
public Dictionary<RoleTypeId, string> ChatSystemRoleTranslation { get; set; } = new Dictionary<RoleTypeId, string>
{
{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<Team, string> ChatSystemTeamTranslation { get; set; } = new Dictionary<Team, string>
{
{Team.Dead , "旁观者" },
{Team.ClassD , "DD阵营" },
{Team.OtherAlive , "神秘阵营" },
{Team.Scientists , "博士阵营" },
{Team.SCPs , "SCP阵营" },
{Team.ChaosInsurgency , "混沌阵营" },
{Team.FoundationForces , "九尾狐阵营" },
};
}
}

136
EasyTools.csproj Normal file
View File

@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A383B034-E591-41C9-8C3A-6CB08752DB5A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>EasyTools</RootNamespace>
<AssemblyName>EasyTools</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>12.0</LangVersion>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>12.0</LangVersion>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<Reference Include="0Harmony, Version=2.3.6.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>packages\Lib.Harmony.2.3.6\lib\net48\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-Publicized">
<HintPath>..\HelpSense-Github\lib\net48\Assembly-CSharp-Publicized.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow, Version=5.4.1.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>packages\HintServiceMeow.5.4.1\lib\net48\HintServiceMeow.dll</HintPath>
</Reference>
<Reference Include="LabApi, Version=1.1.4.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>packages\Northwood.LabAPI.1.1.4\lib\net48\LabApi.dll</HintPath>
</Reference>
<Reference Include="Mirror, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Pooling.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll</HintPath>
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.IO.Pipelines, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\System.IO.Pipelines.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="UnityEngine">
<HintPath>D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.Physics2DModule">
<HintPath>D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.Physics2DModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>D:\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
</Reference>
<Reference Include="YamlDotNet, Version=11.0.0.0, Culture=neutral, PublicKeyToken=ec19458f3c15af5e, processorArchitecture=MSIL">
<HintPath>packages\YamlDotNet.11.0.1\lib\net45\YamlDotNet.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Commands\Chat\AcCommand.cs" />
<Compile Include="Commands\Chat\BcCommand.cs" />
<Compile Include="Commands\Chat\CCommand.cs" />
<Compile Include="Commands\RescueCommand.cs" />
<Compile Include="Configs\Config.cs" />
<Compile Include="Configs\TranslateConfig.cs" />
<Compile Include="Events\CustomEventHandler.cs" />
<Compile Include="Plugins.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Utils\ChatUtils.cs" />
<Compile Include="Utils\Pool\IPool.cs" />
<Compile Include="Utils\Pool\StringBuilderPool.cs" />
<Compile Include="Utils\Util.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

28
EasyTools.sln Normal file
View File

@@ -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

View File

@@ -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<RoleTypeId, float> 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);
}
}
}
}
}

58
Plugins.cs Normal file
View File

@@ -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>("config.yml");
CustomEventHandler.TranslateConfig = this.LoadConfig<TranslateConfig>("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;
}
}
}

View File

@@ -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")]

150
Utils/ChatUtils.cs Normal file
View File

@@ -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
{
/// <summary>
/// Chat privately with admins
/// </summary>
AdminPrivateChat,
/// <summary>
/// Chat with all players
/// </summary>
BroadcastChat,
/// <summary>
/// Chat with all teammates
/// </summary>
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<ChatMessage> MessageList = new();
private static readonly Dictionary<Player, HintServiceMeow.Core.Models.Hints.Hint> 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<float> 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);
}
}
}

9
Utils/Pool/IPool.cs Normal file
View File

@@ -0,0 +1,9 @@
namespace EasyTools.Utils.Pool
{
public interface IPool<T>
{
public T Get();
public void Return(T obj);
}
}

View File

@@ -0,0 +1,29 @@
using System.Text;
using BasePools = NorthwoodLib.Pools;
namespace EasyTools.Utils.Pool
{
public class StringBuilderPool : IPool<StringBuilder>
{
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;
}
}
}

31
Utils/Util.cs Normal file
View File

@@ -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<float> 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);
}
}
}
}

8
packages.config Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="HintServiceMeow" version="5.4.1" targetFramework="net48" />
<package id="Lib.Harmony" version="2.3.6" targetFramework="net48" />
<package id="Northwood.LabAPI" version="1.1.4" targetFramework="net48" />
<package id="System.IO.Compression" version="4.3.0" targetFramework="net48" />
<package id="YamlDotNet" version="11.0.1" targetFramework="net48" />
</packages>