Как контролировать видимость объектов и передачу по сети с помощью системы CheckTransmit.
Система CheckTransmit в Source 2 позволяет контролировать, какие объекты передаются по сети конкретным игрокам. Эта мощная функция позволяет создавать продвинутые игровые механики, такие как скрытие/показ объектов, создание правил видимости для конкретных игроков и оптимизация сетевого трафика.
Что такое CheckTransmit?: CheckTransmit вызывается каждый тик для определения, какие объекты должны быть переданы (по сети) каждому игроку. Подключившись к этой системе, вы можете динамически контролировать, что видит каждый игрок.
Зарегистрировать хук с помощью OnServerCheckTransmit_Register при запуске плагина
Получать указатели CheckTransmitInfo для каждого игрока в вашем колбэке
Изменять состояния передачи используя предоставленные методы
Отменить регистрацию хука с помощью OnServerCheckTransmit_Unregister при завершении плагина
Предупреждение о производительности: CheckTransmit вызывается каждый тик для каждого игрока. Делайте логику вашего колбэка эффективной, чтобы избежать проблем с производительностью сервера.
Зарегистрируйте хук 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];
// Изменяйте состояния передачи для этого слота игрока
}
}
}
#include <plg/plugin.hpp>
#include "s2sdk.hpp"
using namespace s2sdk;
class Sample : public plg::IPluginEntry {
public:
void OnPluginStart() override {
OnServerCheckTransmit_Register(
[](const plg::vector<void*>& infos) {
// индекс массива infos представляет слот игрока (0-63)
for (size_t playerSlot = 0; playerSlot < infos.size(); playerSlot++) {
auto* info = infos[playerSlot];
// Изменяйте состояния передачи для этого слота игрока
}
});
}
void OnPluginEnd() override {
OnServerCheckTransmit_Unregister();
}
};
from plugify.plugin import Plugin
from plugify.pps import s2sdk as s2
class Sample(Plugin):
def plugin_start(self):
s2.OnServerCheckTransmit_Register(self.on_check_transmit)
def plugin_end(self):
s2.OnServerCheckTransmit_Unregister(self.on_check_transmit)
def on_check_transmit(self, infos):
# индекс массива infos представляет слот игрока (0-63)
for player_slot, info in enumerate(infos):
# Изменяйте состояния передачи для этого слота игрока
pass
package main
import (
"s2sdk"
"github.com/untrustedmodders/go-plugify"
)
func init() {
plugify.OnPluginStart(func() {
s2sdk.OnServerCheckTransmit_Register(func(infos []s2sdk.CCheckTransmitInfo) {
// индекс массива infos представляет слот игрока (0-63)
for playerSlot, info := range infos {
// Изменяйте состояния передачи для этого слота игрока
_ = playerSlot
_ = info
}
})
})
plugify.OnPluginEnd(func() {
s2sdk.OnServerCheckTransmit_Unregister()
})
}
import { Plugin } from 'plugify';
import * as s2 from ':s2sdk';
export class Sample extends Plugin {
pluginStart() {
s2.OnServerCheckTransmit_Register((infos) => {
// индекс массива infos представляет слот игрока (0-63)
infos.forEach((info, playerSlot) => {
// Изменяйте состояния передачи для этого слота игрока
});
});
}
pluginEnd() {
s2.OnServerCheckTransmit_Unregister();
}
}
local plugify = require 'plugify'
local Plugin = plugify.Plugin
local s2 = require 's2sdk'
local Sample = {}
setmetatable(Sample, { __index = Plugin })
function Sample:plugin_start()
s2:OnServerCheckTransmit_Register(function(infos)
-- индекс массива infos представляет слот игрока (0-63)
for playerSlot, info in ipairs(infos) do
-- Изменяйте состояния передачи для этого слота игрока
end
end)
end
function Sample:plugin_end()
s2:OnServerCheckTransmit_Unregister()
end
local M = {}
M.Sample = Sample
return M
Каждый объект CheckTransmitInfo содержит информацию о состояниях передачи для конкретного игрока. Слот игрока определяется индексом массива (0-63), а не полем в структуре.
m_pTransmitEntity: BitVec, контролирующий, какие объекты передаются
m_pTransmitNonPlayers: BitVec для объектов, не являющихся игроками
m_pTransmitAlways: BitVec для объектов, которые всегда должны передаваться
m_vecTargetSlots: Список целевых слотов игроков
m_bFullUpdate: Должно ли быть отправлено полное обновление
Слот игрока: Позиция CheckTransmitInfo в массиве infos представляет слот игрока. infos[0] для слота игрока 0, infos[1] для слота игрока 1, и т.д.
Объяснение BitVec: BitVec - это эффективная структура данных, которая использует отдельные биты для отслеживания состояний объектов. Каждая позиция бита соответствует индексу объекта.
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); // Снять отметку всех объектов с постоянной передачи
GetTransmitInfoFullUpdate(info); // Получить флаг полного обновления
SetTransmitInfoFullUpdate(info, fullUpdate); // Установить флаг полного обновления
В языках, которые это поддерживают, вы можете создать класс 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);
}
}
void OnCheckTransmit(const plg::vector<void*>& infos) {
for (size_t playerSlot = 0; playerSlot < infos.size(); playerSlot++) {
// Использовать указатель напрямую с методами
CheckTransmitInfo info(infos[playerSlot]);
info.SetEntity(entityHandle);
info.ClearEntity(entityHandle);
bool isSet = info.IsEntitySet(entityHandle);
// Примечание: playerSlot берётся из индекса массива, а не из метода
info.SetFullUpdate(true);
}
}
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);
}
}
}
Скрывайте объекты от конкретных игроков на основе условий:
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);
}
}
}
}
}
}
Создать "режим невидимости", где конкретные игроки не видят друг друга:
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);
}
}
}
}
}
Отображать объекты только игрокам из той же команды:
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);
}
}
Принудительно отправлять полные обновления для конкретных ситуаций:
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");
}
}
}
}
Проблема: Задержки сервера после реализации CheckTransmit
Решение: Профилируйте ваш колбэк - вероятно, выполняется слишком много работы за тик. Кэшируйте данные и минимизируйте операции.
Проблема: Модификации передачи, похоже, не работают
Решение: Убедитесь, что вы изменяете правильный CheckTransmitInfo для целевого игрока. Проверьте, что хэндлы объектов валидны.
Система CheckTransmit - это мощный инструмент для контроля видимости объектов в Source 2. Подключившись к решениям сервера о передаче, вы можете создавать сложные игровые механики, оптимизировать сетевой трафик и создавать уникальный игровой опыт.
Ключевые моменты:
Регистрируйте хуки в OnPluginStart, отменяйте регистрацию в OnPluginEnd
Используйте функциональный или ООП API в зависимости от предпочтений
Делайте колбэки эффективными - они выполняются каждый тик
Всегда проверяйте хэндлы объектов перед использованием
Очищайте и флаги Entity, и Always при скрытии объектов
Дополнительная литература: Ознакомьтесь с руководством Handle System для получения дополнительной информации о работе с хэндлами объектов и слотами игроков.