• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[JASS] GetUnitArmorReduction

Status
Not open for further replies.
Level 13
Joined
Mar 29, 2012
Messages
530
Just like to share this, with the new functions.

Returns armor reduction of a unit against an attack type.
JASS:
function GetUnitArmorReduction takes unit u, attacktype aType returns real
    local real originalArmor = BlzGetUnitArmor(u)
    local real originalMaxLife = BlzGetUnitMaxHP(u)
    local real originalLife = GetWidgetLife(u)
    local real testLife
  
    call BlzSetUnitArmor(u, 0)
    call BlzSetUnitMaxHP(u, 10000)
    call SetWidgetLife(u, 10000.)
    call UnitDamageTarget(u, u, 100., false, false, aType, DAMAGE_TYPE_UNKNOWN, null)
    set testLife = GetWidgetLife(u)
  
    call BlzSetUnitArmor(u, originalArmor)
    call BlzSetUnitMaxHP(u, R2I(originalMaxLife))
    call SetWidgetLife(u, originalLife)
  
    return (10000. - testLife) / 100.
endfunction
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
The unit's hp is set to 10000 before the unit is damaged, so it won't die unless the attack type deals insanely high damage to the unit.

Why did you set the units armor to 1 instead of 0? I think one is more interested in just the damage modificaton from attack/armor type, since damage reduction from armor points can be easily calculated.
 
The unit's hp is set to 10000 before the unit is damaged, so it won't die unless the attack type deals insanely high damage to the unit.

Why did you set the units armor to 1 instead of 0? I think one is more interested in just the damage modificaton from attack/armor type, since damage reduction from armor points can be easily calculated.

Right, I didn't notice the BlzSetUnitMaxHP
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I just thought that 0 will not cause the unit to have the specified armor reduction.
I also thought so, but haven't tried.

Won't call UnitDamageTarget(u, u,100., false, false, aType, DAMAGE_TYPE_UNKNOWN, null)
run damage event handlers? If so, it might further modify the damage aside from reducing it due to armor. Also, what about mana shield and the like?

Btw, side question, what does the attack boolean parameter of UnitDamageTarget does? I honestly don't know.
 
Level 13
Joined
Mar 29, 2012
Messages
530
Won't call UnitDamageTarget(u, u,100., false, false, aType, DAMAGE_TYPE_UNKNOWN, null) run damage event handlers?
I wasn't aware of this.
It seems damage event handlers must coop not to modify the damage for this one. :confused:
I don't have a solution yet. Maybe I should submit this to the Lab?
Also, what about mana shield and the like?
I tried using mana shield, and the mana shield reduces the damage according to the unit's mana (the unit's mana reduces too). :/
Btw, side question, what does the attack boolean parameter of UnitDamageTarget does?
Idk either. It doesn't show any difference when you use it or not.
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
Spirit Link would be an issue as well. The damage will be reduced and other linked units will take damage.

Btw, side question, what does the attack boolean parameter of UnitDamageTarget does? I honestly don't know.
I think it affects certain abilities like for example Spiked Carapace or Thorns Aura. Better to turn it off in most cases, else the damage dealer might take a lot of damage due to these abilities.
 
Level 13
Joined
Mar 29, 2012
Messages
530
I think this might also fire the unit state events.

TriggerRegisterUnitStateEvent
Spirit Link would be an issue as well. The damage will be reduced and other linked units will take damage.
I see, I might consider posting this to the Lab and experimenting more later.

Unofficially Need Fix xD
Might want to look to GetUnitArmor by gorillabull. I remember implementing it somewhere and has a pretty good precision.
I think it uses more slower process, since WE now has the new 'set unit armor' function, this function make the process more straightforward.
But the flaws are still the same like mine I guess.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
If the user has GUI Damage Engine, you could fit that into this script.

You have to phase the calculation. What if a damage type would amplify the damage beyond the unit's new max HP?

1. Apply 0 armor
2. Damage the unit
3. use DamageEventAmount to see how much the unit will take
4. Set DamageEventAmount to 0 and set DamageEventOverride to true
5. Set the armor back to normal

Technically, another system could be used to calculate the armor reduction so you don't need to mess with that either.
 
I think the name of the function can be misleading, since it practically returns the ratio of damage dealt by a specified attack type to a unit based on the unit's armor type. The issues regarding damage events being run with this function can effectively be circumvented by having a dummy unit damage itself on map initialization, storing the difference between the previous health amount and current health amount as a value for each combination of attack type and armor type.

