Get Unit Armor

Not published
» Способ реализации: vJass
» Тип: Наработка
» Версия Warcraft: 1.26+
Библиотека, добавляющая возможность вычислять текущую броню юнита, включая все бонусы.
» код
library ArmorLib initializer init requires NegateDamageLib, MathLib
globals
    private key ParentKey
    private real LogBase
    
    private constant attacktype AttackType = ATTACK_TYPE_CHAOS
    private constant real AttackType_Mutliplier = 1.
    private constant real ArmorDamageReductionMultiplier = 0.06
    private constant real EtherealHealBonus = 1.66
    private constant real Damage = 1000.
    private constant real ValueWhenUnitInvulnerable = 0.
endglobals

function GetUnitArmor takes unit u returns real
    local integer id = GetHandleId(u)
    local real armor
    call SaveBoolean(Hash, ParentKey, id, false)
    call SaveInteger(Hash, ParentKey, id, 1)
    call UnitDamageTarget(u, u, Damage, false, false, AttackType, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
    if not HaveSavedReal(Hash, ParentKey, id) then
        // unit is ethereal
        call SaveBoolean(Hash, ParentKey, id, true)
        call UnitDamageTarget(u, u, Damage, false, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
    endif
    set armor = LoadReal(Hash, ParentKey, id)
    call RemoveSavedBoolean(Hash, ParentKey, id)
    call RemoveSavedInteger(Hash, ParentKey, id)  // redundant, but necessary if no trigger for damage is defined
    call RemoveSavedReal(Hash, ParentKey, id)
    return armor
endfunction

function EvalArmor takes nothing returns boolean
    local integer id = GetHandleId(GetTriggerUnit())
    local real reduction
    local real armor
    if HaveSavedInteger(Hash, ParentKey, id) then
        call RemoveSavedInteger(Hash, ParentKey, id)
        
        if GetEventDamage() > 0. then
            call NegateDamage(GetTriggerUnit(), GetEventDamage())
            
            // total_damage = damage * (1 - reduction) * armor_type_multiplier
            // reduction = 1 - total_damage / damage / armor_type_multiplier
            if LoadBoolean(Hash, ParentKey, id) then
                // unit is ethereal
                set reduction = 1. - GetEventDamage() / Damage / EtherealHealBonus
            else
                set reduction = 1. - GetEventDamage() / Damage / AttackType_Mutliplier
            endif
            
            // For armor formulas, please, refer to these articles
            // http://classic.battle.net/war3/basics/armorandweapontypes.shtml
            // https://warcraft3.info/articles/208/overview-of-armor-and-damage-reduction
            if reduction < 0. then
                // -reduction = 1 - (1 - multiplier) ^ (-armor)
                // reduction = (1 - multiplier) ^ (-armor) - 1
                // armor = - ln(r + 1) / ln(1 - multiplier)
                set armor = - Ln(reduction + 1.) / LogBase
            else
                // reduction = multiplier * armor / (1 + multiplier * armor)
                // armor = reduction / multiplier / (1 - reduction)
                set armor = reduction / ArmorDamageReductionMultiplier / (1. - reduction)
            endif
        else
            set armor = ValueWhenUnitInvulnerable
        endif
        
        call SaveReal(Hash, ParentKey, id, armor)
        return true
    endif
    return false
endfunction

private function init takes nothing returns nothing
    set LogBase = Ln(1. - ArmorDamageReductionMultiplier)
endfunction

endlibrary

Требования

  • Math library
  • Negate Damage library
  • Наличие триггера, который срабатывает на получение урона юнитом, для которого необходимо узнать количество брони. Пример такого триггера можно посмотреть тут и в карте-примере.

Настройка

  • Присвоить константе AttackType такой тип атаки, у которого одинаковый бонус ко всем типам брони. По умолчанию это Chaos. Не может быть типом атаки Magic.
  • Присвоить константе AttackType_Mutliplier значение мультипликатора бонуса выбранного типа атаки. По умолчанию 1, как в стандарте.
  • Присвоить константе ArmorDamageReductionMultiplier значение игровой константы DefenseArmor (Armor Damage Reduction Multiplier). По умолчанию 0.06, как в стандарте.
  • Присвоить константе EtherealHealBonus значение игровой константы EtherealHealBonus (Ethereal Heal Bonus). По умолчанию 1.66, как в стандарте.
  • Присвоить константе Damage значение урона, которое будет наносится для вычисления брони. Рекомендуется оставить неизменным. Значение должно быть больше нуля.
  • Присвоить константе ValueWhenUnitInvulnerable значение брони, которое хотите получать, когда юнит неуязвим. По умолчанию 0.
  • В триггере, который отслеживает получение урона, нужно добавить вызов функции EvalArmor.
» пример
Если у триггера есть условие, то тогда EvalArmor нужно вызывать следующим образом:
function Trig_TakesDamage_Conditions takes nothing returns boolean
    return not EvalArmor() and (/* тут другие условия, которые должны быть соблюдены */)
endfunction
Если нет, то тогда можно добавить в сами действия:
function Trig_TakesDamage_Actions takes nothing returns nothing
    if EvalArmor() then
        return
    endif
    // другие действия
endfunction

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

Чтобы узнать текущую броню юнита u, вызовите GetUnitArmor(u).

Ограничения

  • Так как используются вычисления, то значения брони будут с некоторой погрешностью.
  • Если юнит неуязвим, функция вернёт значение константы ValueWhenUnitInvulnerable.

Пример

В карте изменены мультипликаторы урона для типа атаки Chaos - все увеличены вдвое. Это было сделано с целью демонстрации.

Обновления

» 16.03.2021 - 4
  • Улучшена надёжность определения того, что урон наносится с целью вычислить броню.
  • Добавлена константа ValueWhenUnitInvulnerable.
» 16.03.2021 - 3
Убран даммик.
» 16.03.2021 - 2
  • Добавлена обработка случая, когда юнит неуязвим.
  • Обновлена карта-пример.
» 16.03.2021 - 1
EvalArmor возвращает true, если урон использовался для вычисления брони, false - если нет. Удобно использовать как условие для триггера, который отлавливает нанесение урона.
» пример
function Trig_TakesDamage_Conditions takes nothing returns boolean
    return not EvalArmor()
endfunction

function Trig_TakesDamage_Actions takes nothing returns nothing
    // ваш код
endfunction

function InitTrig_TakesDamage takes nothing returns nothing
    set gg_trg_TakesDamage = CreateTrigger(  )
    call TriggerAddCondition( gg_trg_TakesDamage, Filter(function Trig_TakesDamage_Conditions) )
    call TriggerAddAction( gg_trg_TakesDamage, function Trig_TakesDamage_Actions )
endfunction
В данном примере действия триггера будут выполняться только в том случае, если урон наносился не с целью вычисления брони.


Views: 219

PT153 #1 - 3 months ago 0
Голосов: +0 / -0
Обновил либу, подробности в посте.

Обновил ещё разок, спасибо Bergi_Bear, что напомнил про неуязвимость.

Ещё раз обновил, теперь не требуется даммик.
PT153 #2 - 3 months ago 0
Голосов: +0 / -0
Повысил надёжность и добавил ещё одну константу.
GetLocalPlayer #3 - 3 months ago 0
Голосов: +0 / -0
Присвоить константе AttackType такой тип атаки, у которого одинаковый бонус ко всем типам брони.
В жассе есть дыра с дополнительным типом атаки, она используется для определения типов брони и ее количества в разных системах. Обсуждение можно найти, например, тут.
Вкратце, базовые типы атаки заданы константами
ATTACK_TYPE_NORMAL = ConvertAttackType(0)
ATTACK_TYPE_MELEE = ConvertAttackType(1)
ATTACK_TYPE_PIERCE = ConvertAttackType(2)
ATTACK_TYPE_SIEGE = ConvertAttackType(3)
ATTACK_TYPE_MAGIC = ConvertAttackType(4)
ATTACK_TYPE_CHAOS = ConvertAttackType(5)
ATTACK_TYPE_HERO = ConvertAttackType(6)
Но влындив семерку промеж булочек мы получим тип атаки нерегулируемый игровыми константами
ATTACK_TYPE_HORNY = ConvertAttackType(7)
Bergi_Bear #4 - 3 months ago 0
Голосов: +0 / -0
Так что, неуязвимый юнит вернёт 0 брони, допустим я сделал скил .
Броня героя: Увеличивают броню союзных юнитов на значение равно 50% брони героя. И допустим это паладин с божественным щитом, получается скил отработает в 0.
Тогда наверное в случае инвула, стоит возвращать последнее значение брони, которое удалось получить, это конечно тоже не точное, но всё таки не 0
PT153 #5 - 3 months ago (изм. ) 0
Голосов: +0 / -0
Bergi_Bear, для этого нужно кешировать последнее значение брони для юнита. Причём нужно этот кеш убирать, когда юнит удалён или умер.
Так что пусть картодел сам решает, как ему с этим быть. Я специально добавил константу ValueWhenUnitInvulnerable. Можно поставить ей такое значение, которое не может получиться в игре. Это будет свидетельствовать о том, что юнит неуязвим.
PT153 #6 - 3 months ago (изм. ) 0
Голосов: +0 / -0
GetLocalPlayer, и как показывает обсуждение, у такого типа бонус ко всем типам брони разный. Так что он не подходит.
GetLocalPlayer #8 - 3 months ago 0
Голосов: +0 / -0
Так что он не подходит.
Почему не подходит? Сначала ты делаешь вывод о типе брони, исходя из того как урон был модифицирован, а затем вычисляешь количество брони.
PT153 #9 - 3 months ago (изм. ) 0
Голосов: +0 / -0
GetLocalPlayer, а, в этом плане. Возможно потом обновлю, спасибо.

В таком случае нужно наносить урон 2 раза, чтобы выяснить чистый урон у такого типа брони и урон с вычетом брони у такого типа брони.
GetLocalPlayer #10 - 3 months ago 0
Голосов: +0 / -0
В таком случае нужно наносить урон 2 раза, чтобы выяснить чистый урон у такого типа брони и урон с вычетом брони у такого типа брони.
Да ну и хрен с ним. Главное чтобы конечный пользователь, по дефолту считающийся дураком, был как можно дальше отстранен от внутренней механики кода.
PT153 #11 - 3 months ago 0
Голосов: +0 / -0
GetLocalPlayer, надо будет проверить, наносит ли он урон юнитам в астрале и иммунным к магии.
PT153 #12 - 3 months ago 0
Голосов: +0 / -0
Короче, это не пашет на эфериалов, сниму с публикации до фикса.