Серверные плагины termi#
Плагин — обычная .NET-сборка (.dll) в папке plugins/ рядом с termi-server.exe. Сервер находит её при старте, создаёт класс-наследник PluginBase и вызывает OnLoad → OnEnable. Всё, что плагин зарегистрировал, снимается само при выключении.
Коротко. Напиши класс-наследникPluginBase, повесь на него[PluginInfo], собери.dll, положи вplugins/— и перезапусти сервер. Дальше — события, команды и задачи.
Быстрый старт за 5 минут#
mkdir MyPlugin && cd MyPlugin && dotnet new classlib -f net48(или Class Library в Visual Studio).- Добавь ссылку на
termi-server.exe(в.csproj:<Reference Include="termi-server"><HintPath>путь\termi-server.exe</HintPath><Private>false</Private></Reference>). - Вставь код из раздела «Минимальный плагин», собери (
dotnet build -c Release). - Скопируй
MyPlugin.dllвplugins/рядом сtermi-server.exeи перезапусти сервер (или/plugins enable MyPlugin). - В консоли сервера появится строка
[Hello] включён, а в клиенте — команда/ping.
Минимальный плагин#
using Termi.Server.Api.Events;
using Termi.Server.Api.Plugins;
[PluginInfo("Hello", "1.0.0", Author = "я", Description = "Приветствие")]
public class Hello : PluginBase, IListener
{
protected override void OnEnable()
{
Config.SetDefault("text", "Привет, {name}!");
Config.Save();
RegisterEvents(this);
RegisterCommand("ping", "проверка", (sender, args) => { sender.SendMessage("pong"); return true; });
}
[EventHandler]
public void OnJoin(PlayerJoinEvent e) { e.Motd = Config.GetString("text").Replace("{name}", e.Player.Name); }
}
Сборка и установка#
- Создайте проект Class Library (net48), добавьте ссылку на
termi-server.exe(Copy Local = false). Шаблон:examples/server-plugins/ExamplePlugins.csproj. - Положите получившуюся
.dllвplugins/, перезапустите сервер. Без Visual Studio:examples/server-plugins/build.sh. - Данные плагина —
plugins/<Имя>/(создаётся сама).
Жизненный цикл#
OnLoad (конфиг, подготовка) → OnEnable (регистрация событий/команд/задач) → … → OnDisable. Ошибка в OnLoad/OnEnable выключает только этот плагин. Включить/выключить на лету: /plugins enable|disable Имя. Зависимости: Depend = new[]{"Other"} — без него не загрузится и загрузится после него; SoftDepend — только порядок.
События#
Метод слушателя: [EventHandler] public void Имя(ТипСобытия e); класс реализует IListener. Приоритеты: Lowest → Low → Normal → High → Highest → Monitor (Monitor только наблюдает, отмену менять не может).
| событие | когда | можно |
|---|---|---|
PlayerLoginEvent | токен проверен, до допуска | отменить + KickMessage |
PlayerJoinEvent | вошёл на сервер | изменить Motd |
PlayerQuitEvent | вышел | — |
PlayerChatEvent | сообщение в канале | отменить, изменить Message |
PlayerCommandEvent | команда до выполнения | отменить, изменить Line |
PlayerStatusChangeEvent | смена статуса | отменить |
PlayerReactEvent | реакция на сообщение | отменить |
VoiceJoinEvent / VoiceLeaveEvent | голосовой канал | отменить вход |
PlayerKickEvent / PlayerBanEvent / PlayerMuteEvent | модерация | отменить, изменить причину/минуты |
ServerStartedEvent / ServerStoppingEvent | запуск/остановка | — |
PluginEnabledEvent / PluginDisabledEvent | жизнь плагинов | — |
Свои события: наследуйте Event (или CancellableEvent) и вызывайте Server.Events.Call(new МоёСобытие()).
Команды#
class Rules : Command {
public Rules() : base("rules", "правила сервера", "/rules") { Permission = 0; } // Permission = Perm.Admin → только админы
public override bool Execute(ICommandSender s, string label, string[] args) { s.SendMessage("1. Не спамить"); return true; }
}
// в OnEnable: RegisterCommand(new Rules());
return false → сервер покажет Usage. Клиент узнаёт о командах плагинов сам (список приходит при входе), набирайте /rules как обычно. ICommandSender: Name, IsConsole, IsOwner, Player, Channel, Rank, HasPermission(Perm.X), SendMessage. Те же команды работают в консоли сервера.
Конфиг плагина#
Config — это plugins/<Имя>/config.json. Значения по умолчанию задаются через SetDefault (уже существующие ключи не трогаются), затем Save().
| метод | что делает |
|---|---|
GetString(ключ, по_умолчанию) | строка |
GetInt(ключ, по_умолчанию) | число |
GetBool(ключ, по_умолчанию) | флаг |
GetStringList(ключ) | список строк |
Set(ключ, значение) / Remove(ключ) | изменить в памяти, потом Save() |
Reload() | перечитать файл (например, по команде /myplugin reload) |
Планировщик#
1 тик = 50 мс, TaskScheduler.TicksPerSecond = 20. RunLater(40, …) — через 2 с; RunRepeating(0, 20 * 60, …) — раз в минуту; Server.Scheduler.RunAsync(this, …) — в пуле потоков. Результат — ScheduledTask с Cancel(). Задачи выключенного плагина отменяются сами.
IServer — что можно делать#
OnlinePlayers, GetPlayer(имя), KnownPlayers, Channels, Broadcast(текст), PostMessage(канал, от_кого, текст), Kick/Ban/Unban/Mute, GetPermissions/HasPermission/RolesOf, Owner, SaveAndSync(), Shutdown(); подсистемы Events, Commands, Scheduler, Plugins.
Права#
Биты из Termi.Protocol.Perm: Send, Images, Voice, Speak, Video, ManageMsgs, ManageChans, ManageRoles, Kick, Ban, Mute, Admin. Проверка: player.HasPermission(Perm.Kick). Владелец и Admin имеют все права.
Потоки#
Обработчики событий и команды выполняются под общей блокировкой сервера — не делайте в них долгих операций (сеть, диск); для этого RunAsync. API сервера потокобезопасно, из задач его можно вызывать свободно.
Полный пример: объявления по таймеру#
using System.Collections.Generic;
using Termi.Protocol;
using Termi.Server.Api.Commands;
using Termi.Server.Api.Plugins;
using Termi.Server.Api.Scheduling;
[PluginInfo("Announcer", "1.0.0", Author = "я", Description = "Периодические объявления")]
public class Announcer : PluginBase
{
ScheduledTask task;
int next;
protected override void OnEnable()
{
Config.SetDefault("interval_seconds", 600);
Config.SetDefault("tips", new List<object> { "Совет: /whois ник покажет роли участника." });
Config.Save();
int ticks = Config.GetInt("interval_seconds", 600) * TaskScheduler.TicksPerSecond;
task = RunRepeating(ticks, ticks, () =>
{
var tips = Config.GetStringList("tips");
if (tips.Count > 0) Server.Broadcast(tips[next++ % tips.Count]);
});
// команда только для администраторов
RegisterCommand("announce", "Разослать объявление всем", (sender, args) =>
{
if (args.Length == 0) return false; // false → сервер покажет Usage
Server.Broadcast("📢 " + string.Join(" ", args));
return true;
}, Perm.Admin);
}
}
Частые вопросы#
Плагин не загрузился. Смотри консоль сервера: там написано, какой класс или зависимость не найдены. У класса должен быть [PluginInfo] и он должен наследовать PluginBase.
Клиент не видит мою команду. Список команд приходит при входе на сервер — перезайди. Для команд плагина, которых нет в списке клиента, сервер всё равно ответит текстом.
Как отменить сообщение? В обработчике PlayerChatEvent поставь e.Cancelled = true (и при желании объясни причину через e.Player.SendMessage).
Можно ли ходить в сеть / на диск? Да, но только из RunAsync — обработчики событий держат общую блокировку сервера.
Несколько плагинов спорят из-за события. Порядок задаёт Priority: сначала Lowest, последним Highest; Monitor вызывается в самом конце и видит итог.
Примеры#
examples/server-plugins/: Welcome (приветствие, /hello), ChatFilter (приоритеты, отмена и правка сообщений), Announcer (планировщик, команда с правом).