Taking advantage of that, we can produce the following snippet:
JASS:
library ArmorDamageReduction

globals
    private constant integer ATTACK_TYPE_COUNT = 8
    private constant integer DEFENSE_TYPE_COUNT = 8
    private real effectiveHPFactor = 0.06 // Assumes a default value of 0.06 based on the Gameplay Constants.
    private real array armorDmgMult
endglobals

// Dynamically compute the damage multipliers based on the Gameplay Constants.
private module M
    private static method onInit takes nothing returns nothing
        local unit dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'hpea', 0.0, 0.0, 0.0)
        local real testDmg = 10.0
        local real delta = 1.0
        local real prevHP = GetWidgetLife(dummy)
        local integer i = 0
        local integer j = 0
        local integer unityIndex = -1
        loop
            exitwhen i >= DEFENSE_TYPE_COUNT
            call BlzSetUnitIntegerField(dummy, UNIT_IF_DEFENSE_TYPE, i)
            loop
                exitwhen j >= ATTACK_TYPE_COUNT
                call UnitDamageTarget(dummy, dummy, 1.0, false, false, ConvertAttackType(j), DAMAGE_TYPE_UNKNOWN, null)
                set armorDmgMult[i*ATTACK_TYPE_COUNT + j] = prevHP - GetWidgetLife(dummy)
                call SetWidgetLife(dummy, prevHP)
                // This attack type and defense type combination is the first neutral entry
                if (unityIndex == -1) and (armorDmgMult[i*ATTACK_TYPE_COUNT + j] == 1.0) then
                    set unityIndex = i*ATTACK_TYPE_COUNT + j
                endif
                set j = j + 1
            endloop
            set i = i + 1
            set j = 0
        endloop

        // Check if there are any neutral attack type to defense type multipliers
        if (unityIndex == -1) then
            set unityIndex = 0
            set delta = armorDmgMult[unityIndex]
        endif

        // Change the dummy unit's armor to follow suit.
        // Compute damage reduction factor
        call BlzSetUnitIntegerField(dummy, UNIT_IF_DEFENSE_TYPE, unityIndex / ATTACK_TYPE_COUNT)
        call BlzSetUnitArmor(dummy, 1.0)

        // Kinda paranoid that the dummy's health isn't prevHP here...
        call SetWidgetLife(dummy, prevHP)
        call UnitDamageTarget(dummy, dummy, testDmg, true, false, ConvertAttackType(ModuloInteger(unityIndex, ATTACK_TYPE_COUNT)), DAMAGE_TYPE_NORMAL, null)
        set delta = (prevHP - GetWidgetLife(dummy)) / (delta * testDmg)

        // We can extract the damage multiplier from the following info:
        // - Neutral attack-type to defense-type damage multiplier
        // - A base armor value of 1.0
        // - The delta value itself (the most meaningful)
        set effectiveHPFactor = (1.0 / delta) - 1.0
        call RemoveUnit(dummy)
        set dummy = null
    endmethod
endmodule

private struct S extends array
    implement M
endstruct

// Renamed the original function from GetUnitArmorReduction to GetUnitArmorTypeReduction
function GetUnitArmorTypeReduction takes unit whichUnit, attacktype a returns real
    local integer i = BlzGetUnitIntegerField(whichUnit, UNIT_IF_DEFENSE_TYPE)
    return armorDmgMult[i*ATTACK_TYPE_COUNT + GetHandleId(a)]
endfunction

// Repurposed the function to take the unit only as the function argument.
function GetUnitArmorReduction takes unit whichUnit returns real
    local real armor = BlzGetUnitArmor(whichUnit)
    if (armor > 0.0) then
        set armor = armor*effectiveHPFactor
        return 1.0 / (1.0 + armor)
    endif
    // As it turns out, the damage multiplier effect is hardcoded for negative numbers. I believe the damage
    // reduction only goes so far up to -21.00.
    set armor = RMaxBJ(armor, -21.0)
    // For some reason, wc3 treats -16.00 armor as -15.00 armor. Weird interaction.
    // Also, for units that start out with negative armor due to items, the calculated
    // damage may be off by at least 1 armor point.
    if (armor < -15.00) then
        set armor = armor + 1.0
    endif
    return 2.00 - Pow(0.94, -armor)
endfunction

endlibrary
 
Status
Not open for further replies.
Top