Добавлен , опубликован
Способ реализации:
Версия Warcraft:
Библиотека, позволяющая отменять входящий урон.
код
library NegateDamageLib
globals
    private constant group Group = CreateGroup()
    private constant timer Timer = CreateTimer()

    public keyword MaxLifeBonusAbility
    public keyword MaxLifeBonus
endglobals

native UnitAlive takes unit id returns boolean

function GetUnitMaxLife takes unit u returns real
    return GetUnitState(u, UNIT_STATE_MAX_LIFE) - GetUnitAbilityLevel(u, MaxLifeBonusAbility) * MaxLifeBonus
endfunction

private function ProcessUnits takes nothing returns nothing
    local unit u
    local real life2set

    loop
        set u = FirstOfGroup(Group)
        exitwhen u == null
        call GroupRemoveUnit(Group, u)

        if UnitAlive(u) then
            set life2set = GetWidgetLife(u)
            call UnitRemoveAbility(u, MaxLifeBonusAbility)
            call SetWidgetLife(u, life2set)
        else
            call UnitRemoveAbility(u, MaxLifeBonusAbility)
        endif
    endloop
endfunction

function NegateDamage takes unit u, real negated returns nothing
    local real life2set = GetWidgetLife(u) + negated
    local boolean add_ability = life2set > GetUnitState(u, UNIT_STATE_MAX_LIFE)

    if add_ability then
        call GroupAddUnit(Group, u)
        call UnitAddAbility(u, MaxLifeBonusAbility)
    endif

    call SetWidgetLife(u, life2set)

    if add_ability then
        call TimerStart(Timer, 0., false, function ProcessUnits)
    endif
endfunction

endlibrary

Установка

  1. Скачать .j файл и переместить в Директория-JNGP/jass.
  2. В коде карты прописать импорт библиотеки.
//! import "NegateDamageLibrary.j"
  1. Создать способность (далее NegateDamageAbil), которая увеличивает максимальное здоровье юнитов. Рекомендуется создать способность на основе Item Life Bonus [AIlf] с увеличением здоровья на 1 миллион или больше.
  2. Указать равкод способности и значение увеличения здоровья в константах по шаблону ниже.
globals
    constant integer NegateDamageLib_MaxLifeBonusAbility = 'A000'
    constant integer NegateDamageLib_MaxLifeBonus = 1000000
endglobals

Использование

NegateDamage
call NegateDamage(damaged_unit, damage_to_negate)
Отменяет damage_to_negate для damaged_unit. damage_to_negate должно быть больше 0. Если это значение больше входящего урона, то юнит будет вылечен на разницу этих значений. Если вызывать функцию не в триггере получения урона, то damaged_unit будет вылечен на damage_to_negate.
Отмена может не произойти в следующих случаях:
  • Входящий урон больше, чем бонус здоровья способности NegateDamageAbil.
  • У юнита здоровья меньше чем 1.
GetUnitMaxLife
set max_life = GetUnitMaxLife(any_unit)
Возвращает максимальное здоровье any_unit без учёта бонуса от NegateDamageAbil.

Особенности

Функция NegateDamage меняет текущее и максимальное здоровье юнита во время вызова и через некоторое время ещё раз:
  • Триггеры с событием Unit - Life (TriggerRegisterUnitLifeEvent) могут срабатывать во время изменений здоровья.
  • Чтобы узнавать максимальное здоровье без учёта изменений от NegateDamage, используйте функцию GetUnitMaxLife.

Примеры

Имунные рабочие

Карта
Рабочие игнорируют любой урон до миллиона включительно. Урон более миллиона может убить, а может и не убить.

Щит

Карта
У пехотинца есть способность Shield, которая даёт ему щиты. Щиты поглощают весь входящий урон, пока не израсходуются.

Обновления

08.12.2024
  • Настраиваемые константы (MaxLifeBonusAbility и MaxLifeBonus) теперь можно определить вне библиотеки.
    • Это позволяет импортировать библиотеку из файла, а не копировать её в код карты.
    • В связи с этим изменён способ установки и настройки.
  • Тестовые карты были обновлены для соответствия изменениям выше.
