• 🏆 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!

GetUnitArmor

Status
Not open for further replies.
Level 18
Joined
Sep 14, 2012
Messages
3,413
Hi everyone,

JASS:
library GetUnitArmor initializer init
/****************************************************************************************************************
*           ~How to configure
*               - Copy the ability
*
*           ~How to use
*               - call GetUnitArmor(unit)
****************************************************************************************************************/

    globals
        //The ID of the item
        private constant integer ITEM_ID = 'A000'
        //Half the amount of life the ability adds to allow for both positive and negative damage
        private constant integer ITEM_STRENGTH = 50000
        //The armor constant on gameplay constant
        private constant real ARMOR_CONSTANT = 0.06
        //The factor for Ethereal and Magic Damage
        private constant real ETHEREAL_FACTOR = 1.66
        //The test value, the higher the more precise (too big can bug and instant kill on negative armor unit)
        private constant real TEST_VALUE = 1000.
    endglobals
    
    globals
        private constant real E = 2.718282
        private constant real INV_E = 1/E
        //Cache for the Ln armor constant used to compute negative armor.
        private real ARMOR_NEGATIVE_LNCACHE
    endglobals
    
    //Thanks to looking_for_help !
    private function internLn takes real r returns real
        local real sum = 0.0
        local real sign = 1.0
        if r < 1.0 then
            set r = 1.0/r
            set sign = -1.0
        endif
        loop
            exitwhen r < E
            set r = r*INV_E
            set sum = sum + 1.0
        endloop
        loop
            exitwhen r < 1.2840254
            set r = r*0.778808
            set sum = sum + 0.25
        endloop

        return sign*(sum + 0.125*(r - 1.0)*(1 + 9.0/(2.0 + r) + 4.5/(0.5 + r) + 1.0/r))
    endfunction
        
    function GetUnitArmor takes unit u returns real
        local real lold
        local real llost
        local real mul = 1
        
        set lold = GetWidgetLife(u)
        call UnitAddAbility(u, ITEM_ID)
        call SetWidgetLife(u, ITEM_STRENGTH)
        
        if (IsUnitType(u, UNIT_TYPE_ETHEREAL)) then
            call UnitDamageTarget(u, u, TEST_VALUE, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null)
            set mul = ETHEREAL_FACTOR
        else
            call UnitDamageTarget(u, u, TEST_VALUE, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL, null)
        endif
        
        set llost = (ITEM_STRENGTH - GetWidgetLife(u))/mul
        
        // apply armor type and damage type corrections here
        call UnitRemoveAbility(u, ITEM_ID)
        call SetWidgetLife(u,lold)

        if llost > TEST_VALUE then
            return -internLn(2 - llost / TEST_VALUE) / ARMOR_NEGATIVE_LNCACHE
        endif
        return (TEST_VALUE - llost) / (ARMOR_CONSTANT * llost)
    endfunction
    
    
    private function init takes nothing returns nothing
        //Generate cache
        set ARMOR_NEGATIVE_LNCACHE = internLn(1 - ARMOR_CONSTANT)
    endfunction
endlibrary
 

Attachments

  • TestMapArmor.w3x
    14.2 KB · Views: 120
Last edited:
Level 18
Joined
Sep 14, 2012
Messages
3,413
Ah yes it already exists. OK so you can gy this if this exists already.

Btw, it works if it was the question.

@Purge : Correcting to make it survive ^^

@Everybody : Nevermind just gy this the one Nestharus linked is better.
I just posted it because I often see people asking how and I didn't know thiis resource.
 
Level 12
Joined
Mar 13, 2012
Messages
1,121
You might want to add that this can fail a thousand different ways, returning a wrong value and making it a potential danger for everyone using it.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007

So, tbh I don't really see the big change in this resource? Especially since this was already GYed, the improvements should be significant.

I didn't find this function so I submit it there :)

Nestharus already posted one, right? GetUnitArmorType v8.

I see several heavy problems here, especially that even in your testmap this system doesn't deliver correct results: Blood Mage has 2 armor, your system says 2.2, Paladin 4 vs 3.9 and Mountain King 51 vs 51.3. So the system fails even in the testmap under ideal conditions already.

Furthermore:

  • It will potentially break DDS and at least trigger "wrong" damage events
  • It is armor table dependent and not configurable to at least work around that point
  • It won't work on invulnerable units
  • It will break if someone modifies the units life in an onDamage handler
  • Calling GetUnitArmor in an onDamage handler will cause an infinite recursion and therefore crash the thread
  • It won't work with ethereal units

So, there has to be done a lot more work before this might get approved eventually...
 
