LuxelWorldMap

Просмотры: 54
  • ŔØ₲ŲĒᏃ᙭Ċ 🌌™ 🅳🆅🆁🅺 Разработчик

Серверный публичный API

Используйте LXWM_GetServerOpenAPI(), если метку должен создать сервер, а увидеть её должны все игроки или выбранные получатели. Например, постоянную точку торговца удобно создавать один раз с сохранением на диск, а место временного события достаточно хранить до рестарта. Для точки, которую игрок отмечает только у себя, используйте клиентский API.

Геттер доступен в 4_World и 5_Mission серверной части. Создавать метки можно уже в MissionServer.OnInit() после super.OnInit(), а можно позже, например при появлении торговца. Если обработчик карты ещё не создан, геттер сам подготовит его перед первым вызовом. Ждать отдельного события или ставить таймер не нужно. Если карта необязательна для Вашего мода, оберните код вместе с объявлениями типов в препроцессор #ifdef LUXELWORLDMAP_SERVER.

Пример: торговец, который не дублируется при рестарте

Поместите этот метод в серверный класс Вашего мода. Вызывайте его при создании торговца и передавайте реальное положение торговца, например TraderObject.GetPosition(). В примере метка видна на карте, но не появляется в мире. Если администратор уже переоформил торговца в редакторе, повторный вызов не затрёт его изменения.

bool EnsureTraderMarker(vector TraderPosition)
{
	LXWM_ServerOpenAPI MapAPI = LXWM_GetServerOpenAPI();
	string TraderMarkerID = "NORTHMARKET_NWAF_TRADER";
	LXWM_ProtectedServerMarker TraderMarker = MapAPI.GetServerMarker(TraderMarkerID);
	if (TraderMarker)
	{
		return true;
	}
	if (MapAPI.GetLastError() != "LXWM_MARKER_NOT_FOUND")
	{
		Print(MapAPI.GetLastError());
		return false;
	}
	
	TraderMarker = MapAPI.CreateServerMarker("Торговец", TraderPosition, true, "map-pin");
	if (!TraderMarker)
	{
		Print(MapAPI.GetLastError());
		return false;
	}
	
	TraderMarker.SetMarkerID(TraderMarkerID);
	TraderMarker.SetShowToPlayers(true);
	TraderMarker.SetAllowShowMapMarker(true);
	TraderMarker.SetAllowShowMarker3D(false);
	
	if (!TraderMarker.Apply())
	{
		Print(TraderMarker.GetLastError());
		return false;
	}
	
	return true;
}

Создание и поиск меток

В этой таблице вызовы доступны через LXWM_GetServerOpenAPI(). В примере выше третий аргумент true означает запись на диск. Без него создаётся метка текущей сессии. Четвёртый аргумент задаёт ID иконки, а не собственный ID метки. Для своей картинки передайте в этот аргумент обычный ID Lucide, а у полученного черновика перед Apply() вызовите SetMarkerIconPath("NorthMarket/Assets/Icons/Trader.paa"). Файл должен быть доступен игрокам, а не только серверу. Пример работы с объектом метки.

Сигнатура Использование
static LXWM_ProtectedServerMarker CreateServerMarker(string MarkerName, vector WorldPosition, bool Persistent = false, string MarkerIconID = "map-pin") Возвращает новый неопубликованный черновик с готовым ID или NULL. MarkerName: название; WorldPosition: X Y Z в метрах; Persistent: хранить ли на диске; MarkerIconID: ID иконки. По умолчанию разрешён показ игрокам и на карте, и в мире, но метка появится только после Apply().
static LXWM_ProtectedServerMarker GetServerMarker(string MarkerID)
static string GetLastError()
Получить существующую метку по публичному ID. При NULL сразу прочитайте ошибку у API: LXWM_MARKER_NOT_FOUND означает отсутствие записи, LXWM_NOT_READY означает, что искать ещё рано.
static array<string> GetMarkerIDs(string WorldFilter = string.Empty, int StorageFilter = STORAGE_ALL) Возвращает список ID. Пустой WorldFilter означает любой мир. Используйте LXWM_ServerOpenAPI.STORAGE_ALL, STORAGE_PERSISTENT или STORAGE_SESSION (все три константы объявлены в этом классе). Если API не готов, список пуст.
static string GetWorldName()
static string GetServerID()
Текущее имя мира в нижнем регистре (например, chernarusplus) и постоянный ID экземпляра сервера соответственно. До готовности возвращают пустую строку.
static bool IsAdministrator(PlayerIdentity MarkerPlayerIdentity) Проверяет серверные права конкретного PlayerIdentity, а не состояние кнопки «Режим админа» в меню игрока. При обработке запроса клиента проверяйте отправителя на сервере.
static ScriptInvoker GetReadyEvent()
static ScriptInvoker GetShutdownEvent()
Два события без аргументов: карта готова и карта завершает работу. Для подключения используйте Insert, для снятия своего обработчика Remove. Не вызывайте Invoke или Clear: эти события используют и другие моды.

