Схемы сущностей

Как читать и изменять схемы сущностей Source 2 и сетевое состояние.

Понимание схем

В Source 1 сущности имели два типа свойств: send props (синхронизируемые с клиентами) и data props (только на сервере). Source 2 упростил это, объединив их в единую систему схем. Схемы определяют структуру и свойства всех игровых объектов, и многие поля схем автоматически передаются по сети.

Схемы позволяют вам получать доступ и изменять свойства любой сущности или объекта в Source 2, включая здоровье, позицию, скорость, модель, команду и бесчисленное количество других атрибутов. Когда вы изменяете сетевые поля схем с установленным changeState в true, изменения автоматически синхронизируются с клиентами.

Поиск информации о схемах

Вы можете найти названия классов схем и имена членов, используя:

  • Онлайн-дампы: репозиторий cs2-dumper
  • Дамперы схем: Различные инструменты сообщества для извлечения определений схем
  • Файлы ресурсов игры: Расположены в папках схем вашей игры

Распространенные классы схем включают CBaseEntity, CBasePlayerPawn, CCSPlayerController, CBaseModelEntity и многие другие.

Методы Schema vs методы Data

s2sdk предоставляет два подхода для работы со схемами:

Методы Schema (рекомендуется)

Используют имена классов и членов в виде строк. Они проще в использовании и более читаемы:

SetEntSchema(entityHandle, "CBaseEntity", "m_iHealth", 200, true, 0);
int health = GetEntSchema(entityHandle, "CBaseEntity", "m_iHealth", 0);

Методы Data (расширенные)

Используют необработанные смещения в памяти. Они быстрее, но требуют ручного управления смещениями:

int offset = GetSchemaOffset("CBaseEntity", "m_iHealth");
SetEntData(entityHandle, offset, 200, 4, true, 0);
int health = GetEntData(entityHandle, offset, 4);

Handle сущностей vs указатели

Большинство функций поставляются в двух версиях:

  • Версия с handle (например, GetEntSchema): Работает с handle сущностей - используйте это по возможности
  • Версия с указателем (например, GetEntSchema2): Работает с необработанными указателями - используйте только когда handle не могут быть сгенерированы (некоторые схемы не основаны на сущностях)

Всегда предпочитайте handle сущностей, если только вы не работаете с объектами схем, не являющимися сущностями.

Базовый пример: изменение здоровья сущности

Вот простой пример чтения и изменения здоровья сущности:

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

public unsafe class Sample : Plugin
{
    public void OnPluginStart()
    {
        AddConsoleCommand("set_health", "Sets player health to 200",
            ConVarFlag.LinkedConcommand | ConVarFlag.Release,
            (caller, context, arguments) =>
            {
                if (caller == -1) return ResultType.Handled;

                // Convert player's slot to entity's handle
                int player = PlayerSlotToEntHandle(caller);

                // Set the caller's health to 200
                SetEntSchema(player, "CBaseEntity", "m_iHealth", 200, true, 0);

                // Read back the health
                int health = (int)GetEntSchema(player, "CBaseEntity", "m_iHealth", 0);
                PrintToServer($"Player health set to {health}\n");

                return ResultType.Handled;
            }, HookMode.Post);
    }
}

Работа с различными типами данных

Система схем поддерживает несколько типов данных. Вот примеры для каждого:

Целые числа

// Получение/установка целочисленных значений (здоровье, броня, деньги и т.д.)
SetEntSchema(entityHandle, "CBaseEntity", "m_iHealth", 100, true, 0);
int health = (int)GetEntSchema(entityHandle, "CBaseEntity", "m_iHealth", 0);

Числа с плавающей точкой

// Получение/установка значений с плавающей точкой (скорость, гравитация и т.д.)
SetEntSchemaFloat(entityHandle, "CCSPlayerPawn", "m_flGravityScale", 0.5, true, 0);
double gravity = GetEntSchemaFloat(entityHandle, "CCSPlayerPawn", "m_flGravityScale", 0);

Строки

// Получение/установка строковых значений (имена, модели и т.д.)
SetEntSchemaString(entityHandle, "CBaseModelEntity", "m_ModelName", "models/player.mdl", true, 0);
string modelName = GetEntSchemaString(entityHandle, "CBaseModelEntity", "m_ModelName", 0);

Векторы

// Получение/установка 3D векторов (позиция, скорость, углы и т.д.)
using System.Numerics;

Vector3 newPos = new Vector3(100.0f, 200.0f, 300.0f);
SetEntSchemaVector3D(entityHandle, "CBaseEntity", "m_vecAbsOrigin", newPos, true, 0);
Vector3 position = GetEntSchemaVector3D(entityHandle, "CBaseEntity", "m_vecAbsOrigin", 0);

Handle сущностей

