Check Transmit

Как контролировать видимость объектов и передачу по сети с помощью системы CheckTransmit.

Система CheckTransmit в Source 2 позволяет контролировать, какие объекты передаются по сети конкретным игрокам. Эта мощная функция позволяет создавать продвинутые игровые механики, такие как скрытие/показ объектов, создание правил видимости для конкретных игроков и оптимизация сетевого трафика.

Обзор

Для работы с системой CheckTransmit необходимо:

  1. Зарегистрировать хук с помощью OnServerCheckTransmit_Register при запуске плагина
  2. Получать указатели CheckTransmitInfo для каждого игрока в вашем колбэке
  3. Изменять состояния передачи используя предоставленные методы
  4. Отменить регистрацию хука с помощью OnServerCheckTransmit_Unregister при завершении плагина

Базовая настройка

Зарегистрируйте хук CheckTransmit в методе запуска вашего плагина и отмените регистрацию при завершении:

c#
c++
python
go
js
lua
using Plugify;
using static s2sdk.s2sdk;

public unsafe class Sample : Plugin
{
    public void OnPluginStart()
    {
        OnServerCheckTransmit_Register(OnCheckTransmit);
    }

    public void OnPluginEnd()
    {
        OnServerCheckTransmit_Unregister(OnCheckTransmit);
    }

    private void OnCheckTransmit(nint[] infos)
    {
        // индекс массива infos представляет слот игрока (0-63)
        for (int playerSlot = 0; playerSlot < infos.Length; playerSlot++)
        {
            var info = infos[playerSlot];
            // Изменяйте состояния передачи для этого слота игрока
        }
    }
}

Структура CheckTransmitInfo

Каждый объект CheckTransmitInfo содержит информацию о состояниях передачи для конкретного игрока. Слот игрока определяется индексом массива (0-63), а не полем в структуре.

  • m_pTransmitEntity: BitVec, контролирующий, какие объекты передаются
  • m_pTransmitNonPlayers: BitVec для объектов, не являющихся игроками
  • m_pTransmitAlways: BitVec для объектов, которые всегда должны передаваться
  • m_vecTargetSlots: Список целевых слотов игроков
  • m_bFullUpdate: Должно ли быть отправлено полное обновление

Доступные методы

S2SDK предоставляет два способа работы с CheckTransmitInfo:

1. Функциональный API (прямые вызовы методов)

Используйте эти методы, передавая указатель CheckTransmitInfo в качестве первого параметра:

Методы передачи объектов

SetTransmitInfoEntity(info, entityHandle);          // Отметить объект как передаваемый
ClearTransmitInfoEntity(info, entityHandle);        // Отметить объект как не передаваемый
IsTransmitInfoEntitySet(info, entityHandle);        // Проверить, передается ли объект
SetTransmitInfoEntityAll(info);                     // Отметить все объекты как передаваемые
ClearTransmitInfoEntityAll(info);                   // Отметить все объекты как не передаваемые

Методы передачи не-игроков

SetTransmitInfoNonPlayer(info, entityHandle);       // Отметить не-игрока как передаваемого
ClearTransmitInfoNonPlayer(info, entityHandle);     // Отметить не-игрока как не передаваемого
IsTransmitInfoNonPlayerSet(info, entityHandle);     // Проверить, передается ли не-игрок
SetTransmitInfoNonPlayerAll(info);                  // Отметить всех не-игроков как передаваемых
ClearTransmitInfoNonPlayerAll(info);                // Отметить всех не-игроков как не передаваемых

Методы постоянной передачи

SetTransmitInfoAlways(info, entityHandle);          // Отметить объект для постоянной передачи
ClearTransmitInfoAlways(info, entityHandle);        // Снять отметку объекта с постоянной передачи
IsTransmitInfoAlwaysSet(info, entityHandle);        // Проверить, всегда ли передается объект
SetTransmitInfoAlwaysAll(info);                     // Отметить все объекты для постоянной передачи
ClearTransmitInfoAlwaysAll(info);                   // Снять отметку всех объектов с постоянной передачи

