Check Transmit

How to control entity visibility and network transmission using CheckTransmit system.

The CheckTransmit system in Source 2 allows you to control which entities are networked to specific players. This powerful feature enables creating advanced gameplay mechanics like hiding/showing entities, creating player-specific visibility rules, and optimizing network traffic.

Overview

To work with the CheckTransmit system, you need to:

  1. Register the hook using OnServerCheckTransmit_Register during plugin start
  2. Receive CheckTransmitInfo pointers for each player in your callback
  3. Modify transmit states using the provided methods
  4. Unregister the hook using OnServerCheckTransmit_Unregister during plugin end

Basic Setup

Register the CheckTransmit hook in your plugin's start method and unregister it on plugin end:

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 array index represents player slot (0-63)
        for (int playerSlot = 0; playerSlot < infos.Length; playerSlot++)
        {
            var info = infos[playerSlot];
            // Modify transmit states for this player slot
        }
    }
}

CheckTransmitInfo Structure

Each CheckTransmitInfo object contains information about transmit states for a specific player. The player slot is determined by the array index (0-63), not by a field in the structure.

  • m_pTransmitEntity: BitVec controlling which entities are transmitted
  • m_pTransmitNonPlayers: BitVec for non-player entities
  • m_pTransmitAlways: BitVec for entities that should always transmit
  • m_vecTargetSlots: List of target player slots
  • m_bFullUpdate: Whether to send a full update

Available Methods

S2SDK provides two ways to work with CheckTransmitInfo:

1. Functional API (Direct Method Calls)

Use these methods by passing the CheckTransmitInfo pointer as the first parameter:

Entity Transmit Methods

SetTransmitInfoEntity(info, entityHandle);          // Mark entity as transmittable
ClearTransmitInfoEntity(info, entityHandle);        // Mark entity as not transmittable
IsTransmitInfoEntitySet(info, entityHandle);        // Check if entity is transmittable
SetTransmitInfoEntityAll(info);                     // Mark all entities as transmittable
ClearTransmitInfoEntityAll(info);                   // Mark all entities as not transmittable

Non-Player Transmit Methods

SetTransmitInfoNonPlayer(info, entityHandle);       // Mark non-player entity as transmittable
ClearTransmitInfoNonPlayer(info, entityHandle);     // Mark non-player entity as not transmittable
IsTransmitInfoNonPlayerSet(info, entityHandle);     // Check if non-player is transmittable
SetTransmitInfoNonPlayerAll(info);                  // Mark all non-players as transmittable
ClearTransmitInfoNonPlayerAll(info);                // Mark all non-players as not transmittable

Always Transmit Methods

SetTransmitInfoAlways(info, entityHandle);          // Mark entity to always transmit
ClearTransmitInfoAlways(info, entityHandle);        // Unmark entity from always transmit
IsTransmitInfoAlwaysSet(info, entityHandle);        // Check if entity always transmits
SetTransmitInfoAlwaysAll(info);                     // Mark all entities to always transmit
ClearTransmitInfoAlwaysAll(info);                   // Unmark all entities from always transmit

Target Slots Methods

GetTransmitInfoTargetSlotsCount(info);              // Get count of target slots
GetTransmitInfoTargetSlot(info, index);             // Get specific target slot
AddTransmitInfoTargetSlot(info, playerSlot);        // Add a target slot
RemoveTransmitInfoTargetSlot(info, index);          // Remove target slot by index
GetTransmitInfoTargetSlotsAll(info);                // Get all target slots
RemoveTransmitInfoTargetSlotsAll(info);             // Clear all target slots

Full Update Methods

GetTransmitInfoFullUpdate(info);                    // Get full update flag
SetTransmitInfoFullUpdate(info, fullUpdate);        // Set full update flag

2. Object-Oriented API (CheckTransmitInfo Class)

In languages that support it, you can construct a CheckTransmitInfo class from the pointer for more intuitive OOP-style access:

c#
c++
private void OnCheckTransmit(nint[] infos)
{
    for (int playerSlot = 0; playerSlot < infos.Length; playerSlot++)
    {
        // Construct OOP wrapper from pointer
        var info = new CheckTransmitInfo(infos[playerSlot]);

        // Use object methods instead of static functions
        info.SetEntity(entityHandle);
        info.ClearEntity(entityHandle);
        bool isSet = info.IsEntitySet(entityHandle);

        // Note: playerSlot is from the array index, not a method
        info.SetFullUpdate(true);
    }
}

Practical Examples

Example 1: Hide Specific Entity from All Players

c#
using Plugify;
using static s2sdk.s2sdk;

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

    public void OnPluginStart()
    {
        OnServerCheckTransmit_Register(OnCheckTransmit);

        // Add console command to hide an entity
        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;

        // Hide entity from all players
        for (int playerSlot = 0; playerSlot < infos.Length; playerSlot++)
        {
            ClearTransmitInfoEntity(infos[playerSlot], hiddenEntityHandle);
            // Also clear from always transmit
            ClearTransmitInfoAlways(infos[playerSlot], hiddenEntityHandle);
        }
    }
}

Example 2: Create Player-Specific Visibility

Hide entities from specific players based on conditions:

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

public unsafe class PlayerVisibility : Plugin
{
    // Map of player slot -> list of hidden entity handles
    private Dictionary<int, List<int>> playerHiddenEntities = new();

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

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

    // Hide a specific entity from a specific player
    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);
        }
    }

    // Show a previously hidden entity to a player
    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++)
        {
            // Using OOP style
            var info = new CheckTransmitInfo(infos[playerSlot]);

            // Check if this player has any hidden entities
            if (playerHiddenEntities.ContainsKey(playerSlot))
            {
                foreach (int entityHandle in playerHiddenEntities[playerSlot])
                {
                    // Only hide if entity is still valid
                    if (IsValidEntHandle(entityHandle))
                    {
                        info.ClearEntity(entityHandle);
                        info.ClearAlways(entityHandle);
                    }
                }
            }
        }
    }
}

Example 3: Hide Players from Each Other

Create "invisible mode" where specific players can't see each other:

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]);

            // For each invisible player
            foreach (int invisibleSlot in invisiblePlayers)
            {
                // Don't hide from themselves
                if (invisibleSlot == receiverSlot)
                    continue;

                // Get invisible player's entity handle
                int invisibleHandle = PlayerSlotToEntHandle(invisibleSlot);

                if (IsValidEntHandle(invisibleHandle))
                {
                    // Hide invisible player from this receiver
                    info.ClearEntity(invisibleHandle);
                    info.ClearAlways(invisibleHandle);
                }
            }
        }
    }
}

Example 4: Show Only Team Members

Display entities only to players on the same team:

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

public unsafe class TeamVisibility : Plugin
{
    // Map of entity handles to team numbers
    private Dictionary<int, int> teamEntities = new();

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

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

    // Register an entity to be visible only to a specific team
    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]);

            // Get player's team
            int playerHandle = PlayerSlotToEntHandle(playerSlot);
            if (!IsValidEntHandle(playerHandle))
                continue;

            int playerTeam = GetEntityTeam(playerHandle);

            // Check each team-restricted entity
            foreach (var kvp in teamEntities)
            {
                int entityHandle = kvp.Key;
                int requiredTeam = kvp.Value;

                if (!IsValidEntHandle(entityHandle))
                    continue;

                // Hide entity if player is not on the correct team
                if (playerTeam != requiredTeam)
                {
                    info.ClearEntity(entityHandle);
                    info.ClearAlways(entityHandle);
                }
                else
                {
                    // Ensure it's visible to team members
                    info.SetEntity(entityHandle);
                }
            }
        }
    }

    private int GetEntityTeam(int entityHandle)
    {
        // Assuming you have a way to get team number from entity
        // This is just an example - implement based on your schema access
        return GetEntSchemaInt32(entityHandle, "CBaseEntity", "m_iTeamNum", false, 0);
    }
}

Example 5: Full Update Control

