Урок Фильтры. Меняем правила игры.

20 Дек 2016
892
170
Что и зачем?
По своей сути фильтры очень похожи на слушателей. Но их преимуществом является то, что они еще и позволяют влиять непосредственно на происходящее событие. С их помощью можно очень просто решать некоторые задачи, которые не поддаются другим методам, или же вызывает большие сложности и кривые костыли.
Как правило, эта технология используется для изменения уже существующих Валвовских игровых правил, не имеющих настроек. При написании своей логики большая часть фильтров теряет свою актуальность.


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

Теперь чуть подробнее...
Включение и выключение.
Фильтров существует несколько, и все они обрабатывают разные события. Для каждого фильтра используется своя уникальная функция установки (изначально все фильтры отключены). Обычно их вызывают в InitGameMode. Все эти функции будут рассмотрены далее, но в общем случае они имеют следующий вид:
Код:
GameModeEntity:Set...Filter(handle hFunction, handle hContext)
hFunction - ссылка на пользовательскую (вашу) функцию, которая будет обрабатывать событие. Сейчас их модно задавать с помощью функции Dynamic_Wrap(...). Она в свою очередь также имеет два параметра: ссылка на класс, и название функции (строка) этого класса.
hContext. Про контекст в данной теме рассказывать смысла нет. Просто ставьте там self.
Как можно заметить, функция эта не глобальная, а применяется к сущности гейммода (без понятия, как нормально перевести GameModeEntity).
Код:
_G.GM = class({})
_G.Filter = class({}) --[[ Часто первым параметром для Dynamic_Wrap задают сам гейммод. Я же создаю отдельный класс, предназначенный конкретно для фильтров. ]]
function Activate()
    GM:InitGameMode()
end
function GM:InitGameMode()
    GameRules:GetGameModeEntity():SetDamageFilter( Dynamic_Wrap( Filter, "DamageFilter" ), self ) --[[ SetDamageFilter, из названия можно догадаться, что отслеживаемое событие - получение урона. Как видите, я установил на вызов функцию DamageFilter класса Filter. ]]
end
function Filter:DamageFilter( kv ) --[[ Эта функция будет вызываться каждый раз, когда происходит событие, т.е. каждый раз при получении урона. Тут и будет происходить обработка. kv - таблица с описанием события. ]]
    print("Kto-to ispitivaet bol...")
    return true --[[ Да, функция еще и что-то возвращать должна. Позднее узнаем, что именно. ]]
end
Для каждого фильтра также предусмотрена отдельная функция отключения. Там даже аргументов никаких нет; после использования ваша функция просто перестанет вызываться и что-там фильтровать. Общий вид:
Код:
GameModeEntity:Clear...Filter()
Сама фильтрация.
На вход функции подается таблица, которая содержит информацию о событии. Для разных фильтров таблицы имеют разные ключи (поля). С помощью функции PrintTable вы всегда можете узнать эти ключи, но дальше я все равно их всех приведу и опишу. Изменяя эти поля вы и влияете на событие. То есть используются установленные вами значения. Но менять нужно поля именно поданной вам таблицы!!!
Код:
function Filter:DamageFilter( kv )
    local damage = kv.damage
    damage = 228
    return true
end
Код:
function Filter:DamageFilter( kv )
    kv.damage = 228
    return true
end
Значения этой таблицы я логически разделил на два типа: константы и переменные.
С переменными все как и было описано выше. Меняете значение - изменение отражается и в самом событии.
Константы же менять нельзя. Точнее менять то их можно, но на событие это никак не повлияет. Они просто используются как дополнительная информация. Удивительный факт: ключи констант всегда кончаются на const.
Часто константным параметром таблицы является индекс сущности. Саму сущность по индексу получить очень просто:
Код:
entity = EntIndexToHScript(index)
Также ваша функция должна возвращать булево значение (true/false). В разных фильтрах по разному, но true всегда подтверждает фильтрацию, а false - либо отменяет фильтрацию, либо отменяет само событие. Если функция ничего не возвращает, то вернется nil, который расценивается как false.

Стоит отметить, что фильтры обычно выполняются в самый последний момент перед событием, после всех остальных обработок.