Level 18
Joined
Sep 14, 2012
Messages
3,413
The link you posted shares the problem with this ressource but you're right about it being better than mine ^^'.
By the way, it was GYed because I didn't want to work on it one year ago.
And actually the 2.2, 3.9 and 51.3 are the REAL values.
In the Gameplay Constants screen I attacked you can see that every Agility point add 0.3 armor. You can also see that there is -2 armor before applying Agility armor bonus.
Now we go to the Object Editor.
Paladin has 2 base defense and 13 agility points = 2 + 13*.3 - 2 = 2 + 3.9 -2 = 3.9 !
Blood Mage has 0 base defense and 14 agility points = 0 + 14*.3 - 2 = 2.2 !
Mountain King has 50 base defense and 11 agility points = 50 + 11*.3 - 2 = 51.3!
So the values are the good ones.

But since the link his better you can gy this again sorry for the disappointment and the time loss.

EDIT : Lol mine doesn't work for negative values.
I lost the formula ahah. Definitely worth to be GY a second time.
 

Attachments

  • GC.png
    GC.png
    37.5 KB · Views: 154
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
the current ones for sure dont work.

Lets say Unit has 6000 life and -5 armor, which is -26% reduction, you store 6000 inside life, hit it for 1000, store life2 as 6000 - 4733.900[6000 - 1000*1.26] which is 1266.1 when you round it.

Now since life2 is bigger than 1000, you compute (-(1000-1266.1))/(1266.1*0.06) which computes to 3.05, which is very far from -5.

You need logarithm, because you need to reverse power function, and inverse to power is log
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
The link you posted shares the problem with this ressource

Thats right, but that doesn't mean you can't do better, right?

The resource from the link is also from the spell section, where different rules apply compared to the Jass section. Since the spell section is more about specific spells/systems, its ok there that the system is not 100% adaptable to all maps. In the Jass section however, this is quite important.

but you're right about it being better than mine ^^'.

Then improve yours until it is better than this system.

And actually the 2.2, 3.9 and 51.3 are the REAL values.
...
So the values are the good ones.

Alright, thats nice then, my fault.

But since the link his better you can gy this again sorry for the disappointment and the time loss.

I'm not disappointed ;)

Its just a list of things that have to be improved, it doesn't mean the system has no potential in general. From the last posts you can see that there is an interest in the community for such a system, so it makes totally sense to keep updating it.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,195
You should really test stuff more. The health restoration logic is completely wrong and will full heal units. Additionally it will probably still kill units at low life since I recall the item ability retaining the same relative life when added (so If you had 1 HP before you will likely have less than 1,000 after).

I am working on a fixed version, but it may take some time (stupid lack of ln in JASS).

EDIT: Here.
JASS:
library GetUnitArmor initializer init
/****************************************************************************************************************
*           ~How to configure
*               - Copy the ability
*
*           ~How to use
*               - call GetUnitArmor(unit)
****************************************************************************************************************/

    globals
        //The ID of the item
        private constant integer ITEM_ID = 'A000'
        //Half the amount of life the ability adds to allow for both positive and negative damage
        private constant integer ITEM_STRENGTH = 50000
        //The armor constant on gameplay constant
        private constant real ARMOR_CONSTANT = 0.06
        //The test value, the higher the more precise (too big can bug and instant kill on negative armor unit)
        private constant real TEST_VALUE = 1000.
        //Constants required for Ln approximation.
        private constant real LN2 = 0.69315
        private constant real LN_PRECSION_M = 9
        private constant real LN_CONVERGE = 0.0001
        //Cache for the Ln armor constant used to compute negative armor.
        private real ARMOR_NEAGITVE_LNCACHE
    endglobals
    
    //Function which approximates the natural log function. Required for negative armor computation.
    private function LnAprox takes real x returns real
        local real gx = 1
        local real gy = Pow(2, 2 - LN_PRECSION_M) / x
        local real tempr //Temporary storage real.
        loop
            set tempr = (gx + gy) / 2 
            set gy = SquareRoot(gx * gy) //Geometric mean.
            set gx = tempr //Arithmetic mean.
            
            //Check M convergence.
            set tempr = gx - gy
            exitwhen tempr <= LN_CONVERGE and tempr >= -LN_CONVERGE
        endloop
        //Compute and return the approximation.
        return bj_PI / (2 * gx) - LN_PRECSION_M * LN2
    endfunction
    
    function GetUnitArmor takes unit u returns real
        local real lold
        local real llost
        set lold = GetWidgetLife(u)
        call UnitAddAbility(u, ITEM_ID)
        call SetWidgetLife(u, ITEM_STRENGTH)
        call UnitDamageTarget(u, u, TEST_VALUE, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL, null)
        set llost = ITEM_STRENGTH - GetWidgetLife(u)
        // apply armor type and damage type corrections here.
        call UnitRemoveAbility(u, ITEM_ID)
        call SetWidgetLife(u,lold)
        if llost > TEST_VALUE then
            return -LnAprox(2 - llost / TEST_VALUE) / ARMOR_NEAGITVE_LNCACHE
        endif
        return (TEST_VALUE - llost) / (ARMOR_CONSTANT * llost)
    endfunction
    
    private function init takes nothing returns nothing
        //Generate cache
        set ARMOR_NEAGITVE_LNCACHE = LnAprox(1 - ARMOR_CONSTANT)
    endfunction
