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

Root

AWOL.
Сообщения
76
Реакции
193
Доброго времени суток!
Я думаю что каждый, кто однажды писал плагины для SourceMod, сталкивался с консольными переменнами (ака ConVars).
В "блог" всё сообщение не вместилось, да и русскоязычного перевода я не нашел. Вот так и решил выложить оригинальную статью на родном языке, да еще и мудростью поделиться захотелось. :-D

Первая часть включает в себя вступление, поиск, создание, изменение значений для переменных, флаги, отслеживание изменений, отправка значений переменных клиентам и полезные советы.
Вторая часть посложнее - ориентирована специально для разработчиков плагинов. В сей раздел входят стили для создания переменных, их возможности и недостатки.

Вступление:
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 когда игрок заходит на сервер
}
Здесь можно увидеть яркий пример использования SendConVarValue.


Полезные советы:
  • Чаще используйте 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);
}
При создании происходит получение значения и хук, далее Handle удаляется. Для получения значения используется callback, указанный в HookConVarChange, ведь в его параметрах содержится Handle переменной. Экономит память, особенно при большом числе переменных. (c) KorDen
Автор неизвестен. Но стиль имеет место быть и достоин внимания.
Полезен в том случае когда много однотипных переменных (полезно для плагина статистики).
Ничем особо не отличается от оригинальных стилей.
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")])); // Дадим убийце Х очков в зависимости от оружия.
}
Мой любимый стиль. Придуман одним талантливым шведом, который частенько мне помогал осваивать SP :)
Преимущества:
  • автоматизация процесса отслеживания переменных (хук).
  • безошибочность в извлечении типа значения (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);
}
Вы так же можете выставить кэш для переменных
PHP:
g_eCvars[g_cvarPauseAmount][aCache] // кэш
g_eCvars[g_cvarPauseAmount][szCache] // string
Zeph утверждает, что его метод лучше потому, что он использует только одну функцию для создания переменных (и при необходимости можно использовать еще и callback). Оригинальное сообщение.

При первом запуске cvars_unlocker.sp запишет все скрытые консольные команды в console.txt и, соответственно, разблокирует их. Используйте sm_cvar <имя> <значение> для активации.

На этом пока всё!
Потом еще чего-нибудь добавлю, а то чувствую что я что-то забыл. :-D
 

Вложения

  • cvar_unlocker.sp
    950 байт · Просмотры: 94
  • zephconvars.inc.sp
    1.8 КБ · Просмотры: 52
Последнее редактирование:

KorDen

Atra esterní ono thelduin!
Сообщения
2,185
Реакции
1,418
>KyleS
The best :D
В достоинства еще следует добавить отсутствие глобальных Handle, которых в больших плагинах получается огромное количество, и как следствие уменьшение необходимого количества памяти - при сотне переменных и прочем подобном может начать ощущаться :)

И да, думаю стоит к каждому стилю кроме кода написать описание принципа, не всегда можно код понять если способ до этого не знал

И да, "оригинальный 2" лучше так:
PHP:
public OnPluginStart() 
{ 
    somecvar = CreateConVar("sm_somecvar", "6", "some cvar"); 
    HookConVarChange(somecvar, OnConVarChange); 
    somevalue = GetConVarInt(somecvar);
} 
public OnConVarChange(Handle:convar, const String:oldValue[], const String:newValue[]) 
{ 
    somevalue = GetConVarInt(somecvar); // Проделаем тоже самое когда значение изменилось, например, через консоль 
}
т.е. не нужно OnConfigsExecuted брать, если оно в конфигах изменилось, то сработает OnConVarChange, а для late load берем в OnPluginStart
 
  • Мне нравится
Реакции: R1KO

R1KO

fuck society
Команда форума
Сообщения
9,016
Реакции
6,897
Очень полезная статья, а главное на русском. Большое спасибо!
 

KorDen

Atra esterní ono thelduin!
Сообщения
2,185
Реакции
1,418
Думаю стоит добавить следующие описания по способам:

Оригинальный 1:
При редком использовании значений переменных это значение можно брать в момент использования, не сохраняя в переменную, либо сохраняя в локальную переменную

Оригинальный 2:
Значение сохраняется в переменной (например somevalue) и изменяется при изменении переменной. Полезно при частом использовании значения