Методы целевых слотов

GetTransmitInfoTargetSlotsCount(info);              // Получить количество целевых слотов
GetTransmitInfoTargetSlot(info, index);             // Получить конкретный целевой слот
AddTransmitInfoTargetSlot(info, playerSlot);        // Добавить целевой слот
RemoveTransmitInfoTargetSlot(info, index);          // Удалить целевой слот по индексу
GetTransmitInfoTargetSlotsAll(info);                // Получить все целевые слоты
RemoveTransmitInfoTargetSlotsAll(info);             // Очистить все целевые слоты

Методы полного обновления

GetTransmitInfoFullUpdate(info);                    // Получить флаг полного обновления
SetTransmitInfoFullUpdate(info, fullUpdate);        // Установить флаг полного обновления

2. Объектно-ориентированный API (класс CheckTransmitInfo)

В языках, которые это поддерживают, вы можете создать класс CheckTransmitInfo из указателя для более интуитивного доступа в стиле ООП:

c#
c++
private void OnCheckTransmit(nint[] infos)
{
    for (int playerSlot = 0; playerSlot < infos.Length; playerSlot++)
    {
        // Создать OOP обёртку из указателя
        var info = new CheckTransmitInfo(infos[playerSlot]);

        // Использовать методы объекта вместо статических функций
        info.SetEntity(entityHandle);
        info.ClearEntity(entityHandle);
        bool isSet = info.IsEntitySet(entityHandle);

        // Примечание: playerSlot берётся из индекса массива, а не из метода
        info.SetFullUpdate(true);
    }
}

Практические примеры

Пример 1: Скрыть конкретный объект от всех игроков

c#
using Plugify;
using static s2sdk.s2sdk;

public unsafe class HideEntity : Plugin
{
    private int hiddenEntityHandle = -1;

    public void OnPluginStart()
    {
        OnServerCheckTransmit_Register(OnCheckTransmit);

        // Добавить консольную команду для скрытия объекта
        AddConsoleCommand("hide_entity", "Hide an entity by index",
            ConVarFlag.LinkedConcommand | ConVarFlag.Release,
            Command_HideEntity, HookMode.Post);
    }

    public void OnPluginEnd()
    {
        OnServerCheckTransmit_Unregister(OnCheckTransmit);
    }

    private ResultType Command_HideEntity(int caller, CommandCallingContext context, string[] arguments)
    {
        if (arguments.Length < 2)
        {
            PrintToServer("Usage: hide_entity <entity_index>\n");
            return ResultType.Handled;
        }

        int entityIndex = int.Parse(arguments[1]);
        hiddenEntityHandle = EntIndexToEntHandle(entityIndex);

        if (IsValidEntHandle(hiddenEntityHandle))
        {
            PrintToServer($"Entity {entityIndex} will be hidden from all players\n");
        }
        else
        {
            PrintToServer($"Invalid entity index: {entityIndex}\n");
        }

        return ResultType.Handled;
    }

    private void OnCheckTransmit(nint[] infos)
    {
        if (hiddenEntityHandle == -1 || !IsValidEntHandle(hiddenEntityHandle))
            return;

        // Скрыть объект от всех игроков
        for (int playerSlot = 0; playerSlot < infos.Length; playerSlot++)
        {
            ClearTransmitInfoEntity(infos[playerSlot], hiddenEntityHandle);
            // Также очистить из списка постоянной передачи
            ClearTransmitInfoAlways(infos[playerSlot], hiddenEntityHandle);
        }
    }
}

Пример 2: Создание видимости для конкретных игроков

Скрывайте объекты от конкретных игроков на основе условий:

c#
using Plugify;
using static s2sdk.s2sdk;
using System.Collections.Generic;

public unsafe class PlayerVisibility : Plugin
{
    // Карта слот игрока -> список скрытых хэндлов объектов
    private Dictionary<int, List<int>> playerHiddenEntities = new();

    public void OnPluginStart()
    {
        OnServerCheckTransmit_Register(OnCheckTransmit);
    }