Force full updates for specific situations:

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

        // Hook player spawn to force full update
        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);

        // Mark player for full update on next transmit
        playersNeedingFullUpdate.Add(playerSlot);
    }

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

            // Check if this player needs a full update
            if (playersNeedingFullUpdate.Contains(playerSlot))
            {
                info.SetFullUpdate(true);

                // Remove after applying once
                playersNeedingFullUpdate.Remove(playerSlot);

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

Method Reference

Entity BitVec Operations

MethodDescription
SetEntity(entityHandle)Mark entity as transmittable to this player
ClearEntity(entityHandle)Mark entity as not transmittable to this player
IsEntitySet(entityHandle)Check if entity is marked for transmission
SetEntityAll()Mark all entities as transmittable
ClearEntityAll()Mark all entities as not transmittable

Non-Player BitVec Operations

MethodDescription
SetNonPlayer(entityHandle)Mark non-player entity as transmittable
ClearNonPlayer(entityHandle)Mark non-player entity as not transmittable
IsNonPlayerSet(entityHandle)Check if non-player is marked for transmission
SetNonPlayerAll()Mark all non-player entities as transmittable
ClearNonPlayerAll()Mark all non-player entities as not transmittable

Always Transmit Operations

MethodDescription
SetAlways(entityHandle)Force entity to always transmit
ClearAlways(entityHandle)Remove force-transmit flag
IsAlwaysSet(entityHandle)Check if entity is forced to transmit
SetAlwaysAll()Force all entities to always transmit
ClearAlwaysAll()Clear all force-transmit flags

Target Slots Operations

MethodDescription
GetTargetSlotsCount()Get number of target player slots
GetTargetSlot(index)Get specific target slot by index
AddTargetSlot(playerSlot)Add a target player slot
RemoveTargetSlot(index)Remove target slot at index
GetTargetSlotsAll()Get array of all target slots
RemoveTargetSlotsAll()Clear all target slots

Full Update Operations

MethodDescription
GetFullUpdate()Get full update flag
SetFullUpdate(bool)Set full update flag

Best Practices

  1. Always register during OnPluginStart and unregister during OnPluginEnd
  2. Validate entity handles before using them in transmit operations
  3. Keep callback logic minimal - CheckTransmit runs every tick
  4. Use OOP wrapper for cleaner, more readable code (when available)
  5. Cache frequently-used data outside the callback
  6. Don't perform heavy calculations in CheckTransmit callback
  7. Don't forget to unregister - memory leaks and crashes can occur
  8. Don't assume entities are valid - always validate handles

Common Use Cases

Spectator Systems

Hide players from spectators or create custom spectator views by controlling which entities spectators can see.

Admin Invisibility

Create invisible admin mode where admins can move around without being seen by players.

Team-Based Gameplay

Implement fog of war or team-specific visibility for custom game modes.

Entity Optimization

Reduce network traffic by preventing transmission of far-away or irrelevant entities.

Custom Vision Systems

Create night vision, thermal vision, or other special vision modes by controlling entity visibility.

Troubleshooting

Entities Still Visible After Clearing

Problem: ClearEntity() called but entity still visible Solution: Also call ClearAlways() - the entity might be in the always-transmit list

Performance Issues

Problem: Server lag after implementing CheckTransmit Solution: Profile your callback - likely doing too much work per tick. Cache data and minimize operations.

Changes Not Taking Effect

Problem: Transmit modifications don't seem to work Solution: Ensure you're modifying the correct CheckTransmitInfo for the target player. Verify entity handles are valid.

Hook Not Working

Problem: Callback never gets called Solution: Verify you registered the hook during OnPluginStart and that S2SDK plugin is loaded.

Summary

The CheckTransmit system is a powerful tool for controlling entity visibility in Source 2. By hooking into the server's transmit decisions, you can create sophisticated gameplay mechanics, optimize network traffic, and build unique player experiences.

Key takeaways:

  • Register hooks in OnPluginStart, unregister in OnPluginEnd
  • Use either functional or OOP API based on preference
  • Keep callbacks efficient - they run every tick
  • Always validate entity handles before use
  • Clear both Entity and Always flags when hiding entities