Про каждый по отдельности.
Так как фильтров существует не очень много, я детально расскажу про каждый из них.
Что ето
Вызывается при любом получении урона, позволяя его модифицировать. Вызывается после учета всех видов сопротивления и прочей херни, влияющей на наносимый/получаемый урон. Т.е если по герою в бкб нанесли (попытались) магический урон, а в фильтре вы его изменили, скажем, на 322, то герой в бкб получит ровно 322 урона, так как этот урон не будет учитывать магрезист, даже несмотря на то, что это урон магический.
Также вызывается после всех других эвентов, вызываемых уроном. Даже после того, как были отображены циферки, показывающие нанесенный урон, и отключены предметы, которые уроном отключаются. В этом заключается огромный недостаток данного фильтра.

Функции установки и отключения
Код:
SetDamageFilter(handle hFunction, handle hContext)
ClearDamageFilter()
Если вернуть false
Отменяет событие. Урон не будет нанесен (но циферки и прочий кайф все-равно будут).
Параметры события
Код:
damage --[[ Сам урон ]]
Код:
damagetype_const --[[ Тип урона. Все типы урона приведены ниже ]]
entindex_attacker_const --[[ Индекс обидчика ]]
entindex_victim_const --[[ Индекс жертвы ]]
entindex_inflictor_const --[[ Может отсутствовать. Индекс способности, которой был нанесен урон (способности это тоже сущности). ]]

Код:
DAMAGE_TYPE_NONE = 0 --[[ Абсолютный. Пробивает даже крест дазла и ничем не снижается. ]]
DAMAGE_TYPE_PHYSICAL = 1 --[[ Физический ]]
DAMAGE_TYPE_MAGICAL = 2 --[[ Магический ]]
DAMAGE_TYPE_PURE = 4 --[[ Чистый ]]
DAMAGE_TYPE_ALL = 7 --[[ Смешанный ]]
Пример
Хочу, чтобы пудж в инвизе не получал урона от вони.
Код:
_G.GM = class({})
_G.Filter = class({})
function Activate()
    GM:InitGameMode()
end
function GM:InitGameMode()
    GameRules:GetGameModeEntity():SetDamageFilter( Dynamic_Wrap( Filter, "DamageFilter" ), self )
end

function Filter:DamageFilter( kv )
    local ability
    local attacker
    local victim
    if kv.entindex_inflictor_const then --[[ так можно проверить, существует ли вообще такая переменная, или же там nil ]]
        ability = EntIndexToHScript(kv.entindex_inflictor_const)
    end
    attacker = EntIndexToHScript(kv.entindex_attacker_const)
    victim = EntIndexToHScript(kv.entindex_victim_const)
    if ability then
        if ability:GetAbilityName() == "pudge_rot" and victim == attacker then
            if victim:IsInvisible() then
                return false
            end
        end
    end
    return true
end
Что ето
По этому фильтру уже есть отдельный гайд. Но меня это не останавливает. Фильтр позволяет отслеживать и модифицировать все действия (приказы) игрока, которые касаются геймплея (Между тем, как вы нажали кнопочку, и тем, что последовало за нажатием этой кнопочки). Очень разнообразный фильтр.
Есть, правда, некоторые приказы, которые этим фильтром не отслеживаются. Я знаю, что прокачка талланта сюда точно не относится, возможно есть и другие действия, не охватываемые этим фильтром.
Визуальные эффекты приказов в любом случае отображаются для отданного приказа.
Функции установки и отключения
Код:
SetExecuteOrderFilter(handle hFunction, handle hContext)
ClearExecuteOrderFilter()
Если вернуть false
Отменяет событие. Приказ будет проигнорирован. Игрок будет повержен в шок и в припадке разобьет свою клавиатуру.
Параметры события
Код:
entindex_ability --[[ Здесь будет индекс способности, если приказ как-то с ней связан. В противном случае 0. ]]
entindex_target --[[ Индекс цели способности или иного действия если таковая имеется. Если не имеется, будет 0. ]]
order_type --[[ Тип приказа. Список всех типов представлен ниже. ]]
position_x
position_y
position_z  --[[ Координаты, если действие направлено на точку. Иначе по нулям. ]]
queue --[[ Обычно ноль. 1, если действие находится в очереди (прожато через шифт). В случае с абилками, событие будет вызывается два раза (если через шифт) - первый раз в момент приказа, а второй - перед кастом, но queue уже будет 0 ]]
units --[[ Массив индексов юнитов, которым был отдан приказ. Обратите внимание: индексация массива обосралась, поэтому она не только начинается с нуля, но и представляет из себя строковые ключи с написанным числом. То есть получается это даже не массив, а таблица (смотрите пример, если не понятно). ]]
Код:
issuer_player_id_const --[[ id игрока (не героя!!1!) ]]
sequence_number_const --[[ Порядковый номер приказа. Начинается с нуля. ]]