01.12.2024
  • Таймер запускается, только если способность NegateDamageAbil была добавлена.
  • Тестовые карты были обновлены для соответствия изменениям выше.
  • В карте NegateDamage-shield занулены поля Art - Animation - Cast Backswing и Art - Animation - Cast Point у пехотинца.
27.01.2022
  • Теперь способность добавляется в случаях, если сумма текущего здоровья и отменяемого урона больше максимального запаса здоровья.
  • GetUnitMaxHealth переименована в GetUnitMaxLife.
  • Тестовые карты были обновлены для соответствия изменениям выше.
25.12.2021
  • Убрано требование "библиотеки" AINativesLib, так как тот ресурс был обновлён.
  • Определение UnitAlive было добавлено в код самой библиотеки.
  • Обновлены тестовые карты дабы соответствовать изменениям выше.
23.03.2021
  • Добавлена функция GetUnitMaxHealth.
  • Некоторые константы стали публичными.
  • Добавлена ещё одна карта-пример.
21.03.2021 - 3
Полная переработка библиотеки:
  • Делает всё тоже самое, но лучше. Например, теперь регенерация здоровья верно работают у юнитов, которые игнорируют весь входящий урон и находятся под непрерывным огнём.
  • Убрана целая куча настроек и требований.
21.03.2021 - 2
  • В требования добавлена AI natives library.
  • Изменена проверка того, что юнит жив.
21.03.2021 - 1
  • Обновлён способ отключения других триггеров, отслеживающих урон.
  • Обновлена карта-пример.
  • Улучшена обработка случая, когда юнит умирает от полученного урона.