Не создавайте метки из глобальных инициализаторов до появления игрового мира или после завершения миссии: в этих случаях запрос вернёт NULL с ошибкой LXWM_NOT_READY. Он не ставится в очередь на следующую миссию.

ID, видимость и список получателей

Если назначаете ID вручную, используйте свой префикс, например NORTHMARKET_NWAF_TRADER вместо TRADER. Другие моды и администраторы работают с теми же ID, а регистр букв не различается. Для постоянной точки оставляйте один и тот же ID после рестарта. Если у Вас несколько событий одновременно, добавьте ID конкретного события, например NORTHMARKET_DELIVERY_42. Не удаляйте найденную метку только потому, что её ID занят: она может быть чужой.

Если искать метку по заранее известному имени не требуется, не вызывайте SetMarkerID(). После успешного Apply() сохраните строку из GetMarkerID() у себя. Так сделано в примере с заданием ниже.

Эти методы вызываются уже у объекта LXWM_ProtectedServerMarker. Методы Set в этой таблице только подготавливают значения: после настройки вызовите Apply(). Методы чтения учитывают Ваши ещё не применённые изменения. Общие методы названия, позиции, цветов, зон и анимаций описаны в работе с объектом метки.

Сигнатура Использование
bool SetMarkerID(string NewValue) Собственный публичный ID, например SetMarkerID("NORTHMARKET_NWAF_TRADER"). От 1 до 64 символов: латинские буквы, цифры, дефис, нижнее подчёркивание. Не используйте зарезервированные имена Windows (CON, NUL и т. п.). Занятый ID не перезаписывается. Можно переименовать уже опубликованную метку: запрос по новому ID сработает после Apply(). Обновите сохранённые строки ID в своём моде.
bool SetWorldName(string NewValue)
string GetWorldName()
Имя игрового мира, не название сервера и не название метки. Новая метка уже привязана к текущему миру. Для явной привязки: TraderMarker.SetWorldName(LXWM_GetServerOpenAPI().GetWorldName()). При сохранении пустое имя заменяется текущим миром.
bool SetShowToPlayers(bool NewValue)
bool GetShowToPlayers()
false скрывает метку от обычных игроков, оставляя её администратору. Это опубликованная административная метка, а не неопубликованный объект API.
bool SetAllowShowMapMarker(bool NewValue)
bool GetAllowShowMapMarker()
Разрешить отображение в меню карты. Не заставляет игрока показывать лично скрытую категорию.
bool SetAllowShowMarker3D(bool NewValue)
bool GetAllowShowMarker3D()
Разрешить отображение 3D-метки в мире независимо от показа в меню карты.
bool SetShowOnlyWithinRadius(bool NewValue)
bool GetShowOnlyWithinRadius()
Включает ограничение показа по горизонтальному расстоянию до метки. Это не ограничение доступа к координатам через API. Если нужно не передавать данные постороннему игроку, используйте список SteamID64.
bool SetVisibilityRadiusMeters(float NewValue)
float GetVisibilityRadiusMeters()
Радиус видимости: больше 0 и не больше 10000000 м, по умолчанию 1000 м. Например, SetVisibilityRadiusMeters(250) вместе с SetShowOnlyWithinRadius(true). Эта пара не рисует зону.
bool SetShowOnlyToSelectedPlayers(bool NewValue)
bool GetShowOnlyToSelectedPlayers()
Включить фильтр по SteamID64. При включённом фильтре и пустом списке обычных получателей нет. Для общедоступной точки отключите фильтр и оставьте ShowToPlayers включённым.
bool SetAllowedPlayersSteamIDs(array<string> NewValue)
array<string> GetAllowedPlayersSteamIDs()
Один SteamID64 строкой на элемент массива, не список через запятую. Передайте пустой массив для пустого списка, не NULL. API копирует массив при чтении и записи. Администраторы сохраняют административный доступ.
bool SetDeleteAfterRestart(bool NewValue)
bool GetDeleteAfterRestart()
У сохранённой на диск метки включает удаление при следующем старте. Для обычного временного события проще изначально создать метку с Persistent = false.