// Получение/установка ссылок на сущности (владелец, родитель, оружие и т.д.)
SetEntSchemaEnt(entityHandle, "CBaseEntity", "m_hOwnerEntity", ownerHandle, true, 0);
int owner = GetEntSchemaEnt(entityHandle, "CBaseEntity", "m_hOwnerEntity", 0);

Работа с массивами

Некоторые поля схем являются массивами. Используйте параметр element для доступа к определенным индексам массива:

// Получение размера массива
int arraySize = GetEntSchemaArraySize(entityHandle, "SomeClass", "m_arrayField");

// Доступ к элементам массива (параметр element - последний перед параметрами по умолчанию для конкретного типа)
for (int i = 0; i < arraySize; i++)
{
    int value = (int)GetEntSchema(entityHandle, "SomeClass", "m_arrayField", i);
    PrintToServer($"Array[{i}] = {value}\n");
}

// Установка элемента массива
SetEntSchema(entityHandle, "SomeClass", "m_arrayField", 42, true, 2); // Установить индекс 2 в 42

Понимание параметров

changeState (bool)

Когда true, помечает поле как измененное и запускает сетевую синхронизацию с клиентами. Устанавливайте в true при изменении сетевых полей, false для изменений только на сервере.

element (int)

Индекс массива при работе с полями массивов схем. Используйте 0 для полей, не являющихся массивами.

chainOffset (int)

Расширенный параметр для смещений цепочки в иерархиях наследования. Используйте 0 для большинства случаев, или используйте GetSchemaChainOffset() для конкретных цепочек наследования. Используйте -1 для классов, не являющихся сущностями.

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

Пример 1: Переключение режима бога

c#
using Plugify;
using static s2sdk.s2sdk;

public unsafe class GodMode : Plugin
{
    private bool[] godMode = new bool[65]; // Max players

    public void OnPluginStart()
    {
        AddConsoleCommand("god", "Toggle god mode",
            ConVarFlag.LinkedConcommand | ConVarFlag.Release,
            Command_God, HookMode.Post);
    }

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

        godMode[caller] = !godMode[caller];

        int player = PlayerSlotToEntHandle(caller);

        if (godMode[caller])
        {
            SetEntSchema(player, "CBaseEntity", "m_iHealth", 999, true, 0);
            SetEntSchema(player, "CBaseEntity", "m_takedamage", 0, true, 0); // DAMAGE_NO
            PrintToServer("God mode enabled\n");
        }
        else
        {
            SetEntSchema(player, "CBaseEntity", "m_iHealth", 100, true, 0);
            SetEntSchema(player, "CBaseEntity", "m_takedamage", 2, true, 0); // DAMAGE_YES
            PrintToServer("God mode disabled\n");
        }

        return ResultType.Handled;
    }
}

Пример 2: Телепортация

c#
using System.Numerics;
using Plugify;
using static s2sdk.s2sdk;

public unsafe class Teleport : Plugin
{
    public void OnPluginStart()
    {
        AddConsoleCommand("teleport", "Teleport to coordinates",
            ConVarFlag.LinkedConcommand | ConVarFlag.Release,
            Command_Teleport, HookMode.Post);
    }

    public ResultType Command_Teleport(int caller, int context, string[] arguments)
    {
        if (caller == -1) return ResultType.Handled;
        if (arguments.Length < 4)
        {
            PrintToServer("Usage: teleport <x> <y> <z>\n");
            return ResultType.Handled;
        }

        int player = PlayerSlotToEntHandle(caller);

        float x = float.Parse(arguments[1]);
        float y = float.Parse(arguments[2]);
        float z = float.Parse(arguments[3]);

        Vector3 newPosition = new Vector3(x, y, z);
        SetEntSchemaVector3D(player, "CBaseEntity", "m_vecAbsOrigin", newPosition, true, 0);

        // Also reset velocity to prevent momentum
        Vector3 zeroVelocity = new Vector3(0, 0, 0);
        SetEntSchemaVector3D(player, "CBaseEntity", "m_vecAbsVelocity", zeroVelocity, true, 0);

        PrintToServer($"Teleported to ({x}, {y}, {z})\n");

        return ResultType.Handled;
    }
}

Пример 3: Смена модели игрока

c#
using Plugify;
using static s2sdk.s2sdk;

public unsafe class ModelChanger : Plugin
{
    public void OnPluginStart()
    {
        AddConsoleCommand("setmodel", "Change player model",
            ConVarFlag.LinkedConcommand | ConVarFlag.Release,
            Command_SetModel, HookMode.Post);
    }

    public ResultType Command_SetModel(int caller, int context, string[] arguments)
    {
        if (caller == -1) return ResultType.Handled;
        if (arguments.Length < 2)
        {
            PrintToServer("Usage: setmodel <model_path>\n");
            return ResultType.Handled;
        }

        int player = PlayerSlotToEntHandle(caller);

        string modelPath = arguments[1];
        SetEntSchemaString(player, "CBaseModelEntity", "m_ModelName", modelPath, true, 0);

        PrintToServer($"Model changed to: {modelPath}\n");

        return ResultType.Handled;
    }
}