16.03.2021
Исправлены критические ошибки.
`
ОЖИДАНИЕ РЕКЛАМЫ...

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
28
GetLocalPlayer, я проверил, вроде всё пашет как надо. Это здорово всё упрощает.

Да что-то мне кажется, что я кому делал ровно так, как и описал GetLocalPlayer, но когда решил выложить, намутил дичи.
28
Обновлено, теперь стало ещё круче. Даже на гуи использовать можно. И без UMSWE. Спасибо GetLocalPlayer.
15
Ты падажи, у меня еще вопросы остались.
А что если пользователь совершает какие-то операции с максимальным запасом здоровья юнита, после вызова твоей функции? Ведь в таком случае, он будет работать с максимальным запасом здоровья, увеличенным способностью из твоей библиотеки.
Например, у пользователя в карте есть такая способность
Карающее воздаяние мстительного возмездия (пассивное)
Всякий урон против героя провоцирует взрывную волну, которая наносит урон всем противникам в размере 20% от максимального запаса здоровья героя.
Перезарядка: 10 секунд.
Предположим, у владельца способности 1000 ед. здоровья. Если пользовательский триггер, отвечающий за эту способность, срабатывает ДО вызова твоей функции, то все работает ожидаемо и противникам вокруг наносятся запланированные 200 ед. урона. Однако, если очередь триггеров выстроилась таким образом, что триггер, отвечающий за способность срабатывает ПОСЛЕ вызова твоей функции, то из-за того что твоя функция увеличивает макс. запас здоровья на 1 000 000, владелец способности сеет вокруг кровавый апокалипсис нанося более 200000 урона за раз,
Та же невообразимая хурма может получится со всякими щитами, которые накладываются на юнита в момент получения урона и зависят от макс. запаса здоровья. Да и вообще с этой механикой можно наворотить кучу всего.
28
GetLocalPlayer, это просто исправляется введением функции, которая возвращает максимум хп без учёта этой способности.
function GetUnitMaxHealth takes unit u returns real
    local real life = GetWidgetLife(u)
    local real maxlife
    local boolean removed = UnitRemoveAbility(u, HealthAbility)
    set maxlife = GetUnitState(u, UNIT_STATE_MAX_LIFE)
    if removed then
        call UnitAddAbility(u, HealthAbility)
        call SetWidgetLife(u, life)
    endif
    return maxlife
endfunction
Можно написать иначе, если известно, сколько здоровья абилка даёт.
function GetUnitMaxHealth takes unit u returns real
    return GetUnitState(u, UNIT_STATE_MAX_LIFE) - 1000000 * GetUnitAbilityLevel(u, HealthAbility)
endfunction
Эту функцию можно добавить в саму либу, а можно, чтобы юзер её сам написал. Мне пока никто с такими проблемами не обращался, потому что либу никто не юзает.
Уверен, тут может быть много подводных камней, но для начинающих вполне сойдёт, а там дальше они своё напишут, или пересядут на мемхак/1.29+.

Именно по этому в WispTD я сделал своё здоровье, и просто процентно отражал его в реальном здоровье.
28
Сразу скажу наперёд - да, будут проблемы с событиями "Жизни у юнита становятся таким-то". Но без изменения здоровья тут никак.
15
А что делать, если пользователь использует другие библиотеки, затрагивающие максимальный запас здоровья? Чужие наработки, сделанные на заказ. Да и что делать тогда ГУИшнику, если в его карте уже тонна обращений к дефолным бж-функциям?
28
GetLocalPlayer, есть идеи, как отменять урон без добавления максимального хп?
15
PT153, не думаю, что это возможно.
Но!
Но мы вероятно можем контролировать момент, когда мы добавляем макс. хп. Варкрафт ведь линеен, у него нет многопоточности, он не порождает параллельных процессов. Это означает что все триггеры на получение урона юнитом, срабатывают последовательно. Но под влиянием каких правил выстраивается последовательность исполнения триггеров? В порядке назначения им событий? Или в порядке создания триггеров? Зависит ли очередь от величины числового значения хэндла? В порядке регистрации собития (TriggerRegister...Event)? Это очередь или стек?
Зная, как выстраивается последовательность, мы можем хукнуть подходящую функцию таким образом, чтобы максимальное хп юнита увеличивалось не в момент вызова функции, а после того как все пользовательские триггеры отработали.
28
GetLocalPlayer:
последовательность исполнения триггеров
В порядке регистрации событий. Кто раньше зарегистрировался, тот раньше и сработает. А порядок регистрации диктуется расположением триггеров в редакторе: те что выше, регистрируются раньше. Там есть пара нюансов, когда триггер был только что создан, но это исправляется при закрытии и повторном открытии карты с сохранением.
Есть вариант хукнуть функцию нанесения урона, тем самым добавить к ней убирание способности сразу, как только она завершилась. Это поможет ситуациям вроде таких:
function ...
    call UnitDamageTarget()
    ...
    call UnitDamageTarget()
endfunction
Предположим, что в коде нет пауз. Без хука способность уберётся только после выполнения всего кода функции, а также того кода, где функция была вызвана. С хуком сразу после получения урона. Однако до этого все триггеры на урон сработают.
Однако это никак не поможет для ситуаций, когда триггеры срабатывают раз за разом. Это можно наблюдать в тестовой карте: порой там так жёстко расстреливают, что до таймера в 0 секунд очередь доходит только после нескольких срабатываний триггера. Что делать в такой ситуации, не совсем ясно.
И нужно понимать, что после вызова NegateDamage ничего не мешает делать что-то ещё в триггере, узнавать то же максимальное здоровье. Тут никакие хуки не помогут.
15
Было бы удобнее хукать TriggerRegisterUnitEvent, но поскольку vJass позволяет хукать функции только до их выполнения, наш хук будет выполнятся перед основной функцией, в то время как нам надо после. Шипко изящного решения что-то не высматривается.
Тем не менее, на данном этапе можно намутить хотя бы это
Код
static if DEBUG_MODE then
    private function hook_GetUnitState takes unit u, unitstate state returns nothing
        if state == UNIT_STATE_MAX_LIFE and GetUnitAbilityLevel(u, HealthAbility) > 0 then
            call BJDebugMsg("WARNING: попытка получить максимальное здоровье юнита, увеличенное библиотекой Negate Damage")
        endif
    endfunction

    hook GetUnitState hook_GetUnitState
endif
По крайней мере пользователь будет знать, где ошибся. Аналогичный хук нужен как минимум для ГУИшного GetUnitStateSwap
28
до их выполнения
До? Я думал, после.
Было бы удобнее хукать TriggerRegisterUnitEvent
А что это даст? Событие всё равно добавится.
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.