Пример: точка задания только для одного игрока

Вызывайте при выдаче задания. Передайте действительный PlayerIdentity получателя и игровую позицию. Настройте список до первого Apply(), чтобы координаты не были опубликованы всем даже на короткое время. Возвращённый ID сохраните у задания, чтобы снять метку после его завершения.

string CreateQuestDestination(PlayerIdentity RecipientIdentity, vector DestinationPosition)
{
	if (!RecipientIdentity)
	{
		return string.Empty;
	}
	
	LXWM_ProtectedServerMarker QuestMarker = LXWM_GetServerOpenAPI().CreateServerMarker("Место доставки", DestinationPosition);
	if (!QuestMarker)
	{
		Print(LXWM_GetServerOpenAPI().GetLastError());
		return string.Empty;
	}
	
	array<string> RecipientIDs = new array<string>;
	RecipientIDs.Insert(RecipientIdentity.GetPlainId());
	QuestMarker.SetAllowedPlayersSteamIDs(RecipientIDs);
	QuestMarker.SetShowOnlyToSelectedPlayers(true);
	
	if (!QuestMarker.Apply())
	{
		Print(QuestMarker.GetLastError());
		return string.Empty;
	}
	
	return QuestMarker.GetMarkerID();
}

Движущиеся метки, копии и запись на диск

Сигнатура Использование
bool SetPositionDynamic(vector WorldPosition) Сразу меняет положение опубликованной метки на сервере. Клиентам новое положение передаётся при очередной рассылке. Применять Apply() после этого вызова не нужно. Для конвоя используйте метку текущей сессии, если её не нужно восстанавливать после рестарта.
LXWM_ProtectedServerMarker Copy(vector WorldPosition, bool Persistent = false) Создаёт копию по заданным координатам с новым ID, включая подготовленное оформление источника. Возвращает черновик или NULL; для публикации вызовите Apply() у копии. По умолчанию копия временная, даже если оригинал постоянный.
bool SetPersistent(bool Persistent)
bool GetPersistent()
SetPersistent(true) выбирает хранение на диске; false оставляет метку в памяти. У черновика это выбор для первого сохранения, а у опубликованной записи переключение применяется сразу.
bool Save() Принудительно записывает общую очередь серверных меток. Не публикует черновик и не превращает временную метку в постоянную.

SetPositionDynamic() специально сделан для частых вызовов. Например, после каждого обновления положения ведущей машины передавайте её GetPosition() в уже созданную метку конвоя. Не нужно создавать метку заново, вызывать Apply() или ставить таймер на 0.1 секунды в своём моде. Сервер собирает изменившиеся метки и рассылает их позиции не чаще одного раза в 0.1 секунды (100 мс, до 10 обновлений в секунду). Если одна метка за это время передвинулась пять раз, в пакет попадёт только последняя позиция. Если позиция не изменилась, новой рассылки для этой метки не будет. Это интервал обработки на сервере, а не гарантия времени доставки игроку. Для разового изменения позиции вместе с названием или цветом используйте SetWorldPosition() и один Apply().

SetPositionDynamic, Save и смена способа хранения опубликованной метки откажут, если есть незавершённые правки (LXWM_PENDING_CHANGES). Сначала вызовите Apply(), чтобы принять их, или Refresh(), чтобы отказаться. Например, такой метод можно вызывать при изменении положения ведущей машины; саму точку конвоя создайте один раз:

bool UpdateConvoyPosition(LXWM_ProtectedServerMarker ConvoyMarker, Object LeadVehicle)
{
	if (!ConvoyMarker || !LeadVehicle)
	{
		return false;
	}
	
	if (!ConvoyMarker.SetPositionDynamic(LeadVehicle.GetPosition()))
	{
		Print(ConvoyMarker.GetLastError());
		return false;
	}
	
	return true;
}


Сигнатура Использование
static bool HasPendingMarkerWrites()
static bool FlushMarkerWrites(out string ErrorKey)
Apply() публикует изменения существующей метки, но запись на диск может быть отложена. Для контрольной точки передайте в FlushMarkerWrites объявленную переменную string WriteError, без слова out в вызове. При false прочитайте её. Это общая очередь, не вызывайте запись на каждом обновлении координат.
static bool RefreshMarkers() Повторно сверяет серверные метки с файлами и запускает синхронизацию. Подходит после внешнего редактирования файлов, после обычного Apply() не нужен.
static bool SynchronizeMarkers()
static bool SynchronizePlayer(PlayerIdentity RecipientIdentity)
Повторная отправка всем или одному PlayerIdentity. Вызов для одного получателя не отменяет фильтры. Возврат true означает, что сервер принял действие, а не что клиент уже отрисовал метку.

Если UpdateConvoyPosition вернул false, остановите отслеживание и разберите причину. Не повторяйте тот же отказ на каждом кадре. При завершении события вызовите ConvoyMarker.Delete(), а не только очистите ссылку: освобождение объекта не удаляет метку.

Пример: временная точка по образцу постоянной

У торговца открылся второй пункт выдачи до следующего рестарта. Передайте публичный ID основного магазина и позицию нового. Копия получит собственный ID и тот же внешний вид. Оригинал не меняется. Для скрытой метки администратора явно укажите, каким игрокам показывать копию: правила видимости тоже копируются.

string OpenSeasonalDepot(string TemplateID, vector DepotPosition)
{
	LXWM_ServerOpenAPI MapAPI = LXWM_GetServerOpenAPI();
	LXWM_ProtectedServerMarker MainDepot = MapAPI.GetServerMarker(TemplateID);
	if (!MainDepot || !MainDepot.GetPersistent())
	{
		return string.Empty;
	}
	
	LXWM_ProtectedServerMarker DepotMarker = MainDepot.Copy(DepotPosition, false);
	if (!DepotMarker)
	{
		Print(MainDepot.GetLastError());
		return string.Empty;
	}
	DepotMarker.SetMarkerName("#NORTHMARKET_SEASONAL_DEPOT");
	DepotMarker.SetShowToPlayers(true);
	DepotMarker.SetShowOnlyToSelectedPlayers(false);
	DepotMarker.SetVisibilityRadiusMeters(750);
	DepotMarker.SetShowOnlyWithinRadius(true);
	if (!DepotMarker.Apply())
	{
		Print(DepotMarker.GetLastError());
		return string.Empty;
	}
	return DepotMarker.GetMarkerID();
}

Если пункт решено оставить после рестарта, найдите его по сохранённому ID, вызовите DepotMarker.SetDeleteAfterRestart(false), затем DepotMarker.Apply() и, только если применение прошло успешно, DepotMarker.SetPersistent(true). Это меняет способ хранения уже опубликованной метки. Обратный вызов DepotMarker.SetPersistent(false) оставит её в текущей сессии без файла.

Чтобы проверить настройки до копирования, у оригинала можно прочитать MainDepot.GetShowToPlayers(), MainDepot.GetAllowShowMapMarker() и MainDepot.GetAllowShowMarker3D(). Радиус из MainDepot.GetVisibilityRadiusMeters() ограничивает показ только при MainDepot.GetShowOnlyWithinRadius() == true. У постоянного оригинала отдельно проверьте MainDepot.GetDeleteAfterRestart(): наличие файла не гарантирует, что метка останется после рестарта.

