В Source 1 сущности имели два типа свойств: send props (синхронизируемые с клиентами) и data props (только на сервере). Source 2 упростил это, объединив их в единую систему схем. Схемы определяют структуру и свойства всех игровых объектов, и многие поля схем автоматически передаются по сети.
Схемы позволяют вам получать доступ и изменять свойства любой сущности или объекта в Source 2, включая здоровье, позицию, скорость, модель, команду и бесчисленное количество других атрибутов. Когда вы изменяете сетевые поля схем с установленным changeState в true, изменения автоматически синхронизируются с клиентами.
Версия с 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);
}
}
#include <plugify/cpp_plugin.hpp>
#include "s2sdk.hpp"
using namespace s2sdk;
class Sample : public plg::IPluginEntry {
public:
void OnPluginStart() override {
ConVarFlag flags = ConVarFlag::LinkedConcommand | ConVarFlag::Release;
AddConsoleCommand("set_health", "Sets player health to 200", flags,
[](int caller, int context, const plg::vector<plg::string>& arguments) -> ResultType {
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
int64_t health = GetEntSchema(player, "CBaseEntity", "m_iHealth", 0);
PrintToServer(std::format("Player health set to {}\n", health).c_str());
return ResultType::Handled;
}, HookMode::Post);
}
};
from plugify.plugin import Plugin
from plugify.pps import s2sdk as s2
class Sample(Plugin):
def plugin_start(self):
flags = s2.ConVarFlag.LinkedConcommand | s2.ConVarFlag.Release
def set_health_cmd(caller, context, arguments):
if caller == -1:
return s2.ResultType.Handled
# Convert player's slot to entity's handle
player = s2.PlayerSlotToEntHandle(caller);
# Set the caller's health to 200
s2.SetEntSchema(player, "CBaseEntity", "m_iHealth", 200, True, 0)
# Read back the health
health = s2.GetEntSchema(player, "CBaseEntity", "m_iHealth", 0)
s2.PrintToServer(f"Player health set to {health}\n")
return s2.ResultType.Handled
s2.AddConsoleCommand("set_health", "Sets player health to 200", flags,
set_health_cmd, s2.HookMode.Post)
package main
import (
"fmt"
"s2sdk"
"github.com/untrustedmodders/go-plugify"
)
func init() {
plugify.OnPluginStart(func() {
flags := s2sdk.ConVarFlag.LinkedConcommand | s2sdk.ConVarFlag.Release
s2sdk.AddConsoleCommand("set_health", "Sets player health to 200", flags,
func(caller int, context int, arguments []string) s2sdk.ResultType {
if caller == -1 {
return s2sdk.ResultType.Handled
}
// Convert player's slot to entity's handle
int player = s2sdk.PlayerSlotToEntHandle(caller);
// Set the caller's health to 200
s2sdk.SetEntSchema(player, "CBaseEntity", "m_iHealth", 200, true, 0)
// Read back the health
health := s2sdk.GetEntSchema(player, "CBaseEntity", "m_iHealth", 0)
s2sdk.PrintToServer(fmt.Sprintf("Player health set to %d\n", health))
return s2sdk.ResultType.Handled
}, s2sdk.HookMode.Post)
})
}
import { Plugin } from 'plugify';
import * as s2 from ':s2sdk';
export class Sample extends Plugin {
pluginStart() {
const flags = s2.ConVarFlag.LinkedConcommand | s2.ConVarFlag.Release;
s2.AddConsoleCommand("set_health", "Sets player health to 200", flags,
(caller, context, arguments) => {
if (caller === -1) return s2.ResultType.Handled;
// Convert player's slot to entity's handle
const player = s2.PlayerSlotToEntHandle(caller);
// Set the caller's health to 200
s2.SetEntSchema(player, "CBaseEntity", "m_iHealth", 200, true, 0);
// Read back the health
const health = s2.GetEntSchema(player, "CBaseEntity", "m_iHealth", 0);
s2.PrintToServer(`Player health set to ${health}\n`);
return s2.ResultType.Handled;
}, s2.HookMode.Post);
}
}
local plugify = require 'plugify'
local Plugin = plugify.Plugin
local s2 = require 's2sdk'
local Sample = {}
setmetatable(Sample, { __index = Plugin })
function Sample:plugin_start()
local flags = bit.bor(s2.ConVarFlag.LinkedConcommand, s2.ConVarFlag.Release)
s2:AddConsoleCommand("set_health", "Sets player health to 200", flags,
function(caller, context, arguments)
if caller == -1 then return s2.ResultType.Handled end
-- Convert player's slot to entity's handle
local player = s2:PlayerSlotToEntHandle(caller);
-- Set the caller's health to 200
s2:SetEntSchema(player, "CBaseEntity", "m_iHealth", 200, true, 0)
-- Read back the health
local health = s2:GetEntSchema(player, "CBaseEntity", "m_iHealth", 0)
s2:PrintToServer(string.format("Player health set to %d\n", health))
return s2.ResultType.Handled
end, s2.HookMode.Post)
end
local M = {}
M.Sample = Sample
return M
Некоторые поля схем являются массивами. Используйте параметр 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
Когда true, помечает поле как измененное и запускает сетевую синхронизацию с клиентами. Устанавливайте в true при изменении сетевых полей, false для изменений только на сервере.
Расширенный параметр для смещений цепочки в иерархиях наследования. Используйте 0 для большинства случаев, или используйте GetSchemaChainOffset() для конкретных цепочек наследования. Используйте -1 для классов, не являющихся сущностями.
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);