endlibrary
It does not alter unit current life and also does support negative armor. With negative armor the calculation is only an approximation so there will be some error (less than 0.1). This error could be corrected with some kind of rounding function if desired.

Also do note that the minimum effective armor a unit can have is -20. Below that amount there is no alteration to damage reduction. Hence the minimum armor the system can detect is -20. Even though units can have below -20 armor it still acts as if it was -20.
 
Last edited:
Level 18
Joined
Sep 14, 2012
Messages
3,413
Holy Moose!
Such support.
Thanks guys and specifically DSG !

Furthermore:
It will potentially break DDS and at least trigger "wrong" damage events
It is armor table dependent and not configurable to at least work around that point
It won't work on invulnerable units
It will break if someone modifies the units life in an onDamage handler
Calling GetUnitArmor in an onDamage handler will cause an infinite recursion and therefore crash the thread
It won't work with ethereal units

I don't really know how to prevent it from fire DDS events :s

The configuration will be done.

Yeah I'll fix it.

How will it break if someone modify life?

Yep sadly but it shares the same as the first point.

Woaps.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,195
How will it break if someone modify life?
It has a potential to clash with damage shield systems. They add large amounts of life in response to a damage event and then run a 0 second timer so they can correct the unit life (as damage events fire before the damage is dealt to the unit).

The solution is to simply disable such systems (turn off all damage detection systems/triggers) before dealing the damage and enable them after the damage is dealt.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
you can also check if some boolean is true, and if it is, run the code while setting it to false(set back to true at the end). Now it will not be true next time this thing runs, if run recursivelly, and since the only real thing that can cause callback in the middle of your code is Damage so you are basically done
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Yes but how can I turn off those system :/ ?

You can implement the functionality with conditional compilation for the most common DDS. Something like:

JASS:
library GetUnitArmor uses optional DamageEvent, optional SomeOtherDDS
    // ...
    static if LIBRARY_DamageEvent then
        // Use library DamageEvent methods to disable the DDS
    else if LIBRARY_SomeOtherDDS then
        // Use library SomeOtherDDS methods to disable the DDS
    else
        // and so on
    endif
endlibrary

For that of course, the DDS have to provide some kind of API to disable them temporary. At the moment, PDDS doesn't have this because nobody requested it until now. If you want to implement that feature for PDDS, I can extend the API by a new method to activate/deactivate the system.