Код:
DOTA_UNIT_ORDER_NONE = 0
DOTA_UNIT_ORDER_MOVE_TO_POSITION = 1 --[[ Идти ]]
DOTA_UNIT_ORDER_MOVE_TO_TARGET = 2 --[[ Преследовать юнита ]]
DOTA_UNIT_ORDER_ATTACK_MOVE = 3 --[[ Идти, атакуя всех на пути ]]
DOTA_UNIT_ORDER_ATTACK_TARGET = 4 --[[ Атаковать ]]
DOTA_UNIT_ORDER_CAST_POSITION = 5 --[[ Использовать способность направленную на точку ]]
DOTA_UNIT_ORDER_CAST_TARGET = 6 --[[ Использовать способность направленнную на юнита ]]
DOTA_UNIT_ORDER_CAST_TARGET_TREE = 7 --[[ Использовать способность направленную на дерево ]]
DOTA_UNIT_ORDER_CAST_NO_TARGET = 8 --[[ Использовать ненаправленную способность ]]
DOTA_UNIT_ORDER_CAST_TOGGLE = 9 --[[ Использовать переключаемую способность ]]
DOTA_UNIT_ORDER_HOLD_POSITION = 10 --[[ Отмена действия ]]
DOTA_UNIT_ORDER_TRAIN_ABILITY = 11 --[[ Изучить способность ]]
DOTA_UNIT_ORDER_DROP_ITEM = 12 --[[ Убрать предмет из инвентаря на землю ]]
DOTA_UNIT_ORDER_GIVE_ITEM = 13 --[[ Передать предмет ]]
DOTA_UNIT_ORDER_PICKUP_ITEM = 14 --[[ Поднять предмет ]]
DOTA_UNIT_ORDER_PICKUP_RUNE = 15 --[[ Подобрать руну ]]
DOTA_UNIT_ORDER_PURCHASE_ITEM = 16 --[[ Купить предмет ]]
DOTA_UNIT_ORDER_SELL_ITEM = 17 --[[ Продать предмет ]]
DOTA_UNIT_ORDER_DISASSEMBLE_ITEM = 18 --[[ Разобрать предмет ]]
DOTA_UNIT_ORDER_MOVE_ITEM = 19 --[[ Перемещение предмета по инвентарю. Тайник считается частью инвентаря. ]]
DOTA_UNIT_ORDER_CAST_TOGGLE_AUTO = 20 --[[ Переключить способность-автоатаку ]]
DOTA_UNIT_ORDER_STOP = 21
DOTA_UNIT_ORDER_TAUNT = 22 --[[ Использовать насмешку ]]
DOTA_UNIT_ORDER_BUYBACK = 23 --[[ Выкупиться ]]
DOTA_UNIT_ORDER_GLYPH = 24 --[[ Укрепить строяния ]]
DOTA_UNIT_ORDER_EJECT_ITEM_FROM_STASH = 25 --[[ Выложить предмет из тайника ]]
DOTA_UNIT_ORDER_CAST_RUNE = 26 --[[ Использовать способность, направленную на руну. Не важно, что в доте таких нет. ]]
DOTA_UNIT_ORDER_PING_ABILITY = 27 --[[ Альт-клик по способности или предмету ]]
DOTA_UNIT_ORDER_MOVE_TO_DIRECTION = 28 --[[ Есть в доте такая функция "Идти в направлении". Герой будет идти прямо к курсору и упрется в первое препятствие. Включается в настройках управления. ]]
DOTA_UNIT_ORDER_PATROL = 29 --[[ Еще менее известная функция "Патрулировать". Герой будет патрулировать. ]]
DOTA_UNIT_ORDER_VECTOR_TARGET_POSITION = 30 --[[ Использовать способность, как первая у Пангольера ]]
DOTA_UNIT_ORDER_RADAR = 31 --[[ Сканировать территорию ]]
DOTA_UNIT_ORDER_SET_ITEM_COMBINE_LOCK = 32 --[[ Отключить сборку предмета ]]
DOTA_UNIT_ORDER_CONTINUE = 33
DOTA_UNIT_ORDER_VECTOR_TARGET_CANCELED = 34
DOTA_UNIT_ORDER_CAST_RIVER_PAINT = 35
DOTA_UNIT_ORDER_PREGAME_ADJUST_ITEM_ASSIGNMENT = 36 --[[ Распределение предметов на стадии планирования между игроками (чужие танго, варды..) ]]
Пример
Теперь хочу, чтобы пудж с рапирой мог только автоатачить и вообще вел себя крайне агрессивно, нападая на всех врагов неподалеку.
Код:
_G.GM = class({})
_G.Filter = class({})
function Activate()
    GM:InitGameMode()