Пример: добавить второго получателя задания

Здесь метка уже опубликована как персональная точка задания с включённым фильтром получателей. Для общедоступной точки метод вернёт false, чтобы случайно не скрыть её от остальных игроков. Чтение списка возвращает копию: после добавления SteamID передайте список обратно и примените правку.

bool ShareQuestDestination(LXWM_ProtectedServerMarker QuestMarker, PlayerIdentity Recipient)
{
	if (!QuestMarker || !QuestMarker.IsValid() || QuestMarker.IsDraft() || !Recipient || !QuestMarker.GetShowOnlyToSelectedPlayers())
	{
		return false;
	}
	array<string> Audience = QuestMarker.GetAllowedPlayersSteamIDs();
	string RecipientID = Recipient.GetPlainId();
	if (Audience.Find(RecipientID) != -1)
	{
		return true;
	}
	Audience.Insert(RecipientID);
	return QuestMarker.SetAllowedPlayersSteamIDs(Audience) && QuestMarker.Apply();
}

Пример: диагностика по команде администратора

Этот метод предназначен для серверного обработчика команды. Передавайте удостоверение отправителя, которое получил сервер, а не SteamID из тела клиентского запроса. В логе будут ID этого сервера, мир и количество сохранённых меток данного мира. Затем сервер повторно отправит этому администратору доступные ему данные. Другим игрокам обновление не отправляется.

bool ResyncAdminMap(PlayerIdentity CommandSender)
{
	LXWM_ServerOpenAPI MapAPI = LXWM_GetServerOpenAPI();
	if (!CommandSender || !MapAPI.IsAdministrator(CommandSender))
	{
		return false;
	}
	string WorldName = MapAPI.GetWorldName();
	array<string> SavedIDs = MapAPI.GetMarkerIDs(WorldName, LXWM_ServerOpenAPI.STORAGE_PERSISTENT);
	Print(string.Format("Server: %1 | World: %2 | Persistent markers: %3", MapAPI.GetServerID(), WorldName, SavedIDs.Count()));
	return MapAPI.SynchronizePlayer(CommandSender);
}

Если Ваш мод наблюдает за жизненным циклом карты, события подключаются через MapAPI.GetReadyEvent().Insert(OnMapStarted) и MapAPI.GetShutdownEvent().Insert(OnMapStopped). Оба обработчика имеют сигнатуру void без аргументов. При остановке своего обработчика снимите именно их через MapAPI.GetReadyEvent().Remove(OnMapStarted) и MapAPI.GetShutdownEvent().Remove(OnMapStopped). Событие старта не повторяется для поздней подписки. Для создания метки такая подписка не нужна.

Для принудительной отправки всем используйте MapAPI.SynchronizeMarkers(). Если администратор восстановил файлы из резервной копии и нужно заново прочитать файлы, используйте MapAPI.RefreshMarkers(). Это разные задачи: после обычного сохранения из редактора эти вызовы не нужны.

Проверить запись перед созданием резервной копии можно следующим образом. При отказе метод оставляет в логе исходный код ошибки и возвращает false: резервную копию не следует считать актуальной. Вариант через DepotMarker.Save() у опубликованной метки также записывает общую очередь, а не только файл этого магазина.

bool FlushMarkersBeforeBackup()
{
	LXWM_ServerOpenAPI MapAPI = LXWM_GetServerOpenAPI();
	if (!MapAPI.HasPendingMarkerWrites())
	{
		return true;
	}
	string WriteError;
	if (!MapAPI.FlushMarkerWrites(WriteError))
	{
		Print(WriteError);
		return false;
	}
	return true;
}

Полный список ошибок API меток разделяет служебные коды и ключи перевода. Например, #LXWM_ServerMarkerIDUsed означает конфликт ID, #LXWM_ServerMarkerInvalidAudience указывает на неверный SteamID в списке, а #LXWM_ServerMarkerWriteFailed требует проверить доступ процесса к папке профиля. Эти случаи не стоит обрабатывать одинаково: повтор запроса с тем же занятым ID его не освободит.