SourcePawn Консольные переменные в SourceMod (ConVars)

Pushistik↯❤

Команда форума
Регистрация
6 Июл 2017
Сообщения
393
Реакции
97
Баллы
28
Консольные переменные в SourceMod (ConVars)
Я думаю что каждый, кто однажды писал плагины для SourceMod, сталкивался с консольными переменнами (ака ConVars).
В "блог" всё сообщение не вместилось, да и русскоязычного перевода я не нашел. Вот так и решил выложить оригинальную статью на родном языке.

Первая часть включает в себя вступление, поиск, создание, изменение значений для переменных, флаги, отслеживание изменений, отправка значений переменных клиентам и полезные советы.
Вторая часть посложнее - ориентирована специально для разработчиков плагинов. В сей раздел входят стили для создания переменных, их возможности и недостатки.
Вступление:
ConVars это консольные переменные. А значения переменных могут быть как string, float так и другие нумерационные значения.
Эти значения можно изменять через консоль или конфиги, и они остаются на протяжении всего времени работы сервера.
Доступ к ConVars можно получить через носители (Handles). Существует два способа контролировать переменные - создать новую или запросить старую.

Заметка: Носители переменных всегда разные и их не нужно "освобождать" вручную. Когда плагин будет отключен, SourceMod, соответственно, всё выполнит за Вас.

Поиск переменных:
Запрос и поиск существующих переменных довольно прост. Допустим, Вы хотите найти mp_startmoney из Counter-Strike: Source.
PHP:
new Handle:mp_startmoney 

public OnPluginStart()
{
    mp_startmoney = FindConVar("mp_startmoney")
}
Заметка: FindConVar() может вернуть нулевое значение (например, когда переменной не существует). Имейте это ввиду, если Вы хотите запрашивать переменные из других плагинов.

Создание ConVars:
Простой переменной достаточно всего лишь задать имя и значение. Однако, неплохо бы еще добавить описание:
PHP:
new Handle:g_hEnabled 

public OnPluginStart()
{
    g_hEnabled = CreateConVar("название_переменной", "значение", "описание")
}
Так же Вы можете задать лимиты значений для своей переменной (допустим от 0 до 1, как в bool)
PHP:
new Handle:g_hEnabled 

public OnPluginStart()
{
    g_hEnabled = CreateConVar("название_переменной", "0", "описание",
            _,    // Флаг (об этом позже)
            true, // Зададим минимум
            0.0,  // И, соответственно, значение
            true, // Нужно ли нам задать максимум?
            1.0)  // Да!
}
Заметка: Если Вы используете не нумерационное значение для переменной, то и лимиты добавлять не нужно!

Использование/изменение значений:
Вы хотите изменить значение mp_startmoney, но в дальнейшем вернуть значение к оригинальному? Вот пример:
PHP:
new g_oldmoney 

SetStartMoney(newmoney)
{
    // Сохраним значение mp_startmoney в глобальный g_oldmoney (integer)
    g_oldmoney = GetConVarInt(g_hStartMoney)

    // А теперь изменим значение mp_startmoney на новое
    SetConVarInt(g_hStartMoney, newmoney)
}

RestoreStartMoney()
{
    SetConVarInt(g_hStartMoney, g_oldmoney) // Вернем оригинальное значение для mp_startmoney
}
Не забывайте, что типы значений для каждой переменной могут быть разными (float, string и другие). Само собой разумеется, что и значение сохраняется в том же типе.
PHP:
GetStartMoney() 

{
    decl String:buffer[128] // Буффер в который мы "запишем" значение

    // Запросим значение mp_startmoney
    GetConVarString(g_hStartMoney, buffer, sizeof(buffer))

    // Конвертируем string в integer
    return StringToInt(buffer)
}
Заметка: Несмотря на то, что значение mp_startmoney нумерационное (integer), оно может извлекаться как string!

Флаги:
Консольным переменным можно задавать флаги. В отдельных случаях флаги играют важную роль.