end
function GM:InitGameMode()
    GameRules:GetGameModeEntity():SetExecuteOrderFilter( Dynamic_Wrap( Filter, "OrderFilter" ), self )
end

function Filter:OrderFilter( kv )
    local hero = PlayerResource:GetSelectedHeroEntity(kv.issuer_player_id_const)
    if hero and hero:GetUnitName() == "npc_dota_hero_pudge" and hero:HasItemInInventory("item_rapier") then
        if kv.units["0"] == hero:GetEntityIndex() then --[[ Обратите внимание, как выполнено обращение к элементу массива. ]]
            if kv.order_type == DOTA_UNIT_ORDER_CAST_POSITION then
                return false
            end
            if kv.order_type == DOTA_UNIT_ORDER_CAST_TARGET then
                return false
            end
            if kv.order_type == DOTA_UNIT_ORDER_CAST_TARGET_TREE then
                return false
            end
            if kv.order_type == DOTA_UNIT_ORDER_CAST_NO_TARGET then
                return false
            end
            if kv.order_type == DOTA_UNIT_ORDER_CAST_TOGGLE then
                return false
            end
            if kv.order_type == DOTA_UNIT_ORDER_HOLD_POSITION then
                return false
            end
            if kv.order_type == DOTA_UNIT_ORDER_MOVE_TO_POSITION or kv.order_type == DOTA_UNIT_ORDER_MOVE_TO_TARGET then
                kv.order_type = DOTA_UNIT_ORDER_ATTACK_MOVE
                return true
            end
        end
    end
    return true
end
Что ето
Вызывается, когда происходит обращение к какому либо значению из блока способности "AbilitySpecial" на дд. Также срабатывает и для оригинальных способностей, но с ними будьте внимательны; некоторые способности берут и запоминают эти значения в момент прокачки, а не во время действия способности.
Не работает для луа функций GetLevelSpecialValueFor и GetSpecialValueFor, но это и не нужно, так как на луа все проверки удобнее проводить сразу внутри кода способности, а не в отдельном фильтре.

