Иконка ресурса

Ядро плагина Effect Calculator 2.1

Нет прав для скачивания
Поддерживаемые игры
  1. CS: Source (OrangeBox)
  2. CS: Source (v34)
  3. CS: GO
  4. Team Fortress 2
  5. DOD: Source
  6. L4D 1 & 2
  7. Half-Life 2: Deathmatch
  8. Synergy
  9. Codename Cure
  10. Black Mesa
Данное ядро было написано для разрешения конфликтов плагинов, изменяющих одно и то же в разной степени
К примеру:
У вас есть 2 плагина, изменяющих скорость, и зомби сервер
Плагин1 устанавливает скорость на 1.5 для зомби через 1 секунду после спавна и сразу после заражения
Плагин2 увеличивает скорость игрока на 0.5 через 1 секунду после спавна
В таком случае у нас 2 исхода установки скорости после спавна:
  1. Благоприятный исход. Плагин1 установит скорость раньше Плагина2, Плагин2 добавит к этой скорости свою
  2. Не благоприятный исход. Плагин2 увеличит скорость, а потом Плагин1 установит свою, нивелируя старания Плагина2
В случае заражения у нас лишь 1 исход: плагин2 не добавит скорости
Нам, как разработчикам, так и серверодержателям, такой расклад не по душе. Именно тут полезно данное ядро

Ввод местную терминологию и немного объяснений:
  • Эффект - это просчитываемый в реальном времени коэффициент (на него мы умножаем/делим что-либо), для применения его для чего-либо. Состоит из названия и массива множителей
  • Множитель - один компонентов эффекта. Представляет собой название множителя и коэффициент, просчитываемый в реальном времени.
  • Хук на эффект (хук на множитель) - Функция, вызываемая при просчете коэффициента множителя. Хук ставится на множитель, соответственно, при установке хука необходимо указать и эффект, и множитель.
  • Вычисление множителя - выполнение всех его хуков с целью изменения заданного коэффициента (по умолчанию - 1.0)
  • Вычисление эффекта - вычисление всех множителей и перемножение их коэффициентов (по умолчанию - 1.0)
  • Попросить применить эффект - "попросить" модуль, реализующий эффект, заново вычислить эффект и применить изменения в соответствии с изменениями коэффициента эффекта
  • Хук на применение эффекта - функция, вызываемая, когда модуль просит применить эффект
  • Модуль-сервер - плагин, реализующий вычисление эффекта и применение изменений (К примеру, напрямую устанавливающий скорость), и отвечающий на просьбу применения эффекта
  • Модуль-клиент - плагин, устанавливающий хук на эффект и, в некоторых случаях, просящий применить эффект.
Вероятно, больше и чаще всего вы будете реализовывать модули-клиенты, так что начнем с них:
Возьмем нашей целью реализовать плагин, линейно увеличивающий игроку урон в соответствии с уменьшением урона (меньше хп = больше урона)
Во-первых, нужно реализовать хук на эффект (поглядывая в 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% к наносимому урону. Теперь нужно установить хук
Хук можно установить когда и сколько угодно. Вызван при вычислении множителя он будет лишь 1 раз, даже если он будет несколько раз подряд установлен на один и тот же множитель.
Я советую устанавливать хук при OnLibraryAdded, если верить результатам моего теста, эта функция запускается в плагине под каждую библиотеку, зарегистрированную через SourceMod, даже если сам плагин был загружен гораздо позже. Как по мне, это идеальное место:
C-подобный:
public void OnLibraryAdded(const char[] library)
{
    if(!strcmp(library))    ECalc_Hook2("damage", "lesshpmoredmg", ModifyDamage);
}
Наш модуль-клиент готов. Было быстро, не так-ли? Однако, есть нюансы, о них мы поговорим позже, в модуле-клиенте для скорости
Но он будет работать, если будет работать соответствующий модуль-сервер. Давайте же напишем его
Чтобы написанный нами модуль-клиент урона работал, нужно написать соответствующий модуль-сервер
Напишем стандартный хук урона, вычислим эффект и умножим его коэффициент на урон
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);
Будьте внимательны, при работе с any, компилятор работает с ними, как с int'ами. Для работы с ячейкой как с float-типом, нужно обернуть его в view_as:
Этот код увеличит передаваемый модулям-клиентам урон в 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);
    }
}
ВНИМАНИЕ! ВАЖНО!
Данный раздел я прошу прочитать внимательно, т.к. в будущем на основе этой концепции будет введено кеширование коэффициентов эффектов, чтобы не вычислять эффекты при каждом чихе. Поехали.

В пример мы привели изменение урона, и даже написали модуль-клиент и модуль-сервер, но как быть, если модуль-клиент изменяет скорость и хочет её изменить прямо сейчас?
Для этого есть концепция просьбы об применении эффекта
Для её реализации требуется реализовать новый модуль-сервер для скорости и установить хук на применение эффекта
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
Автор
inklesspen
Скачивания
95
Просмотры
2,027
Первый выпуск
Обновление
Оценка
5.00 звёзд 1 оценок

Другие ресурсы пользователя inklesspen

Последние обновления

  1. Обновлено описание

    Обновил описание, теперь по нему можно клепать новые модули под новое API
  2. Изменения в API, сломанная совместимость, натив применения эффекта

    СТАРОЕ API СЛОМАНО Модули под версию 1.Х не совместимы с версией 2.Х и наоборот Написать...

Последние отзывы

"alternativeHeadline": "Надоело, что все плагины просчитывают скорость по-своему? Тогда мы идем к вам!"
Сверху