You might also consider things like mana shield (at the moment I guess it completly breaks the system) and spirit link (don't know if that is even solvable at all).

You will need to perform some research on the topic and make tests to overcome as much of these kind of problems as possible.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
The damage detection systems should all share a standard API for enable and disable. Since more than 1 DDS might be in a map it would need to be some sort of broadcast messaging kind of API (not sure if static linking would work).

Easy. Then instead else if just use a new if on every DDS:

JASS:
static if LIBRARY_DDS1 then
    // Disable DDS1
endif
static if LIBRARY_DDS2 then
    // Disable DDS2
endif
// and so on

// do stuff 

static if LIBRARY_DDS1 then
    // Enable DDS1
endif
static if LIBRARY_DDS2 then
    // Enable DDS2
endif
// and so on

But yeah, I think edo is right, 2 different DDS in the same map is not the kind of code one wants to maintain and work with...
 
Level 7
Joined
Oct 11, 2008
Messages
304
Can I suggest another way instead of tons of if's?

So people should configure by itself.

JASS:
//! textmacro DisableDDS()
// Put your disabling function of DDS
//! endtextmacro

//! textmacro EnableDDS()
// Put your enabling function of DDS
//! endtextmacro

Or (I know, another callback, but we're here for tons of nanoseconds?)

JASS:
private function DisableDDS takes nothing returns nothing
// Disabling function of DDS
endfunction

private function EnableDDS takes nothing returns nothing
// Enabling function of DDS
endfunction

And implement before-after the damage itself. :ogre_kawaii:

The best IMO would be the textmacro, but it is ugly by itself (but, no callbacks and no extra codes and no extra stupid statics ifs).
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
what about the boolean thing?

Also, you could just say it doesnt work with shielding abilities, or you could create copy unit, give it all the abilities, all the items, all the boosts(you would have to hook multiple functions to make it idiot-proof) and hit that unit
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Okay but I need one function in each DDS to disable the DDS.
And I also need it for Shield System ^^'

Correct, then I will update PDDS soon to support that.

Mana shield should be solvable by something like:

Code:
// Pseudo code
1. Save unit mana
2. Set unit mana to zero
3. Deal damage and restore health
4. Reset unit mana to previous value

EDIT:

Can I suggest another way instead of tons of if's?

It will be about 3 or 4 ifs in total (that get compiled away), so thats not really an argument.

A strong argument for the static if solution however is, that its done automatically by the compiler and the user doesn't have to implement enabling/disabling calls himself.


or you could create copy unit, give it all the abilities, all the items, all the boosts(you would have to hook multiple functions to make it idiot-proof) and hit that unit

That would probably be a good solution, but I'm not sure if its possible... can we even iterate over all abilities/buffs a unit has?
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,195
The problem is that there should be a common call to disable all DDS systems. Having to link each specific DDS implementation to turn off is a maintainability nightmare as you then couple the code to specific DDS implementations.

The only real solution is that all DDS systems should implement a standard interface. This will solve the "multiple DDS" problem as now users choose one. It also solves coupling and maintainability as you can replace DDS systems without needing to change any code.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
The problem is that there should be a common call to disable all DDS systems. Having to link each specific DDS implementation to turn off is a maintainability nightmare as you then couple the code to specific DDS implementations.

The only real solution is that all DDS systems should implement a standard interface. This will solve the "multiple DDS" problem as now users choose one. It also solves coupling and maintainability as you can replace DDS systems without needing to change any code.

That won't be possible because not all DDS are really maintained anymore and relying on a specific API is also just convention but not a perfect solution ...

Ruke said:
as dr points out, an alternative to an interface could be a wrapper

Just provide an optional module users can implement whenever they have to deal with a DDS thats not recognized by the system itself:

JASS:
library GetUnitArmor
	private struct DDSDisabler extends array
		implement optional DDSDisableModule
	endstruct
	function GetUnitArmor takes unit u returns real
		static if DDSDisabler.DisableDDS.exists then
			call DDSDisabler.DisableDDS()
		endif
		// ... do stuff
		return 0.0
	endfunction
endlibrary

module DDSDisableModule
	static method DisableDDS takes nothing returns nothing
	endmethod
endmodule

Advantages:

  • The user can add whatever enabling/disabling mechanism he wants for any custom DDS
  • Basicall no overhead (neither runtime nor space) since enabling/disabling should be inlined
  • The module can be placed in a different trigger, so the user does not have to modify the system code
  • It also plays nice together with static ifs for the most common DDS

So just add the most used DDS with seperate static if's. That should be sufficient for >90% of all users, so they don't have to care about anything.

If someone really wants to use a "special"/custom DDS, then he can just implement the module.
 
Level 10
Joined
Sep 19, 2011
Messages
527
^ a wrapper should take care of those tasks, not this resource

JASS:
library DDSWrapper
public function Enable takes nothing returns nothing
    static if (DDS_1 exists) then
        call <dds1 function to enable>
    endif

    static if (DDS_2 exists) then
        call <dds2 function to enable>
    endif

    //...
endfunction

// same for disable, do damage, get target, get source & get amount
endlibrary

edit: this is quite simple, i'm gonna submit the snippet in a moment
 
Level 10
Joined
Sep 19, 2011
Messages
527
the wrapper will provide support for a few known dds just to avoid a little bit of work if you do use one of them. if you use a non supported one, just add support for it by modifying the wrapper, there is nothing wrong on doing it. this is a practical way of solving this issue (the module is kinda hacky).
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
the wrapper will provide support for a few known dds just to avoid a little bit of work if you do use one of them. if you use a non supported one, just add support for it by modifying the wrapper, there is nothing wrong on doing it. this is a practical way of solving this issue

Yes, there is something wrong with that.

A wrapper can make sense just for a single purpose, namely to make the conditional compilation reusable. As such, the wrapper would be a seperate library that other libraries can use to have a uniform interface for a given set of DDS.

Since the wrapper would be a library itself, it should not be modified by every of its users individually because otherwise the wrapper isn't reusable anymore which kills its purpose in the first place.

(the module is kinda hacky)

Why?

Thats exactly how modules are meant to be used: to provide a clean method to extend existing code.
 
Level 10
Joined
Sep 19, 2011
Messages
527
Since the wrapper would be a library itself, it should not be modified by every of its users individually because otherwise the wrapper isn't reusable anymore which kills its purpose in the first place

the purpose of the adapter, is to give a common interface to the client. its fine if it gets modified by the user as long as the api remains the same.

Why?

Thats exactly how modules are meant to be used: to provide a clean method to extend existing code.

having to use <x> exists directly on the resource is kinda shitty from my point of view.
 
Status
Not open for further replies.
Top