Функции установки и отключения
Код:
SetAbilityTuningValueFilter(handle hFunction, handle hContext)
ClearAbilityTuningValueFilter()
Если вернуть false
Отменяет лишь фильтрацию. Будут использованы значения до обработки.
Параметры события
Код:
value --[[ Само значение из "AbilitySpecial" ]]
Код:
value_name_const --[[ Имя параметра, к которому обратились. Значению value соответствует именно ему. ]]
entindex_ability_const --[[ Индекс способности ]]
entindex_caster_const --[[ Индекс владельца способности ]]
Пример
Сделаем пуджу талант на скорость хука. На длину никак(9((.
Код:
_G.GM = class({})
_G.Filter = class({})
function Activate()
    GM:InitGameMode()
end
function GM:InitGameMode()
    GameRules:GetGameModeEntity():SetAbilityTuningValueFilter( Dynamic_Wrap( Filter, "AbilityValueFilter" ), self )
end

function Filter:AbilityValueFilter( kv )
    local ability = EntIndexToHScript( kv.entindex_ability_const )
    local unit = EntIndexToHScript( kv.entindex_caster_const )
    if ability:GetAbilityName() == "pudge_meat_hook" and kv.value_name_const == "hook_speed" then
        local talent = unit:FindAbilityByName( "special_bonus_unique_pudge_hook_speed" )
        if talent and talent:IsTrained() then
            kv.value = kv.value + talent:GetSpecialValueFor("value")
            return true
        end
    end
end
Примечание: не путайте kv.value и value, которое я получаю из таланта. Это совершенно разные независимые значения.
Осталось только создать способность special_bonus_unique_pudge_hook_speed, наследовать от special_bonus_undefined, установить тип DOTA_ABILITY_TYPE_ATTRIBUTES, прописать ей нужное значение в value в AbilitySpecial и добавить пуджу вместо какого-нибудь таланта. Но, к сожалению, изменения в тултипах способности при вкачке такого таланта не будут отображаться.
Что ето
Позволяет контролировать, какая руна появляется. Просто и удобно.
Распространяется только на 2 спавнера на реке. Не влияет на баунти руны.
Срабатывает даже при спавне руны чит/консольной командой.
Функции установки и отключения
Код:
SetRuneSpawnFilter(handle hFunction, handle hContext)
ClearRuneSpawnFilter()
Если вернуть false
Отменяет событие. Руна не появляется.
Параметры события
Код:
rune_type --[[ Тип руны. Список типов чуть ниже. ]]
Код:
spawner_entindex_const --[[ Индекс СПАВНЕРА, на котором руна появилась. ]]

Код:
DOTA_RUNE_DOUBLEDAMAGE = 0 --[[ Двойной урон ]]
DOTA_RUNE_HASTE = 1 --[[ Ускорение ]]
DOTA_RUNE_ILLUSION = 2 --[[ Руна иллюзий ]]
DOTA_RUNE_INVISIBILITY = 3 --[[ Невидимость ]]
DOTA_RUNE_REGENERATION = 4 --[[ Регенерация ]]
DOTA_RUNE_BOUNTY = 5 --[[ Руна богатства ]]
DOTA_RUNE_ARCANE = 6 --[[ Руна волшебства ]]
DOTA_RUNE_COUNT = 7 --[[ Пустышка. Использовалась, когда на реке появлялась только одна руна в случайном месте. ]]
DOTA_RUNE_INVALID = -1 --[[ Руна инвалидности ]]
Пример
Нужно мне, чтобы в радиусе 1000 от алхимика всегда появлялись баунти.
Код:
_G.GM = class({})
_G.Filter = class({})
function Activate()
    GM:InitGameMode()
end
function GM:InitGameMode()
    GameRules:GetGameModeEntity():SetRuneSpawnFilter( Dynamic_Wrap( Filter, "RuneFilter" ), self )
end

function Filter:RuneFilter( kv )
    local spawner = EntIndexToHScript(kv.spawner_entindex_const)
    local units = FindUnitsInRadius( 0, spawner:GetOrigin(), spawner, 1000, DOTA_UNIT_TARGET_TEAM_BOTH, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_INVULNERABLE + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, 0, false )
    for _, unit in pairs(units) do
        if unit:HasAbility("alchemist_goblins_greed") then
            kv.rune_type = DOTA_RUNE_BOUNTY
        end
    end
    return true
end
Что ето
Срабатывает, когда герой подбирает баунти руну. Позволяет изменить получаемые при этом золото и опыт.
Довольно приятно то, что фильтр срабатывает до появления золотых цифр и анимации голды, а значит влияет и на них.
Функции установки и отключения
Код:
SetBountyRunePickupFilter(handle hFunction, handle hContext)
ClearBountyRunePickupFilter()
Если вернуть false
Отменяет фильтрацию. Герой получит стандартную награду.
Параметры события
Код:
gold_bounty --[[ Получаемое золото ]]
xp_bounty --[[ Сейчас не работает. Получаемый опыт ]]
Код:
player_id_const --[[ id игрока (не героя!!1!) ]]
Пример
На этот раз хочу, чтобы герои с мидасом за поднятие руны получали удвоенное золото и получали опыт.
Код:
_G.GM = class({})
_G.Filter = class({})
function Activate()
    GM:InitGameMode()
end
function GM:InitGameMode()
    GameRules:GetGameModeEntity():SetBountyRunePickupFilter( Dynamic_Wrap( Filter, "BountyFilter" ), self )
end

function Filter:BountyFilter( kv )
    --PrintTable(kv)
    local hero = PlayerResource:GetSelectedHeroEntity(kv.player_id_const)
    local midas
    for i = 0, 5 do
        local item = hero:GetItemInSlot(i)
        if item and item:GetAbilityName() == "item_hand_of_midas" then
            midas = item
        end
    end
    if midas then
        kv.gold_bounty = kv.gold_bounty * 2
        hero:AddExperience( midas:GetSpecialValueFor("bonus_gold"), 0, true, true )
        return true
    end
end
Что ето
Срабатывает, когда герой получает опыт.
Функции установки и отключения
Код:
SetModifyExperienceFilter(handle hFunction, handle hContext)
ClearModifyExperienceFilter()
Если вернуть false
Отменяет фильтрацию. Герой получит стандартную награду.
Параметры события
Код:
experience --[[ полученный опыт ]]
Код:
reason_const --[[ Целое число. Соответствует причине. Список возможных причин чуть ниже. ]]
player_id_const --[[ id игрока (не героя!!1!) ]]

Код:
1 --[[ Смерть героя ]]
2 --[[ Смерть крипа ]]
3 --[[ Смерть Рошана ]]
0 --[[ Все остальное ]]
Пример
Ускоряем получение опыта в 2 раза. Рошан не дает опыта, а повышает уровень на 2
Код:
_G.GM = class({})
_G.Filter = class({})
function Activate()
    GM:InitGameMode()
end
function GM:InitGameMode()
    GameRules:GetGameModeEntity():SetModifyExperienceFilter( Dynamic_Wrap( Filter, "ExperienceFilter" ), self )
end

function Filter:ExperienceFilter( kv )
    kv.experience = kv.experience * 2
    if kv.reason_const == 3 then
        kv.experience = 0
        PlayerResource:GetSelectedHeroEntity(kv.player_id_const):HeroLevelUp(false)
        PlayerResource:GetSelectedHeroEntity(kv.player_id_const):HeroLevelUp(true)
    end
    return true
end
Что ето
Срабатывает, когда герой получает золото за убийство существ. Не срабатывает из всех остальных источников получения золота.
Фильтр срабатывает перед анимацией получения золота, так что визуальные изменения отобразятся.
Функции установки и отключения
Код:
SetModifyGoldFilter(handle hFunction, handle hContext)
ClearModifyGoldFilter()
Если вернуть false
Отменяет событие. Герой не получит награду.
Параметры события
Код:
gold --[[ Полученное золото]]
reliable --[[ Надежное/ненадежное. (0/1) ]]
Код:
reason_const --[[ Целое число. Соответствует причине. Так как фильтр срабатывает не для всех возможных причин, ниже было решено выписать только те, которые сюда относятся. ]]
player_id_const --[[ id игрока (не героя!!1!) ]]

Код:
11 --[[ Разрушение постройки ]]
12 --[[ Смерть героя ]]
13 --[[ Смерть крипа ]]
14 --[[ Смерть Рошана ]]
15 --[[ Убийство курьера ]]
Пример
Афк фарм теперь будет в 10 раз профитнее.
Код:
_G.GM = class({})
_G.Filter = class({})
function Activate()
    GM:InitGameMode()
end
function GM:InitGameMode()
    GameRules:GetGameModeEntity():SetModifyGoldFilter( Dynamic_Wrap( Filter, "ExperienceFilter" ), self )
end

function Filter:GoldFilter( kv )
    if kv.reason_const == 13 then
        kv.gold = kv.gold * 5
        kv.reliable = 1
    end
    return true
end
Что ето
Очень сильно похоже на фильтр урона. Вызывается при любом восстановлении здоровья после всех циферок, отображающих это восстановление.
Регенерацию можно разделить на два типа: естественную и моментальную. С моментальной, думаю, все понятно. Естественная же срабатывает раз в 0.1 секунды или реже (если ежесекундная регенерация меньше 10). Собственно в таблицу будет передано количество здоровья, восстановленного естественной регенерацией за последнюю 0.1 секунды. Хиллер и способность-источник при этом отсутствуют.
Фильтр не вызывается, если в момент лечения у юнита было фуллхп.
Функции установки и отключения
Код:
SetHealingFilter(handle hFunction, handle hContext)
ClearHealingFilter()
Если вернуть false
Отменяет лечение.
Параметры события
Код:
heal --[[ Количество восстановленного здоровья ]]
Код:
entindex_target_const --[[ Индекс юнита, которому восстанавливается здоровье ]]
entindex_healer_const --[[ Может отсутствовать. Индекс хиллера. (Не указывается, даже если хиллер наложил модификатор, увеличивающий естественную регенерацию.) ]]
entindex_inflictor_const --[[ Может отсутствовать. Индекс способности, вызвавшей мгновенное лечение. ]]
Пример
Хочу, чтобы все лечение на фонтане утраивалось, а хилящие способности не имели перезарядки.
Код:
_G.GM = class({})
_G.Filter = class({})
function Activate()
    GM:InitGameMode()
end
function GM:InitGameMode()
    GameRules:GetGameModeEntity():SetHealingFilter( Dynamic_Wrap( Filter, "HealingFilter" ), self )
end

function Filter:HealingFilter( kv )
    local unit = EntIndexToHScript( kv.entindex_target_const )
    if unit:HasModifier("modifier_fountain_aura_buff") then
        kv.heal = kv.heal * 3
        if kv.entindex_inflictor_const then
            EntIndexToHScript(kv.entindex_inflictor_const):EndCooldown()
        end
    end
    return true
end
Что ето
Срабатывает когда на кого-то накладывают модификатор.
Ссылки на сам модификатор в фильтр не передается, поэтому получить его достаточно проблематично. Особенно если модификаторы обладают MODIFIER_ATTRIBUTE_MULTIPLE .
Функции установки и отключения
Код:
SetModifierGainedFilter(handle hFunction, handle hContext)
ClearModifierGainedFilter()
Если вернуть false
Отклоняет модификатор. Однако работает это криво и функция OnCreated этого модификатора (а также все дочерние) успевают сработать еще до фильтра. Сразу после фильтрации модификатор убирается с вызовом соответствующих функций OnRemoved и OnDestroy.
Параметры события
Код:
duration --[[ Длительность ]]
Код:
name_const --[[ Название модификатора ]]
entindex_parent_const --[[ Индекс юнита, на которого наложили модификатор ]]
entindex_caster_const --[[ Может отсутствовать. Индекс юнита, который наложил модификатор ]]
entindex_ability_const --[[ Может отсутствовать. Индекс способности, которой был наложен модификатор ]]
Лайфхак
Вот такая функция позволяет получить модификатор по таблице, передаваемой в фильтр.
Код:
function GetModifierFromFilterEvent( kv )
    local unit = EntIndexToHScript(kv.entindex_parent_const)
    local modifiers = unit:FindAllModifiersByName(kv.name_const)
    for _, modifier in pairs(modifiers) do
        if kv.entindex_caster_const == nil or EntIndexToHScript(kv.entindex_caster_const) == modifier:GetCaster() then
            if math.abs( kv.duration - modifier:GetRemainingTime() ) < 0.0001 then
                return modifier
            end
        end
    end
end
Есть узкий круг ситуаций, в которых эта функция вернет не совсем то, что нужно, но вряд ли они возникнут.
Пример
Хочу, бабушка, чтоб пудж под момом вместо обычного бафа получал хосту, урон, а потом помирал.
Код:
_G.GM = class({})
_G.Filter = class({})
function Activate()
    GM:InitGameMode()
end
function GM:InitGameMode()
    GameRules:GetGameModeEntity():SetModifierGainedFilter( Dynamic_Wrap( Filter, "ModifierFilter" ), self )
end

function Filter:ModifierFilter( kv )
    local unit = EntIndexToHScript(kv.entindex_parent_const)
    local ability
    if kv.entindex_ability_const then
        ability = EntIndexToHScript(kv.entindex_ability_const)
    end
    if unit:GetUnitName() == "npc_dota_hero_pudge" and kv.name_const == "modifier_item_mask_of_madness_berserk" then
        unit:AddNewModifier( unit, ability, "modifier_kill", { duration = kv.duration } )
        unit:AddNewModifier( unit, ability, "modifier_rune_haste", { duration = kv.duration } )
        unit:AddNewModifier( unit, ability, "modifier_rune_doubledamage", { duration = kv.duration } )
        ability:StartCooldown(120)
        return false
    end
    return true
end
Что ето
Вызывается при добавлении предмета в инвентарь. Тайник является частью инвентаря, поэтому не сработает, если взять предмет из тайника.
Функции установки и отключения
Код:
SetItemAddedToInventoryFilter(handle hFunction, handle hContext)
ClearItemAddedToInventoryFilter()
Если вернуть false
Отменяет евент. Предмет как-бы передастся, но пропадет. CastOnPickup не срабатывает. Если предмет появляется в магазине раз в некоторое время, то кулдаун не запустится, но деньги потратятся.
Параметры события
Код:
suggested_slot --[[ Слот, в который добавляется предмет. Если собираетесь менять это значение, позаботьтесь, чтобы новый слот был свободен. Иначе может происходить самая разнообразная херня вплоть от ошибки до дропа предмета на базе. ]]
Код:
inventory_parent_entindex_const --[[ Индекс юнита, который получает предмет. При покупке на стадии планирования -1. ]]
item_entindex_const --[[ Индекс предмета ]]
item_parent_entindex_const --[[ Не совсем понял, зачем это нужно. Если герой покупает предмет, то это значение равно  inventory_parent_entindex_const. Если герой подбирает дроп или ему передают предмет, то -1 ]]

Код:
-1 --[[ Предмет добавляется в первый свободный слот ]]
DOTA_ITEM_SLOT_1 = 0 --[[ 0 - 5 это слоты активного инвентаря ]]
DOTA_ITEM_SLOT_2 = 1
DOTA_ITEM_SLOT_3 = 2
DOTA_ITEM_SLOT_4 = 3
DOTA_ITEM_SLOT_5 = 4
DOTA_ITEM_SLOT_6 = 5
DOTA_ITEM_SLOT_7 = 6 --[[ 6 - 8 это рюкзак. (Три резервных слота) ]]
DOTA_ITEM_SLOT_8 = 7
DOTA_ITEM_SLOT_9 = 8
DOTA_STASH_SLOT_1 = 9 --[[ 9 - 14 это слоты тайника ]]
DOTA_STASH_SLOT_2 = 10
DOTA_STASH_SLOT_3 = 11
DOTA_STASH_SLOT_4 = 12
DOTA_STASH_SLOT_5 = 13
DOTA_STASH_SLOT_6 = 14
Пример
Убираем выдачу телепорта на старте.
Код:
_G.GM = class({})
_G.Filter = class({})
function Activate()
    GM:InitGameMode()
end
function GM:InitGameMode()
    GameRules:GetGameModeEntity():SetItemAddedToInventoryFilter( Dynamic_Wrap( Filter, "ItemFilter" ), self )
end

function Filter:ItemFilter( kv )
    local item = EntIndexToHScript(kv.item_entindex_const)
    if item:GetAbilityName() == "item_tpscroll" then
        if kv.item_parent_entindex_const == -1 then --Убираем на стадии планирования
            return false
        else
            local owner = EntIndexToHScript(kv.item_parent_entindex_const) --Итем на стадии пика лишь муляж. Убираем у каждого героя первый тп, который попадает ему в инвентарь.
            if owner:IsHero() and owner.tp_remove_marked == nil then
                owner.tp_remove_marked = true
                return false
            end
        end
    end
    return true
end
Что ето
Вызывается при создании направленных проджектаилов (ProjectileManager:CreateTrackingProjectile()). Тычки ренджевиков сюда тоже относятся.
Функции установки и отключения
Код:
SetTrackingProjectileFilter(handle hFunction, handle hContext)
ClearTrackingProjectileFilter()
Если вернуть false
Отменяет евент. Проджектаил не создается.
Параметры события
Код:
dodgeable --[[ Возможно ли заевейдить проджектаил ]]
move_speed --[[ Скорость полета проджектаила ]]
is_attack --[[ Это тычка? (1/0) ]]
expire_time --[[ Если время игры GameRules:GetGameTime() больше этого значения, то проджектаилы начинают неадекватно работать. Они пролетают расстояние, равное расстоянию до цели в момент запуска и уничтожаются, не производя никакого эффекта, если это способность. Если тычка, то урон все-таки нанесется. ]]
max_impact_time --[[ Параметр неизвестного назначения ]]
Код:
entindex_ability_const --[[ Индекс способности, которой был запущен проджектаил. -1 если это была не способность. ]]
entindex_source_const --[[ Индекс юнита запустившего проджектаил ]]
entindex_target_const --[[ Индекс цели ]]
Пример
Делаем случайный разброс для скорости тычек.
Код:
_G.GM = class({})
_G.Filter = class({})
function Activate()
    GM:InitGameMode()
end
function GM:InitGameMode()
    GameRules:GetGameModeEntity():SetTrackingProjectileFilter( Dynamic_Wrap( Filter, "ProjectileFilter" ), self )
end

function Filter:ProjectileFilter( kv )
    if kv.is_attack == 1 then
        local rand = RandomFloat( 0.75, 1.25 )
        kv.move_speed = kv.move_speed * rand
    end
    return true
end


PS
Дота со временем меняется, поэтому все приведенные мной списки дотовских констант когда-то устареют (возможно уже устарели). Актуальные списки констант всегда можно найти на моддоте.
 
Последнее редактирование:

ZLOY

Администратор
Команда форума
27 Июн 2016
953
182
Использовать численное значение вместо его константы не очень хорошо.
 

HappyFeedFriends

Друзья CG
14 Авг 2017
540
32
Проект
Battle Heroes Arena
Полезненько поизучать,и настроить под себя. Спасибо
 
  • Нравится
Реакции: SH4R1K

dovernento

Новичок
8 Июл 2020
8
1
Проект
Начал изучать создание кастомок
Жалко что DOTA_UNIT_ORDER_NONE = 0 не фильтруется в приказах, то есть юнит останавливается и всё для этого ему приказ не понадобился и в фильтре этот юнит не проскакивает, а мне он тварь нужен...)
 
Реклама: