Обновление 0.3.0 — большое обновление C#, этап второй.
Добавлены короткий серверный API, поддержка асинхронных обработчиков, перехват урона, огня и взрывов. Расширено событие отключения игрока и управление отображением интерфейсов.
Добавлен класс Api — аналог Alt в alt:V по назначению.
Это короткая точка доступа к серверному C# SDK: создание и поиск сущностей, события, метаданные, ресурсы, RPC и другие операции.
В классе
215 публичных методов с учётом перегрузок, 49 событий и 11 свойств. Группированный API через
Server также остаётся доступен.
C#:
using System.Threading;
using System.Threading.Tasks;
using Mint.Server;
using Mint.Server.Events;
using Mint.Server.Players;
using Mint.Server.Shared;
// Создание транспорта.
var vehicle = Api.CreateVehicle("sultan", new Vector3(100, 200, 30));
// Поиск игрока и отправка клиентского события.
var player = Api.GetPlayerById(1);
if (player is not null)
Api.EmitClient(player, "notification", "Привет!");
// Событие всем игрокам.
Api.EmitAllClients("announcement", "Сервер обновлён!");
// Типизированные метаданные.
Api.SetMeta("weather", "CLEAR");
var weather = Api.GetMeta<string>("weather");
// Экспорт функции для других ресурсов.
Api.Export("sum", (int a, int b) => a + b);
Примеры подписок ниже размещаются в
OnStart() ресурса.
Добавлена поддержка асинхронного выполнения в C#.
Она встроена в основной SDK: отдельная библиотека и классы вроде
AsyncPlayer не требуются.
Обработчики могут возвращать
Task или
ValueTask, ожидать запрос к БД, HTTP или другую асинхронную операцию. После обычного
await выполнение продолжается в контексте ресурса, где снова доступна работа с SDK.
C#:
Api.OnClientAsync("request", async (IPlayer player, string message) =>
{
await Task.Delay(1000); // Например, ожидание запроса к БД.
if (!player.Valid) return;
Api.EmitClient(player, "response", $"Получено: {message}");
});
Для отмены ожидания при остановке ресурса можно добавить
CancellationToken. Runtime передаёт его автоматически — отправлять токен с клиента не нужно.
C#:
Api.OnClientAsync("request", async (
IPlayer player, string message, CancellationToken token) =>
{
await Task.Delay(1000, token);
if (!player.Valid) return;
Api.EmitClient(player, "response", message);
});
Для запуска отдельной асинхронной операции доступен dispatcher:
C#:
_ = Api.Dispatcher.RunAsync(async token =>
{
await Task.Delay(5000, token);
Api.EmitAllClients("announcement", "Прошло пять секунд!");
});
Поддерживаются асинхронные серверные и клиентские события, одноразовые подписки, обработчики через атрибуты и RPC. Для обращения к SDK из стороннего потока используется
Api.Dispatcher.InvokeAsync(...).
Ограничения:
async void не поддерживается.
OnStart, OnTick, OnStop и хуки IModule остаются синхронными.
RunAsync не переносит вычисления в отдельный поток. Для тяжёлых вычислений используется Task.Run, внутри которого нельзя напрямую обращаться к живым сущностям.
- После
await сущность могла быть удалена или игрок мог отключиться — проверяйте Valid.
- События, требующие немедленного решения, обрабатываются синхронно.
- Асинхронные результаты межресурсных exports пока не поддерживаются.
Добавлены серверные события fire, explosion и weaponDamage.
Они доступны в C# и JavaScript.
fire позволяет разрешить или запретить создание пожара. Обработчик получает отправителя запроса, позицию и сущность, к которой привязан огонь. Для огня на земле сущность равна
null.
explosion позволяет разрешить или запретить взрыв. Передаются отправитель запроса, тип взрыва, позиция и множитель урона.
У обоих событий
false запрещает эффект. Отправитель запроса не обязательно является виновником; для серверного источника он может быть
null. Изменение урона через эти два события пока не поддерживается. Отмена
fire не тушит уже существующий пожар.
Примеры на C#:
C#:
// Разрешить огонь на сущностях, запретить отдельные пожары на земле.
Api.OnFire += (reporter, position, entity) =>
{
Api.Log($"Пожар: {position}, сущность: {entity?.Id}");
return entity is not null;
};
// Запретить взрывы ракет.
Api.OnExplosion += (reporter, type, position, damageScale) =>
{
Api.Log($"Взрыв: {type}, множитель урона: {damageScale}");
return type != ExplosionType.Rocket;
};
Примеры на серверном JS:
JavaScript:
import * as mint from 'mint-server';
mint.on('fire', (reporter, position, entity) => {
return entity != null;
});
mint.on('explosion', (reporter, type, position, damageScale) => {
return type !== 4; // 4 — взрыв ракеты.
});
Также можно поджечь колесо автомобиля или запустить таймер его взрыва. Создаваемые эффекты проходят соответствующие серверные проверки.
C#:
// vehicle и player — действующие сущности.
vehicle.Wheels.SetWheelOnFire((byte)0, true);
vehicle.Weapons.SetTimedExplosion(true, player, 3000);
JavaScript:
vehicle.setWheelOnFire(0, true);
vehicle.setTimedExplosion(true, player, 3000);
weaponDamage позволяет отменить попадание или изменить величину урона.
Добавлена обработка обычных ударов ближнего боя, включая кулаки и холодное оружие.
Обработчик получает атакующего, цель, хеш оружия, урон, смещение точки попадания относительно цели и идентификатор кости GTA.
Пример на C#: запрет ударов кулаками и уменьшение остального поддерживаемого урона вдвое.
C#:
Api.OnWeaponDamage += (source, target, weapon, damage, offset, bone) =>
{
if (weapon == Api.Hash("WEAPON_UNARMED"))
return WeaponDamageResult.Cancel;
return WeaponDamageResult.WithDamage(damage * 0.5f);
};
Для сохранения исходного урона возвращается
WeaponDamageResult.Allow.
Аналогичный пример на серверном JS:
JavaScript:
mint.on('weaponDamage', (source, target, weapon, damage, offset, bone) => {
if (weapon === mint.hash('WEAPON_UNARMED'))
return false;
return damage * 0.5;
});
В JS
false отменяет попадание, число заменяет урон,
true или отсутствие возвращаемого значения сохраняет его.
Перехват охватывает поддерживаемые попадания из ручного огнестрельного оружия и обычные melee-удары по игрокам и динамическим серверным педам с назначенным владельцем. Транспорт и объекты как цели, урон взрывов и парные добивания пока не входят в это покрытие.
Величина
damage передаётся в расчёт GTA: итоговая потеря HP также зависит от брони и других игровых модификаторов.
Все три события требуют синхронного решения —
async и
await в их обработчиках не поддерживаются.
В событие playerDisconnect добавлен снимок последних известных серверу данных игрока.
Теперь параметры события:
player — уже недействительная сущность отключившегося игрока.
reason — причина отключения.
snapshot — неизменяемый снимок данных, сохранённый до очистки сессии.
Снимок можно читать после
await, например при сохранении персонажа.
| C# | JS | Данные |
|---|---|---|
|
Id |
id | ID игрока |
|
Name |
name | Имя |
|
Position |
position | Позиция |
|
Rotation |
rotation | Поворот в радианах |
|
Velocity |
velocity | Скорость |
|
Dimension |
dimension | Измерение |
|
Model |
model | Хеш модели |
|
Health,
MaxHealth |
health,
maxHealth | Здоровье и его максимум |
|
Armor,
MaxArmor |
armour,
maxArmour | Броня и её максимум |
|
CurrentWeapon |
currentWeapon | Хеш текущего оружия |
|
VehicleId |
vehicleId | ID транспорта либо
null |
|
Seat |
seat | Место в транспорте либо
null; водитель —
1 |
|
Interior |
interior | Интерьер |
|
IsDead |
isDead | Состояние смерти |
Пример на C#:
C#:
Api.OnPlayerDisconnect += (player, reason, snapshot) =>
{
Api.Log($"{snapshot.Name} отключился: {reason}");
Api.Log($"Последняя позиция: {snapshot.Position}");
};
Пример на серверном JS:
JavaScript:
mint.on('playerDisconnect', (player, reason, snapshot) => {
mint.log(`${snapshot.name} отключился: ${reason}`);
mint.log(`Последнее здоровье: ${snapshot.health}`);
});
Существующие C#-обработчики необходимо обновить с учётом третьего параметра.
Для CEF WebView, RmlDocument и TextLabel добавлено свойство showOnPauseMenu.
Оно определяет, продолжит ли объект отображаться при открытой игровой карте или меню паузы. По умолчанию —
false.
JavaScript:
webView.showOnPauseMenu = true;
rmlDocument.showOnPauseMenu = true;
textLabel.showOnPauseMenu = true;
Для серверного
TextLabel в C#:
C#:
textLabel.ShowOnPauseMenu = true;
Исправления:
- Исправлен краш клиента при выполнении
clear в консоли с большим количеством сообщений и появившейся полосой прокрутки.
- Исправлена ошибка времени жизни C#-сущностей, из-за которой действующий
TextLabel мог ошибочно считаться удалённым.
- Исправлено нанесение урона и добивание обычными melee-ударами: цель больше не остаётся с 1 HP при достаточном разрешённом уроне.
- Увеличен лимит всплеска пакетов который генерируется при блокировке мейнпотока сложными задачами и защиты от флуда
- Увеличен размер максимального размера одного dlc.rpf до 5 гиг
MiNT Multiplayer v0.3.0-dev Git