    public void OnPluginEnd()
    {
        OnServerCheckTransmit_Unregister(OnCheckTransmit);
    }

    // Скрыть конкретный объект от конкретного игрока
    public void HideEntityFromPlayer(int playerSlot, int entityHandle)
    {
        if (!playerHiddenEntities.ContainsKey(playerSlot))
        {
            playerHiddenEntities[playerSlot] = new List<int>();
        }

        if (!playerHiddenEntities[playerSlot].Contains(entityHandle))
        {
            playerHiddenEntities[playerSlot].Add(entityHandle);
        }
    }

    // Показать ранее скрытый объект игроку
    public void ShowEntityToPlayer(int playerSlot, int entityHandle)
    {
        if (playerHiddenEntities.ContainsKey(playerSlot))
        {
            playerHiddenEntities[playerSlot].Remove(entityHandle);
        }
    }

    private void OnCheckTransmit(nint[] infos)
    {
        for (int playerSlot = 0; playerSlot < infos.Length; playerSlot++)
        {
            // Использование ООП стиля
            var info = new CheckTransmitInfo(infos[playerSlot]);

            // Проверить, есть ли у этого игрока скрытые объекты
            if (playerHiddenEntities.ContainsKey(playerSlot))
            {
                foreach (int entityHandle in playerHiddenEntities[playerSlot])
                {
                    // Скрывать только если объект всё ещё валиден
                    if (IsValidEntHandle(entityHandle))
                    {
                        info.ClearEntity(entityHandle);
                        info.ClearAlways(entityHandle);
                    }
                }
            }
        }
    }
}

Пример 3: Скрыть игроков друг от друга

Создать "режим невидимости", где конкретные игроки не видят друг друга:

c#
using Plugify;
using static s2sdk.s2sdk;
using System.Collections.Generic;

public unsafe class InvisibleMode : Plugin
{
    private HashSet<int> invisiblePlayers = new();

    public void OnPluginStart()
    {
        OnServerCheckTransmit_Register(OnCheckTransmit);

        AddConsoleCommand("toggle_invisible", "Toggle invisible mode",
            ConVarFlag.LinkedConcommand | ConVarFlag.Release | ConVarFlag.ClientCanExecute,
            Command_ToggleInvisible, HookMode.Post);
    }

    public void OnPluginEnd()
    {
        OnServerCheckTransmit_Unregister(OnCheckTransmit);
    }

    private ResultType Command_ToggleInvisible(int caller, CommandCallingContext context, string[] arguments)
    {
        if (caller == -1) return ResultType.Handled;

        if (invisiblePlayers.Contains(caller))
        {
            invisiblePlayers.Remove(caller);
            PrintToChat(caller, "Invisible mode: OFF");
        }
        else
        {
            invisiblePlayers.Add(caller);
            PrintToChat(caller, "Invisible mode: ON");
        }

        return ResultType.Handled;
    }

    private void OnCheckTransmit(nint[] infos)
    {
        for (int receiverSlot = 0; receiverSlot < infos.Length; receiverSlot++)
        {
            var info = new CheckTransmitInfo(infos[receiverSlot]);

            // Для каждого невидимого игрока
            foreach (int invisibleSlot in invisiblePlayers)
            {
                // Не скрывать от самих себя
                if (invisibleSlot == receiverSlot)
                    continue;

                // Получить хэндл объекта невидимого игрока
                int invisibleHandle = PlayerSlotToEntHandle(invisibleSlot);

                if (IsValidEntHandle(invisibleHandle))
                {
                    // Скрыть невидимого игрока от этого получателя
                    info.ClearEntity(invisibleHandle);
                    info.ClearAlways(invisibleHandle);
                }
            }
        }
    }
}

Пример 4: Показывать только членов команды

Отображать объекты только игрокам из той же команды:

c#
using Plugify;
using static s2sdk.s2sdk;
using System.Collections.Generic;

public unsafe class TeamVisibility : Plugin
{
    // Карта хэндлов объектов к номерам команд
    private Dictionary<int, int> teamEntities = new();

