- Поддерживаемые игры
-
- CS: Source (OrangeBox)
- CS: Source (v34)
- CS: GO
- Team Fortress 2
- DOD: Source
- L4D 1 & 2
- Half-Life 2: Deathmatch
- Synergy
- Codename Cure
- Black Mesa
Данное ядро было написано для разрешения конфликтов плагинов, изменяющих одно и то же в разной степени
К примеру:
У вас есть 2 плагина, изменяющих скорость, и зомби сервер
Плагин1 устанавливает скорость на 1.5 для зомби через 1 секунду после спавна и сразу после заражения
Плагин2 увеличивает скорость игрока на 0.5 через 1 секунду после спавна
В таком случае у нас 2 исхода установки скорости после спавна:
Нам, как разработчикам, так и серверодержателям, такой расклад не по душе. Именно тут полезно данное ядро
Ввод местную терминологию и немного объяснений:
ВНИМАНИЕ! ВАЖНО!
К примеру:
У вас есть 2 плагина, изменяющих скорость, и зомби сервер
Плагин1 устанавливает скорость на 1.5 для зомби через 1 секунду после спавна и сразу после заражения
Плагин2 увеличивает скорость игрока на 0.5 через 1 секунду после спавна
В таком случае у нас 2 исхода установки скорости после спавна:
- Благоприятный исход. Плагин1 установит скорость раньше Плагина2, Плагин2 добавит к этой скорости свою
- Не благоприятный исход. Плагин2 увеличит скорость, а потом Плагин1 установит свою, нивелируя старания Плагина2
Нам, как разработчикам, так и серверодержателям, такой расклад не по душе. Именно тут полезно данное ядро
Ввод местную терминологию и немного объяснений:
- Эффект - это просчитываемый в реальном времени коэффициент (на него мы умножаем/делим что-либо), для применения его для чего-либо. Состоит из названия и массива множителей
- Множитель - один компонентов эффекта. Представляет собой название множителя и коэффициент, просчитываемый в реальном времени.
- Хук на эффект (хук на множитель) - Функция, вызываемая при просчете коэффициента множителя. Хук ставится на множитель, соответственно, при установке хука необходимо указать и эффект, и множитель.
- Вычисление множителя - выполнение всех его хуков с целью изменения заданного коэффициента (по умолчанию - 1.0)
- Вычисление эффекта - вычисление всех множителей и перемножение их коэффициентов (по умолчанию - 1.0)
- Попросить применить эффект - "попросить" модуль, реализующий эффект, заново вычислить эффект и применить изменения в соответствии с изменениями коэффициента эффекта
- Хук на применение эффекта - функция, вызываемая, когда модуль просит применить эффект
- Модуль-сервер - плагин, реализующий вычисление эффекта и применение изменений (К примеру, напрямую устанавливающий скорость), и отвечающий на просьбу применения эффекта
- Модуль-клиент - плагин, устанавливающий хук на эффект и, в некоторых случаях, просящий применить эффект.
Возьмем нашей целью реализовать плагин, линейно увеличивающий игроку урон в соответствии с уменьшением урона (меньше хп = больше урона)
Во-первых, нужно реализовать хук на эффект (поглядывая в include-файл) и прописать логику:
Идеально: за каждое потерянное хп игрок получает +1% к наносимому урону. Теперь нужно установить хук
Хук можно установить когда и сколько угодно. Вызван при вычислении множителя он будет лишь 1 раз, даже если он будет несколько раз подряд установлен на один и тот же множитель.
Я советую устанавливать хук при OnLibraryAdded, если верить результатам моего теста, эта функция запускается в плагине под каждую библиотеку, зарегистрированную через SourceMod, даже если сам плагин был загружен гораздо позже. Как по мне, это идеальное место:
Наш модуль-клиент готов. Было быстро, не так-ли? Однако, есть нюансы, о них мы поговорим позже, в модуле-клиенте для скорости
Но он будет работать, если будет работать соответствующий модуль-сервер. Давайте же напишем его
Во-первых, нужно реализовать хук на эффект (поглядывая в include-файл) и прописать логику:
C-подобный:
public void ModifyDamage(int client, float &value)
{
if(IsPlayerAlive(client))
{
int val = 100 - GetClientHealth(client);
if(val > 0)
value += float(val);
}
else
value += 1.0;
}
Хук можно установить когда и сколько угодно. Вызван при вычислении множителя он будет лишь 1 раз, даже если он будет несколько раз подряд установлен на один и тот же множитель.
Я советую устанавливать хук при OnLibraryAdded, если верить результатам моего теста, эта функция запускается в плагине под каждую библиотеку, зарегистрированную через SourceMod, даже если сам плагин был загружен гораздо позже. Как по мне, это идеальное место:
C-подобный:
public void OnLibraryAdded(const char[] library)
{
if(!strcmp(library)) ECalc_Hook2("damage", "lesshpmoredmg", ModifyDamage);
}
Но он будет работать, если будет работать соответствующий модуль-сервер. Давайте же напишем его
Чтобы написанный нами модуль-клиент урона работал, нужно написать соответствующий модуль-сервер
Напишем стандартный хук урона, вычислим эффект и умножим его коэффициент на урон
И... Готово! Да, это настолько просто, хотел сказать бы я, но тут есть несколько нюансов:
Модуль-клиент при такой реализации модуля-сервера не сможет определить оружие и тип урона, с которого был нанесён урон, и здесь вступают дополнительные аргументы натива ECalc_Run2:
Мы здесь создаем массив ячеек (any) и передаем.
Будьте внимательны, при работе с any, компилятор работает с ними, как с int'ами. Для работы с ячейкой как с float-типом, нужно обернуть его в view_as:
Этот код увеличит передаваемый модулям-клиентам урон в 2 раза
Если этого не учесть, можно получить довольно неприятные эффекты, как, например, неправильное вычисление количества кредитов
Теперь мы можем переписать наш модуль-клиент, чтобы, например, урон увеличивался только при ударе с ножа:
Напишем стандартный хук урона, вычислим эффект и умножим его коэффициент на урон
C-подобный:
public void OnClientPutInServer(int client)
{
SDKHookEx(client, SDKHook_OnTakeDamage, OnEntityTakeDamage)
}
public Action OnEntityTakeDamage(int victim, int& attacker, int& inflictor, float& damage, int& damagetype, int& weapon, float damageForce[3], float damagePosition[3], int damagecustom)
{
if(effect != -1 && (0 < attacker <= MaxClients || 0 < victim <= MaxClients))
{
damage *= ECalc_Run2(attacker, "damage");
return Plugin_Changed
}
return Plugin_Continue
}
Модуль-клиент при такой реализации модуля-сервера не сможет определить оружие и тип урона, с которого был нанесён урон, и здесь вступают дополнительные аргументы натива ECalc_Run2:
Мы здесь создаем массив ячеек (any) и передаем.
C-подобный:
any dmginfo[5];
dmginfo[1] = inflictor;
dmginfo[2] = damage;
dmginfo[3] = damagetype;
dmginfo[4] = weapon;
damage *= ECalc_Run2(attacker, "damage", dmginfo, sizeof dmginfo);
Этот код увеличит передаваемый модулям-клиентам урон в 2 раза
C-подобный:
view_as<float>(dmginfo[2]) *= 2.0
Теперь мы можем переписать наш модуль-клиент, чтобы, например, урон увеличивался только при ударе с ножа:
C-подобный:
public void ModifyDamage(int client, float &value, const char[] effect, any[] data, int size)
{
if(size < 5) return;
if(IsPlayerAlive(client))
{
char sBuffer[8];
int weapon = data[4];
if(!IsValidEntity(weapon)) return;
GetEntityNetClass(weapon, sBuffer, sizeof sBuffer);
if(strcmp(sBuffer, "CKnife", 6)) return;
int val = 100 - GetClientHealth(client);
if(val > 0)
value += float(val);
}
}
Данный раздел я прошу прочитать внимательно, т.к. в будущем на основе этой концепции будет введено кеширование коэффициентов эффектов, чтобы не вычислять эффекты при каждом чихе. Поехали.
В пример мы привели изменение урона, и даже написали модуль-клиент и модуль-сервер, но как быть, если модуль-клиент изменяет скорость и хочет её изменить прямо сейчас?
Для этого есть концепция просьбы об применении эффекта
Для её реализации требуется реализовать новый модуль-сервер для скорости и установить хук на применение эффекта
И, допустим, модуль-клиент, изменяющий скорость каждые 5 секунд:
В пример мы привели изменение урона, и даже написали модуль-клиент и модуль-сервер, но как быть, если модуль-клиент изменяет скорость и хочет её изменить прямо сейчас?
Для этого есть концепция просьбы об применении эффекта
Для её реализации требуется реализовать новый модуль-сервер для скорости и установить хук на применение эффекта
C-подобный:
public void OnPluginStart()
{
HookEvent("player_spawn", EventSpawn);
}
// Новый код, гляньте сюда
public void OnLibraryAdded(const char[] lib) {
if(!strcmp(lib, "effectcalc")) {
ECalc_HookApply("speed", ApplyPlayerSpeed) // устанавливаем хук на применение эффекта
}
}
// Хук на применение эффекта
public Action ApplyPlayerSpeed(int client) {
SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", ECalc_Run2(client, "speed")); // применяем эффект
return Plugin_Stop // отвечаем на запрос, в будущем это будет использоваться для кеширования
}
public void EventSpawn(Event event, const char[] name, bool dbc)
{
int client = GetClientOfUserId(event.GetInt("userid"))
if(client) ApplyPlayerSpeed(client);
}
И, допустим, модуль-клиент, изменяющий скорость каждые 5 секунд:
C-подобный:
float g_flValue = 0.0;
public void OnPluginStart()
{
CreateTimer(5.0, ReRandomSpeed, _, TIMER_REPEAT);
}
public Action ReRandomSpeed(Handle timer)
{
g_flValue = GetRandomFloat(-0.5,0.5); // изменение скорости игроков от -50% до +50%
for(int i = 1; i <= MaxClients; ++i)
{
if(IsClientInGame(i)) ECalc_Apply(i, "speed");
}
}
public void OnLibraryAdded(const char[] lib) {
if(!strcmp(lib, "effectcalc")) {
ECalc_Hook2("speed", "randomizer", HookPlayerSpeed)
}
}
public void HookPlayerSpeed(int client, float& value)
{
value += g_flValue;
}
C-подобный:
/***
* Hook callback typeset
***/
typeset ECalc_HookApplyCallBack {
function Action (int client); // return Plugin_Stop on applying
}
typeset ECalc_HookCallBack {
// old
//function void (any[] data, int size, float &value);
//function void (any[] data, int size, float &value, const char[] effect);
// new
function void (int client, float &value, const char[] effect, any[] data, int size);
function void (int client, float &value, const char[] effect);
function void (int client, float &value);
}
/***
* ECalc_Hook Hook calculating effect multiplier
*
* @param effect Name of effect
* @param mult_name Name of multiplier (same multipliers will sum)
* @param func Hook callback (See ECalc_HookCallBack)
* @param remove If true, your hook will be removed
*
* @no return
***/
native void ECalc_Hook2(const char[] effect, const char[] mult_name, ECalc_HookCallBack func, bool remove = false);
/***
* ECalc_Hook Hook calculating effect multiplier
*
* @param effect Name of effect
* @param mult_name Name of multiplier (same multipliers will sum)
* @param func Hook callback (See ECalc_HookCallBack)
* @param remove If true, your hook will be removed
*
* @no return
***/
native void ECalc_HookApply(const char[] effect, ECalc_HookApplyCallBack func, bool remove = false);
/***
* ECalc_Run Calculating effects final multiplier
*
* @param effect Effect ID (not name, for faster work)
* @param data Dynamic array for data
* @param size Count of values in array
*
* @return multiplier Final multiplier of this effect, ready to use
**/
native float ECalc_Run2(int client, const char[] effect, any[] data = NULL_VECTOR, int size = 0);
/***
* ECalc_Recalculate Functions calls to recalculate some effects of player
*
* @param effect client
* @param effect Name of effect
*
* @return true if effect applied
***/
native bool ECalc_Apply(int client, const char[] effect);
[code][/spoiler]
- Требования
-
SourceMod
- Установка
-
Переместить plugins/effectcalc.smx в addons/sourcemod/plugins/
Переместить по желанию плагины из plugins/disabled/в addons/sourcemod/plugins
Ввести в консоль sm plugins refresh