v1.0.5

Серверные плагины termi#

Плагин — обычная .NET-сборка (.dll) в папке plugins/ рядом с termi-server.exe. Сервер находит её при старте, создаёт класс-наследник PluginBase и вызывает OnLoad → OnEnable. Всё, что плагин зарегистрировал, снимается само при выключении.

Коротко. Напиши класс-наследник PluginBase, повесь на него [PluginInfo], собери .dll, положи в plugins/ — и перезапусти сервер. Дальше — события, команды и задачи.

Быстрый старт за 5 минут#

  1. mkdir MyPlugin && cd MyPlugin && dotnet new classlib -f net48 (или Class Library в Visual Studio).
  2. Добавь ссылку на termi-server.exe (в .csproj: <Reference Include="termi-server"><HintPath>путь\termi-server.exe</HintPath><Private>false</Private></Reference>).
  3. Вставь код из раздела «Минимальный плагин», собери (dotnet build -c Release).
  4. Скопируй MyPlugin.dll в plugins/ рядом с termi-server.exe и перезапусти сервер (или /plugins enable MyPlugin).
  5. В консоли сервера появится строка [Hello] включён, а в клиенте — команда /ping.

Минимальный плагин#

csharp
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); } }

Сборка и установка#

  1. Создайте проект Class Library (net48), добавьте ссылку на termi-server.exe (Copy Local = false). Шаблон: examples/server-plugins/ExamplePlugins.csproj.
  2. Положите получившуюся .dll в plugins/, перезапустите сервер. Без Visual Studio: examples/server-plugins/build.sh.
  3. Данные плагина — 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 МоёСобытие()).

Команды#

csharp
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 сервера потокобезопасно, из задач его можно вызывать свободно.

Полный пример: объявления по таймеру#

csharp
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 (планировщик, команда с правом).