    public void OnPluginStart()
    {
        OnServerCheckTransmit_Register(OnCheckTransmit);
    }

    public void OnPluginEnd()
    {
        OnServerCheckTransmit_Unregister(OnCheckTransmit);
    }

    // Зарегистрировать объект как видимый только для конкретной команды
    public void SetEntityTeamVisibility(int entityHandle, int team)
    {
        teamEntities[entityHandle] = team;
    }

    private void OnCheckTransmit(nint[] infos)
    {
        for (int playerSlot = 0; playerSlot < infos.Length; playerSlot++)
        {
            var info = new CheckTransmitInfo(infos[playerSlot]);

            // Получить команду игрока
            int playerHandle = PlayerSlotToEntHandle(playerSlot);
            if (!IsValidEntHandle(playerHandle))
                continue;

            int playerTeam = GetEntityTeam(playerHandle);

            // Проверить каждый объект с ограничением по команде
            foreach (var kvp in teamEntities)
            {
                int entityHandle = kvp.Key;
                int requiredTeam = kvp.Value;

                if (!IsValidEntHandle(entityHandle))
                    continue;

                // Скрыть объект, если игрок не в правильной команде
                if (playerTeam != requiredTeam)
                {
                    info.ClearEntity(entityHandle);
                    info.ClearAlways(entityHandle);
                }
                else
                {
                    // Убедиться, что он виден для членов команды
                    info.SetEntity(entityHandle);
                }
            }
        }
    }

    private int GetEntityTeam(int entityHandle)
    {
        // Предполагая, что у вас есть способ получить номер команды из объекта
        // Это просто пример - реализуйте на основе вашего доступа к схеме
        return GetEntSchemaInt32(entityHandle, "CBaseEntity", "m_iTeamNum", false, 0);
    }
}

Пример 5: Контроль полного обновления

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

c#
using Plugify;
using static s2sdk.s2sdk;
using System.Collections.Generic;

public unsafe class FullUpdateControl : Plugin
{
    private HashSet<int> playersNeedingFullUpdate = new();

    public void OnPluginStart()
    {
        OnServerCheckTransmit_Register(OnCheckTransmit);

        // Подключить спавн игрока для принудительного полного обновления
        HookEvent("player_spawn", Event_PlayerSpawn, HookMode.Post);
    }

    public void OnPluginEnd()
    {
        OnServerCheckTransmit_Unregister(OnCheckTransmit);
    }

    private void Event_PlayerSpawn(string name, nint @event, bool dontBroadcast)
    {
        int playerSlot = GetEventPlayerSlot(@event, "userid", 0);

        // Отметить игрока для полного обновления при следующей передаче
        playersNeedingFullUpdate.Add(playerSlot);
    }

    private void OnCheckTransmit(nint[] infos)
    {
        for (int playerSlot = 0; playerSlot < infos.Length; playerSlot++)
        {
            var info = new CheckTransmitInfo(infos[playerSlot]);

            // Проверить, нужно ли этому игроку полное обновление
            if (playersNeedingFullUpdate.Contains(playerSlot))
            {
                info.SetFullUpdate(true);

                // Удалить после однократного применения
                playersNeedingFullUpdate.Remove(playerSlot);

                PrintToServer($"Sending full update to player {playerSlot}\n");
            }
        }
    }
}

Справочник методов

Операции с Entity BitVec

МетодОписание
SetEntity(entityHandle)Отметить объект как передаваемый этому игроку
ClearEntity(entityHandle)Отметить объект как не передаваемый этому игроку
IsEntitySet(entityHandle)Проверить, отмечен ли объект для передачи
SetEntityAll()Отметить все объекты как передаваемые
ClearEntityAll()Отметить все объекты как не передаваемые

Операции с Non-Player BitVec

МетодОписание
SetNonPlayer(entityHandle)Отметить не-игрока как передаваемого
ClearNonPlayer(entityHandle)Отметить не-игрока как не передаваемого
IsNonPlayerSet(entityHandle)Проверить, отмечен ли не-игрок для передачи
SetNonPlayerAll()Отметить всех не-игроков как передаваемых
ClearNonPlayerAll()Отметить всех не-игроков как не передаваемых