Основные флаги:
FCVAR_NOTIFY - Игроки будут оповещены об изменениях. Еще при помощи этого флага можно отслеживать
кто пользуется Вашим плагином.
FCVAR_CHEAT - Задает cheat-флаг переменной (т.е. чтобы изменить значение нужно прописать sv_cheats 1 в консоль сервера).
FCVAR_PLUGIN - Флаг, определяющий что эта переменная создана плагином (на самом деле это вообще ни на что не влияет).
FCVAR_DONTRECORD - Если переменная имеет этот флаг, то SM не будет записывать её в конфиг (при использовании
AutoExecConfig(true).

Флаги можно не только задавать, но и добавлять/убирать/whatever.
Например, мы хотим изменять значение переменной без изменения sv_cheats.
PHP:
new Handle:MyConVar 


public OnPluginStart()
{
    MyConVar = FindConVar("mp_mycheatcvar") // Найдем нашу переменную

    if (MyConVar != INVALID_HANDLE) // Проверим существует ли она вообще
    {
        //SetCheatVar(MyConVar) // Добавим чит-флаг
        UnsetCheatVar(MyConVar) // Извлечем чит-флаг
    }
}

UnsetCheatVar(Handle:hndl)
{
    new flags = GetConVarFlags(hndl) // Запросим оригинальные флаги
    flags &= ~FCVAR_CHEAT // Извлечем чит-флаг
    SetConVarFlags(hndl, flags) // Вернем все предыдущие флаги, но теперь без FCVAR_CHEAT
}

SetCheatVar(Handle:hndl)
{
    new flags = GetConVarFlags(hndl)
    flags |= FCVAR_CHEAT // То же самое, но добавим FCVAR_CHEAT
    SetConVarFlags(hndl, flags)
}
Заметка: Вся эта мишура с флагами необязательна, так как SourceMod предоставляет возможность менять значения для переменных с любым флагом через sm_cvar "cvar_name" "value".
Список всех флагов можно посмотреть на
Developer.Valvesoftware и SM API

Отслеживание изменений:
Очень полезная функция. Тем не менее ею нужно пользоваться с осторожностью!
Нельзя изменять значение переменной внутри хука, иначе цикл будет продолжаться до бесконечности!
Например, Вы хотите чтобы значение bot_quota не было ниже 2:
PHP:
new Handle:g_hBotQuota 


public OnPluginStart()
{
    g_hBotQuota = FindConVar("bot_quota")
    if (g_hBotQuota != INVALID_HANDLE)
    {
        HookConVarChange(g_hBotQuota, OnBotQuotaChange) // Hook all the changes
    }
}

public OnBotQuotaChange(Handle:cvar, const String:oldVal[], const String:newVal[])
{
    if (StringToInt(newVal) < 2) // Так как значение всегда string ("золотая середина"), его нужно конвертировать в int или float (т.е. просто убрать скобки)
    {
        SetConVarInt(cvar, 2) // В нашем случае значение изменилось на 0 или 1, но мы не хотим этого допускать! Возвращаем минимально-допустимое значение....
    }
}
Заметка: Как уже было сказано ранее, нет необходимости высвобождать переменные и хук, SM всё сделает за Вас.

Отправка переменных.
SM предоставляет отличную функцию - отправлять переменные клиентам.
Таким образом можно отправлять клиентам значение переменной без изменения этой переменной на сервере!
Это тоже вполне легко реализовать:
PHP:
new Handle:g_hMyCvar 


public OnPluginStart()
{
    g_hMyCvar = FindConVar("sv_cheats") // Найдем необходмую нам переменную
}

public OnClientPutInServer(client)
{
    SendConVarValue(client, g_hMyCvar, "1") // И отправим значение 1 когда игрок заходит на сервер
}
Полезные советы:
  • Чаще используйте HookConVarChange!
    Если Ваш плагин часто использует значения переменных (например каждый раз когда игрок умирает или получает урон), то можно использовать хук таким образом:
PHP:
new Handle:Enabled 


public OnPluginStart()
{
    enabled = CreateConVar("mycvar_enable",  "1", "shall we work?", FCVAR_PLUGIN, true, 0.0, true, 1.0);

    // hoooooooooooooooooooooooooooooook
    HookConVarChange(enabled, OnConVarChange);

    // Вручную сменим значение с 0 до 1 чтобы загрузить события
    OnConVarChange(enabled, "0", "1");
}

public OnConVarChange(Handle:convar, const String:oldValue[], const String:newValue[])
{
    switch (StringToInt(newValue))
    {
        case false:
        {
            UnhookEvent("player_spawn",            Event_Player_Spawn,    EventHookMode_Post);
            UnhookEvent("player_hurt",             Event_Player_Hurt,     EventHookMode_Post);
            UnhookEvent("dod_stats_weapon_attack", Event_Player_Attack,   EventHookMode_Post);
            UnhookEvent("dod_point_captured",      Event_Point_Captured,  EventHookMode_Post);
            UnhookEvent("dod_capture_blocked",     Event_Capture_Blocked, EventHookMode_Post);
            UnhookEvent("dod_bomb_planted",        Event_Bomb_Planted,    EventHookMode_Post);
            UnhookEvent("dod_bomb_defused",        Event_Bomb_Defused,    EventHookMode_Post);
            UnhookEvent("dod_round_win",           Event_Round_End,       EventHookMode_Post);
            UnhookEvent("dod_round_start",         Event_Game_Over,       EventHookMode_PostNoCopy);
            UnhookEvent("dod_game_over",           Event_Game_Over,       EventHookMode_PostNoCopy);
        }
        case true:
        {
            HookEvent("player_spawn",            Event_Player_Spawn,    EventHookMode_Post);
            HookEvent("player_hurt",             Event_Player_Hurt,     EventHookMode_Post);
            HookEvent("dod_stats_weapon_attack", Event_Player_Attack,   EventHookMode_Post);
            HookEvent("dod_point_captured",      Event_Point_Captured,  EventHookMode_Post);
            HookEvent("dod_capture_blocked",     Event_Capture_Blocked, EventHookMode_Post);
            HookEvent("dod_bomb_planted",        Event_Bomb_Planted,    EventHookMode_Post);
            HookEvent("dod_bomb_defused",        Event_Bomb_Defused,    EventHookMode_Post);
            HookEvent("dod_round_win",           Event_Round_End,       EventHookMode_Post);
            HookEvent("dod_round_start",         Event_Game_Over,       EventHookMode_PostNoCopy);
            HookEvent("dod_game_over",           Event_Game_Over,       EventHookMode_PostNoCopy);
        }
    }
}
Согласитель, что этот метод намного лучше чем тот, который указан ниже:
PHP:
public Event_Player_Spawn(Handle:event, const String:name[], bool:dontBroadcast) 

{
    if (GetConVarBool(enabled))
    {

    }
}

public Event_Player_Hurt(Handle:event, const String:name[], bool:dontBroadcast)
{
    if (GetConVarBool(enabled))
    {

    }
}
...
Полезные советы:
  • Не использутей FCVAR_REPLICATED для Вашей переменной! Иначе каждый раз, когда игрок будет заходить на сервер, он будет получать ошибку в консоли типа:
    "ConVarRef mycvar_version doesn't point to an existing ConVar. SetConVar: No such cvar ( mycvar_version set to XYZ), skipping"
  • Если переменная использует много значений (1 2 3 4 5 6 7 8 9 10), используйте bit оператор вместо GetConVarInt (ну или в конце-концов switch()).
  • Старайтесь избегать одинаковых названий с обычными переменными (типа mp_startmoney), в противном случае плагин откажется работать!
  • Однажды созданная переменная сохраняется в кэше сервера, так что если Вы использовали некорректное описание/лимит - лучше перезагрузите сервер - иначе изменения не вступят в силу.
  • Если Вам надо найти уже существующую переменную, ищите её в событии OnPluginStart() и сохраните как глобальный Handle. Почему? Да потому что тысячу раз твердили что поиск уже существующих переменных тяжеловат для сервера. Лучше один раз найти и сохранить в памяти! Так то.
У Вас есть плагин, который запрашивает много переменных, но сей процесс требует оптимизации и упрощения? Тогда эта часть статьи для Вас!
Существует некоторое множество стилей для ConVars, о которых многие не знают или просто догадываются.
Самый обычный и оригинальный стиль, который используется во множестве плагинов.
Преимущества: простота в использовании.
Недостатки: не лучший способ для работы с крупными плагинами.
PHP:
new Handle:MyConVar
public OnPluginStart()
{
    MyConVar = CreateConVar("MyConVar", "1", "Description");
}
public OnMapStart()
{
    if (GetConVarBool(MyConVar) == true)
    {
        // бла бла бла
    }
}
Преимущества: Значение сохраняется в переменной (например somevalue) и изменяется при изменении переменной. Полезно при частом использовании значения.
Недостатки:
-не лучший способ для небольших плагинов.
-много (ненужных) переменных типа somevalue.
PHP:
new Handle:somecvar = INVALID_HANDLE;
new somevalue; // Место хранения значения

public OnPluginStart()
{
    somecvar = CreateConVar("sm_somecvar", "6", "some cvar");
    HookConVarChange(somecvar, OnConVarChange);
}

public OnConVarChange(Handle:convar, const String:oldValue[], const String:newValue[])
{
    somevalue = GetConVarInt(somecvar); // Проделаем тоже самое когда значение изменилось, например, через консоль
}

public OnPlayerSpawn(client)
{
    switch (somevalue)
    {
        case 0: // Когда значение равно 0
        case 1: // Или 1
        default: // Другое
}
Стиль одного из разработчиков SourceMod. Достоин внимания, так как очень оригинален, интересен и в некой степени оптимизирован.
Преимущества: Глобальные носители (Handle) не используются, что положительно сказывается на производительности сервера.
Недостатки: нужно отслеживать изменения для каждой переменной по отдельности.
PHP:
new g_iMinPCount = 12;
new bool:g_bEnabled = true;
new g_iCmdRate[2];

public OnPluginStart()
{
    new Handle:hRegister; // Локальный handle для регистрации ConVar'ов

    // Сразу будем отслеживать изменения и зарегистрируем переменную
    HookConVarChange((hRegister = CreateConVar("enabled", "1", "Should I even be running?", _, true, 0.0, true, 1.0)), OnEnabledChange);
    g_bEnabled = GetConVarBool(hRegister); // Мгновенно присвоим значение для глобального буля

    HookConVarChange((hRegister = CreateConVar("minplayercount", "12", "How many players need to be ingame for any check to occur.")), OnMinPlayChange);
    g_iMinPCount = GetConVarInt(hRegister);

    // С таким же успехом можно и находить отдельные переменные
    if((hRegister = FindConVar("sv_mincmdrate")) != INVALID_HANDLE)
    {
        g_iCmdRate[0] = GetConVarInt(hRegister);
        HookConVarChange(hRegister, OnMinCmdRateChange);
    }

    if((hRegister = FindConVar("sv_maxcmdrate")) != INVALID_HANDLE)
    {
        g_iCmdRate[1] = GetConVarInt(hRegister);
        HookConVarChange(hRegister, OnMaxCmdRateChange);
    }

    CloseHandle(hRegister); // Нам ведь не нужны утечки памяти
}

public OnConfigsExecuted()
{
    if(g_bEnabled)
    {
        // бла бла
    }
}

public OnEnabledChange(Handle:convar, const String:oldValue[], const String:newValue[])
{
    g_bEnabled = GetConVarBool(convar);
}

public OnMinPlayChange(Handle:convar, const String:oldValue[], const String:newValue[])
{
    g_iMinPCount = GetConVarInt(convar); // ... для каждой переменной
}

public OnMinCmdRateChange(Handle:convar, const String:oldValue[], const String:newValue[])
{
    g_iCmdRate[0] = GetConVarInt(convar);
}

public OnMaxCmdRateChange(Handle:convar, const String:oldValue[], const String:newValue[])
{
    g_iCmdRate[1] = GetConVarInt(convar);
}
Автор неизвестен. Но стиль имеет место быть и достоин внимания.
Полезен в том случае когда много однотипных переменных (полезно для плагина статистики).
Ничем особо не отличается от оригинальных стилей.
PHP:
enum CSWeaponID //взято из cstrike.inc
{
    CSWeapon_NONE = 0,
    CSWeapon_P228,
    CSWeapon_GLOCK,
    CSWeapon_SCOUT,
    ...
}
enum WeaponType // Список носителей для создания ConVar
{
    Handle:bomb,
    Handle:p228,
    Handle:glock,
    Handle:scout
};

new Points[WeaponType]; // "Регистратор"

public OnPluginStart()
{
    Points[bomb] = CreateConVar("stats_points_bomb", "1",  "Points for killing by bomb");
    Points[p228]  = CreateConVar("stats_points_p228",  "5",  "Points for killing by p228");
    Points[glock] = CreateConVar("stats_points_glock",  "5",  "Points for killing by glock");
    Points[scout] = CreateConVar("stats_points_scout",  "7",  "Points for killing by scout");
}

public OnPlayerDeath(attacker)
{
    GivePoints(attacker, GetConVarInt(Points[GetEventInt(event, "weapon")])); // Дадим убийце Х очков в зависимости от оружия.
}
Придуман одним талантливым шведом.
Преимущества:
  • автоматизация процесса отслеживания переменных (хук).
  • безошибочность в извлечении типа значения (float, int или string).
  • простота процесса извлечения значения.
  • отлично подходит для больших плагинов.
  • оптимизированность и оригинальность.
Недостатки:
  • нельзя мгновенно отслеживать изменения значений типа string
  • может показаться сложным
Пример из плагина DoD:S DropManager
PHP:
enum
{
    AllowHealthkit, // Номер переменной
    ItemLifeTime,
    DeadDrop,
    ConfigFile,

    ConVar_Size // Максимальное количество переменных
};

enum ValueType
{
    ValueType_Bool,
    ValueType_Int,
    ValueType_Float,
    ValueType_String
};

enum ConVar
{
    Handle:ConVarHandle, // Носитель консольной переменной
    ValueType:Type,      // Тип значения (int, bool)
    any:Value            // Само значение
};

new GetConVar[ConVar_Size][ConVar]; // Функция запроса значения

LoadConVars() // Добавьте это в OnPluginStart()
{
    // Номер, тип и оригинальный CreateConVar
    AddConVar(AllowHealthkit, ValueType_Bool,  CreateConVar("dod_dropmanager_healthkit", "1", "Whether or not allow health kits dropping", FCVAR_PLUGIN, true, 0.0, true, 1.0));

    // Значение float/integer/string
    AddConVar(ItemLifeTime,  ValueType_Float,  CreateConVar("dod_dropmanager_lifetime", "45", "Number of seconds a dropped items stays on the ground", FCVAR_PLUGIN, true, 0.0));
    AddConVar(DeadDrop,      ValueType_Int,    CreateConVar("dod_dropmanager_deaddrop", "3", "item to drop after death", FCVAR_PLUGIN, true, 0.0, true, 30.0));
    AddConVar(ConfigFile,    ValueType_String, CreateConVar("dod_dropmanager_config",   "", "Load a custom config for DropManager (without .cfg!)", FCVAR_PLUGIN));
}

// Регистрирует/добавляет ConVar
AddConVar(conVar, ValueType:type, Handle:conVarHandle)
{
    GetConVar[conVar][ConVarHandle] = conVarHandle;
    GetConVar[conVar][Type] = type;

    // String не поддерживается
    if (type != ValueType_String)
    {
        // Обновим значения
        UpdateConVarValue(conVar);

        // И будем отслеживать
        HookConVarChange(conVarHandle, OnConVarChange);
    }
}

UpdateConVarValue(conVar)
{
    switch (GetConVar[conVar][Type])
    {
        case ValueType_Bool:  GetConVar[conVar][Value] = GetConVarBool (GetConVar[conVar][ConVarHandle]);
        case ValueType_Int:   GetConVar[conVar][Value] = GetConVarInt  (GetConVar[conVar][ConVarHandle]);
        case ValueType_Float: GetConVar[conVar][Value] = GetConVarFloat(GetConVar[conVar][ConVarHandle]);
    }
}

public OnConVarChange(Handle:conVar, const String:oldValue[], const String:newValue[])
{
    // Цикл через все переменные
    for (new i = 0; i < ConVar_Size; i++)
    {
        if (conVar == GetConVar[i][ConVarHandle])
        {
            UpdateConVarValue(i);

            // Еще можно отдельно для каждой переменной указать свои изменения
            if (i == ItemLifeTime)
            {
                // ex. KillTimer
            }
        }
    }
}

public OnConfigsExecuted()
{
    // String можно заполучить только так
    decl String:cfg[256];
    GetConVarString(GetConVar[ConfigFile][ConVarHandle], cfg, sizeof(cfg));

    if (cfg[0] != '\0')
    {
        AutoExecConfig(false, cfg, NULL_STRING);
    }
}

public Action:MyFunction()
{
    if (GetConVar[AllowHealthkit][Value])
    {
        // bool
    }

    // Таймер со значением float
    CreateTimer(GetConVar[ItemLifeTime][Value], MyTimer, _, TIMER_FLAG_NO_MAPCHANGE);

    switch (GetConVar[DeadDrop][Value])
    {
        // int
    }
}
Схож с предыдущим, но в этом можно запрашивать у одной переменной значения int, float и другие.
Преимущества и недостатки должны быть очевидны.
В пример взят DoD:S ZombieMod
PHP:
enum
{
    ConVar_Enabled,
    ConVar_Zombie_CritHPRefresh,

    ConVar_Size
};

enum ConVar
{
    Handle:ConVarHandle,

    Value_Int,           // Int
    bool:Value_Bool,     // Bool
    Float:Value_Float    // Float
};

new g_ConVars[ConVar_Size][ConVar];

InitConVars()
{
    CreateConVar("sm_zombiemod_version", PLUGIN_VERSION, PLUGIN_NAME, FCVAR_NOTIFY|FCVAR_DONTRECORD);

    AddConVar(ConVar_Enabled,              CreateConVar("sm_zombiemod_enabled",         "1",   "Enable/Disable Zombie Mod."));
    AddConVar(ConVar_Zombie_CritHPRefresh, CreateConVar("sm_zombiemod_crit_hp_refresh", "100", "Amount of health a crit zombie will regenerate on kill."));
}

AddConVar(conVar, Handle:conVarHandle)
{
    g_ConVars[conVar][ConVarHandle] = conVarHandle;

    UpdateConVarValue(conVar);
    HookConVarChange(conVarHandle, OnConVarChange);
}

UpdateConVarValue(conVar)
{
    g_ConVars[conVar][Value_Int] = GetConVarInt(g_ConVars[conVar][ConVarHandle]);
    g_ConVars[conVar][Value_Bool] = GetConVarBool(g_ConVars[conVar][ConVarHandle]);
    g_ConVars[conVar][Value_Float] = GetConVarFloat(g_ConVars[conVar][ConVarHandle]);
}

public OnConVarChange(Handle:conVar, const String:oldValue[], const String:newValue[])
{
    for (new i = 0; i < ConVar_Size; i++)
    {
        if (conVar == g_ConVars[i][ConVarHandle])
        {
            UpdateConVarValue(i);
            ConVarChanged(i);

            break;
        }
    }
}

ConVarChanged(conVar)
{
    switch (conVar)
    {
        case ConVar_Enabled: // Что нужно сделать когда изменилось значение первой переменной?
        {
            // Правильно: бла и бла
        }
    }
}

public Action:MyFunction()
{
    if (g_ConVars[ConVar_Zombie_CritHPRefresh][Value_Float] > 0.0) // FLOAT
    {
        SetEntityHealth(attacker, g_ConVars[ConVar_Zombie_CritHPRefresh][Value_Int]); // INT
    }
}
На первый взгляд может показаться, что стиль Andersso сложноват и запутан, но на самом деле это не так!
Достаточно просто всё скопировать и добавить AddConVar(ИМЯ, тип_значения, CreateConVar ("название", "значение", "описание", "флаг", "лимиты") и запрашивать значение через GetConVar[ИМЯ][Value].
Схож с последними двумя, но есть свои недостатки и преимущества.
Преимущества: Поддержка string и простая регистрация ConVar'ов через .inc файл
Недостатки: необходимость своих callback'ов, из-за которых стиль уступает в скорости по отношению к последним двум.
Я, пожалуй, просто приложу библиотеку и напишу как этим ею нужно пользоваться.
Цитирую Zephyrus'а:
Я сделал эту библиотеку для упрощения и надежности консольных переменных. Чтобы зарегистрировать консольную переменную, достаточно изменить Ваш код на:
PHP:
#include "zephconvars.inc"
new g_cvarPauseAmount;

public OnPluginStart()
{
    g_cvarPauseAmount = RegisterConVar("match_pause_amount", "900", "Maximum amount of pause per match", TYPE_INT);
}
При первом запуске cvars_unlocker.sp запишет все скрытые консольные команды в console.txt и, соответственно, разблокирует их. Используйте sm_cvar <имя> <значение> для активации.
Всем спасибо за внимание!
 

Вложения

Сверху Снизу