KyleS:
Не создается глобальный Handle - в этом отличие способа от остальных. При создании происходит получение значения и хук, далее Handle удаляется. Для получения значения используется callback, указанный в HookConVarChange, ведь в его параметрах содержится Handle переменной. Экономит память, особенно при большом числе переменных

Кстати в примере бред:
PHP:
public OnEnabledChange(Handle:convar, const String:oldValue[], const String:newValue[]) 
{ 
    switch(GetConVarBool(convar)) 
    { 
        case false: g_bEnabled = false; // Изменения нужно задавать вручную каждый раз 
        case true: g_bEnabled = true; 
    }
}
зачем? ведь проще
PHP:
public OnEnabledChange(Handle:convar, const String:oldValue[], const String:newValue[]) 
    g_bEnabled=GetConVarBool(convar);

Unknown:
//Описывать сложно, способ честно говоря дурацкий, можно сделать гораздо оптимизированнее,чем 4 раза писать GivePoints...
Например как у AFK manager by Rothgar - дефайнами сделаны номера переменных и тогда вместо свича можно просто использовать одну функцию GivePoints(attacker, GetConVarInt(Points[weapon])), а weapon Заранее приравнять одному из дефайнов. Гораздо меньше кода

В остальных пока до конца не разобрался чтобы понять фишку...
 

Root

AWOL.
Сообщения
76
Реакции
193
KorDen, true dat.
Завтра обновлю инфу о стилях. Утро вечера мудренее.

Добавлено через 16 часов 56 минут
Обновил первый пост (спасибо KorDen), добавил cvar_unlocker.sp и забытый zephconvars.inc (файл с расшиением .inc прикреплять нельзя, так что вы просто переименуйте .inc.sp в .inc)
 
Последнее редактирование:
  • Мне нравится
Реакции: R1KO

R1KO

fuck society
Команда форума
Сообщения
9,016
Реакции
6,897
Закрепил тему.

В KyleS

CloseHandle(hRegister); // Нам ведь не нужны утечки памяти
как я понял хандл можно не закрывать.
ConVarsType: ConVar
Closeable: No
Cloneable: No
API: Core
Found in: console.inc


ConVar Handles are primarily used for getting and setting a console variable's value. They cannot be cloned nor deleted since they exist until SourceMod is shut down.
 

AlmazON

Не путать с самим yand3xmail
Сообщения
5,127
Реакции
2,338
как я понял хандл можно не закрывать
Он итак не закрывается, по сути (давно проверял):
PHP:
PrintToServer("Handle convar's %s!", CloseHandle(hRegister) ? "close successful":"not closeable");
Но, это не считается ошибкой - можно закрывать (мало ли, к чему ещё Handle будет привязан), а можно и нет.
Я, например, иногда отдельно использую этот приём с глобальным Handle для убийства таймера, чтобы не проверять его постоянно на INVALID_HANDLE.
 

gibs

Фитиль народного волненья
Сообщения
722
Реакции
267
Я, например, иногда отдельно использую этот приём с глобальным Handle для убийства таймера, чтобы не проверять его постоянно на INVALID_HANDLE.

А разве таймер не логичней убивать через KillTimer()?
Вообще данная статья с приходом нового синтаксиса является просто бесполезной. Тут просто показывается как красивенько можно организовать тело плагина с использованием старого синтаксиса, не более.
 

Dragokas

Меценат
Сообщения
178
Реакции
143
Спасибо за полезный материал.
Интересно, почему в некоторых статьях так много внимания уделяется экономии памяти (оптимизация кода в виде кол-ва одновременно удерживаемых в памяти дескрипторов, открытых единожды, например, этих же кваров) в целом, и утечек (не закрытых хендлов) в частности? Ведь свободного обьема ОЗУ достаточно, и это по идее не должно отразиться на производительность, т.к. здесь нет сборщика мусора. И наоборот, попытка закрыть хендл только ради того, чтобы не удерживать его на время работы плагина, затратит доп. циклы процессора.
 

Kruzya

Raspberry Pi 4
Команда форума
Меценат
Сообщения
10,550
Реакции
8,726
Потому что есть некоторый предел на кол-во одновременно открытых хендлов, и при достижении оного предела - SourceMod выгружает плагин принудительно.
 
Сверху