Операции постоянной передачи

МетодОписание
SetAlways(entityHandle)Принудительно всегда передавать объект
ClearAlways(entityHandle)Снять флаг принудительной передачи
IsAlwaysSet(entityHandle)Проверить, принудительно ли передается объект
SetAlwaysAll()Принудительно всегда передавать все объекты
ClearAlwaysAll()Очистить все флаги принудительной передачи

Операции с целевыми слотами

МетодОписание
GetTargetSlotsCount()Получить количество целевых слотов игроков
GetTargetSlot(index)Получить конкретный целевой слот по индексу
AddTargetSlot(playerSlot)Добавить целевой слот игрока
RemoveTargetSlot(index)Удалить целевой слот по индексу
GetTargetSlotsAll()Получить массив всех целевых слотов
RemoveTargetSlotsAll()Очистить все целевые слоты

Операции полного обновления

МетодОписание
GetFullUpdate()Получить флаг полного обновления
SetFullUpdate(bool)Установить флаг полного обновления

Лучшие практики

  1. Всегда регистрируйте во время OnPluginStart и отменяйте регистрацию во время OnPluginEnd
  2. Проверяйте хэндлы объектов перед использованием их в операциях передачи
  3. Делайте логику колбэка минимальной - CheckTransmit выполняется каждый тик
  4. Используйте ООП обёртку для более чистого и читаемого кода (когда доступно)
  5. Кэшируйте часто используемые данные вне колбэка
  6. Не выполняйте тяжёлые вычисления в колбэке CheckTransmit
  7. Не забывайте отменять регистрацию - могут возникнуть утечки памяти и сбои
  8. Не предполагайте, что объекты валидны - всегда проверяйте хэндлы

Типичные случаи использования

Системы наблюдателей

Скрывайте игроков от наблюдателей или создавайте пользовательские виды наблюдателей, контролируя, какие объекты могут видеть наблюдатели.

Невидимость администратора

Создайте режим невидимого администратора, где администраторы могут передвигаться, не будучи замеченными игроками.

Командный геймплей

Реализуйте туман войны или видимость для конкретных команд в пользовательских игровых режимах.

Оптимизация объектов

Сокращайте сетевой трафик, предотвращая передачу далёких или неактуальных объектов.

Пользовательские системы зрения

Создавайте ночное зрение, тепловидение или другие специальные режимы зрения, контролируя видимость объектов.

Устранение неполадок

Объекты всё ещё видны после очистки

Проблема: ClearEntity() вызван, но объект всё ещё виден Решение: Также вызовите ClearAlways() - объект может быть в списке постоянной передачи

Проблемы с производительностью

Проблема: Задержки сервера после реализации CheckTransmit Решение: Профилируйте ваш колбэк - вероятно, выполняется слишком много работы за тик. Кэшируйте данные и минимизируйте операции.

Изменения не применяются

Проблема: Модификации передачи, похоже, не работают Решение: Убедитесь, что вы изменяете правильный CheckTransmitInfo для целевого игрока. Проверьте, что хэндлы объектов валидны.

Хук не работает

Проблема: Колбэк никогда не вызывается Решение: Проверьте, что вы зарегистрировали хук во время OnPluginStart и что плагин S2SDK загружен.

Заключение

Система CheckTransmit - это мощный инструмент для контроля видимости объектов в Source 2. Подключившись к решениям сервера о передаче, вы можете создавать сложные игровые механики, оптимизировать сетевой трафик и создавать уникальный игровой опыт.

Ключевые моменты:

  • Регистрируйте хуки в OnPluginStart, отменяйте регистрацию в OnPluginEnd
  • Используйте функциональный или ООП API в зависимости от предпочтений
  • Делайте колбэки эффективными - они выполняются каждый тик
  • Всегда проверяйте хэндлы объектов перед использованием
  • Очищайте и флаги Entity, и Always при скрытии объектов