Пример 4: Модификатор скорости

c#
using Plugify;
using static s2sdk.s2sdk;

public unsafe class SpeedMod : Plugin
{
    public void OnPluginStart()
    {
        AddConsoleCommand("speed", "Change player speed",
            ConVarFlag.LinkedConcommand | ConVarFlag.Release,
            Command_Speed, HookMode.Post);
    }

    public ResultType Command_Speed(int caller, int context, string[] arguments)
    {
        if (caller == -1) return ResultType.Handled;
        if (arguments.Length < 2)
        {
            PrintToServer("Usage: speed <multiplier> (1.0 = normal)\n");
            return ResultType.Handled;
        }

        int player = PlayerSlotToEntHandle(caller);

        // Get pawn entity from player
        int entityHandle = GetEntSchemaEnt(player, "CCSPlayerController", "m_hPlayerPawn");

        float speedMultiplier = float.Parse(arguments[1]);

        // Set speed on player pawn
        SetEntSchemaFloat(entityHandle, "CCSPlayerPawn", "m_flVelocityModifier", speedMultiplier, true, 0);

        PrintToServer($"Speed set to {speedMultiplier}x\n");

        return ResultType.Handled;
    }
}

Расширенное: использование необработанных смещений

Для критичного по производительности кода вы можете использовать необработанные смещения:

// Получите смещение один раз и кешируйте его
int healthOffset = GetSchemaOffset("CBaseEntity", "m_iHealth");
int chainOffset = GetSchemaChainOffset("CBaseEntity");

// Используйте смещение напрямую для лучшей производительности
SetEntData(entityHandle, healthOffset, 100, 4, true, chainOffset);
int health = (int)GetEntData(entityHandle, healthOffset, 4);

Использование версий с указателями

Для объектов схем, не являющихся сущностями (когда handle недоступны):

// Пример: работа с объектом схемы, не являющимся сущностью
nint objectPointer = GetSomeNonEntityPointer();

// Используйте версию функций с '2'
SetEntSchema2(objectPointer, "SomeClass", "m_someField", 42, true, 0);
int value = (int)GetEntSchema2(objectPointer, "SomeClass", "m_someField", 0);

Обновления сетевого состояния

Для ручного запуска сетевых обновлений:

// Обновить конкретное поле
NetworkStateChanged(entityHandle, "CBaseEntity", "m_iHealth");

// Или используя смещение
int offset = GetSchemaOffset("CBaseEntity", "m_iHealth");
int chainOffset = GetSchemaChainOffset("CBaseEntity");
ChangeEntityState(entityHandle, offset, chainOffset);

Советы и лучшие практики

  1. Используйте методы schema вместо методов data - Они более читаемы и удобны в обслуживании
  2. Предпочитайте handle сущностей указателям - Используйте версии с указателями только при необходимости
  3. Кешируйте смещения в критичном по производительности коде - Избегайте повторных вызовов GetSchemaOffset()
  4. Всегда устанавливайте changeState правильно - Используйте true для сетевых изменений, false только для сервера
  5. Обращайтесь к дампам схем - Проверьте s2d-schema для доступных полей
  6. Проверяйте handle сущностей - Убедитесь, что сущности действительны перед доступом к схемам
  7. Используйте соответствующие типы данных - Сопоставляйте функцию с типом поля схемы (Int, Float, String, Vector и т.д.)
  8. Тестируйте с клиентами - Проверьте, что сетевые изменения синхронизируются правильно

Распространенные поля схем

Вот некоторые часто используемые поля схем:

CBaseEntity:

  • m_iHealth - Здоровье сущности
  • m_iMaxHealth - Максимальное здоровье
  • m_vecAbsOrigin - Позиция
  • m_angAbsRotation - Углы поворота
  • m_vecAbsVelocity - Скорость
  • m_hOwnerEntity - Handle сущности-владельца
  • m_takedamage - Поведение урона (0=нет, 2=да)

CBasePlayerPawn:

  • m_iMaxHealth - Максимальное здоровье
  • m_ArmorValue - Количество брони
  • m_vecViewOffset - Смещение высоты обзора

CCSPlayerPawn:

  • m_flGravityScale - Множитель гравитации
  • m_flVelocityModifier - Множитель скорости
  • m_bIsScoped - Использует ли игрок прицел

CCSPlayerController:

  • m_sSanitizedPlayerName - Имя игрока
  • m_iPing - Пинг игрока
  • m_iScore - Счет игрока
  • m_hPlayerPawn - Handle пешки игрока

CBaseModelEntity:

  • m_ModelName - Путь к модели
  • m_nSkin - Скин модели
  • m_clrRender - Цвет рендера

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