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

[vJASS] DamagePackage

I've spent today to read much dds threads, and as far as I can see I think lfh's is my favorite of approved ones.
As I haven't coded or reviewed a DDS system, I'm also not really familar with the edge cases I need to care about.

As I see bracers and locust work with lfh's, were the tequniques considered?

I'm honestly not sure, but it made sense to me when I saw the DamageEx function that won't execute new events again, instead of manualy turning off one trigger and then turning it on again. Wouldn't it be good if the internal call just doesn't fire new events, or is it really that you avoid a new custom function, or do I miss something?

I like your personal style though, very much, and all seems very intuitive and modular. I think it's good.

As you use no damage timer 0 anymore I guess the GetLife native should in no cases be broken?

The bucket strucutre seems good, I think that's definitly a pro, too.

edit: code damage seems not supported, but could someone explain me why it's exactly relevant to get it?
 
Last edited:
Level 22
Joined
Feb 6, 2014
Messages
2,466
As I see bracers and locust work with lfh's, were the tequniques considered?
I didn't test in mine if bracer's work, but why use bracers when you can use this DDS to code it. Something like set Damage.amount = 0.75*Damage.amount will do the trick after passing it to a condition that Damage.type is MAGICAL.

I'm honestly not sure, but it made sense to me when I saw the DamageEx function that won't execute new events again, instead of manualy turning off one trigger and then turning it on again. Wouldn't it be good if the internal call just doesn't fire new events, or is it really that you avoid a new custom function, or do I miss something?
The idea is what if you want to fire other registered events. For example:
Spell1 - block all incoming damage.
Spell2 - returns all incoming damage.
During Spell2, if the internal damage doesn't fire new events again, Spell1 won't be able to block it. Of course it's also possible to to block all other events if that's what the user wants using Damage.enabled = false -> Deals damage -> Damage.enabled = true.

As you use no damage timer 0 anymore I guess the GetLife native should in no cases be broken?
GetWidgetLife works like a charm.

edit: code damage seems not supported, but could someone explain me why it's exactly relevant to get it?
I didn't put a feature like that here, but when working on a map, one can easily use a wrapper function for that.
JASS:
static method apply takes unit source, unit target, real amount, attacktype at, damagetype dt returns nothing
    set Damage.coded = true   //a new static attribute manually added
    call UnitDamageTarget(source, target, amount, true, false, at, dt, null)
    set Damage.coded = false //a new static attribute manually added
endmethod
 
Your points make sense, and I guess I support the idea that not each of endless flaws has to be fixed, as it could be solved by custom code not too hard in case something is needed.

Hm, but isn't there also this locust bug, if you don't invert the damage that they damage the own unit? Lfh seems to require it, and he mentions it. (but honestly have not tested it, maybe you know it)

Well, you can use the unit life changed event to avoid the GetWidgetLife issue, but that has its own problems.
What have you meant?

The decoupling with modifier and simple damage getting is really a good point, which other system seem to lack, from my side this has potential for being approved.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Hm, but isn't there also this locust bug, if you don't invert the damage that they damage the own unit? Lfh seems to require it, and he mentions it. (but honestly have not tested it, maybe you know it)
Ahh that. It's also mentioned in my own post here but for some reason i forgot to do it. I'll run some tests when i have the time.

What have you meant?
I think it cannot detect really small changes, but i have not experience this.
As mentioned here, damaging units by 0.01 seems to work fine.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Ancestral Link?
Spirit Link from Spirit Walker

Anyways updated to v1.40 after encountering a bug when there is too many recursion (Apply damage within an onDamage)

v1.40 - [29 March 2017]
- Implemented a stack to make Damage.source, Damage.target, Damage.amount and Damage.type behave like local variables in the callback.
- Fixed Damage.source and Damage.target changing unit value within callback due to recursion.
- Fixed Damage.amount and Damage.type changing value within callback due to recursion.
- Improved documentation on how it affects default Warcraft 3 abilities.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Try having a unit with only 1 damage on it hit a unit with like 100000 armor : ).
It detects the damage and displays the amount as 1 and the unit with 99999 armor has its hp reduced by 1.
I'll update the attached map soon to include the demo and as well as update the code to fix this bug.
I actually updated my own local copy that added new features like Damage.lockAmount() and fix a bug that occurs when unit with very very high hp (>70k) takes a small damage but too lazy too update the thread.
 
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657
That may be the problem when you are suffering from a floating point inaccuracy error.

But I think you sort of remember the issue with health changed events not catching enough change to actually catch the event and run the triggers.
 
Talking about health-change events? Is it for the NOT_EQUAL state?

I tried this approach for my DDS:

JASS:
private static real finalLife = 0.

private static method onDamage ...
    local trigger trig
    local real currLife = GetWidgetLife(target)
    ...
    ...
    set currLife = currLife + dmg - finalDmg
    // That way, when the unit actually receives the damage, currLife becomes currLife - finalDmg
    ...
    ...
    call SetWidgetLife(target, RMaxBJ(currLife, 0.405))
    if currLife - (2*dmg) + finalDmg < 0.405 then
        // currLife - dmg < 0.405; modify health immediately
        call SetWidgetLife(target, RMaxBJ(currLife, 0.405))
                      
        set finalLife = currLife - dmg
        // currLife is currLife + dmg - finalDmg
        set currLife = finalLife
        // currLife - finalDmg
    else
        // It's safe to delegate the final life to a life-change trigger..
        set finalLife = currLife - dmg
        // modifying current life to expected health...
        set currLife = currLife - (2*dmg) + finalDmg
    endif
    set trig = CreateTrigger()
    // At one point, the NOT_EQUAL limitop did not suit me well, so I changed it to GREATER_THAN or
    // LESS_THAN
    call TriggerRegisterUnitStateEvent(trig, target, UNIT_STATE_LIFE, GREATER_THAN, currLife)
    call TriggerRegisterUnitStateEvent(trig, target, UNIT_STATE_LIFE, LESS_THAN, currLife)
    ...
    set trig = null
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
There's still the bug Nestharus mentioned which can't detect small life change event when the Damage.target's hp is too big. I didn't saw it at first because it only works when the damage is modified or magical.

EDIT: No wait, managed to fix it. I'll update it after removing all the debug messages.
 
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657
@MyPad
The life events fire immediately so they are a big pro compared to the 0s timers that were ussually implemented.

However, iirc, the life events only worked when the health changed from above the value given to the event to below the value given to the event, with at least 0.125 health change (this is just from memory, could be something entirely different.
Nestharus preferred to use 0.5 * damage to be the threshold as it will remove the floating point error, but it also has to remain above that specific value.
Combinind those two requirements would almost certainly run the trigger and it will work fine.
 
@Wietlol
Yup, they fire immediately. The 0s timer is useful in the sense that it allows blocking of damage greater than one's max health.

EDIT:

Never mind the 0s timer. It turns out that it works without it.

@Flux
Tested it with Blademaster with 2 damage and a Critical Strike multiplier of 0.01. Damage Detection still detected it. I also studied the nature of the multiplier and came to the conclusion that the minimum damage that you can deal to a unit is 0.01 (due to a multiplier).
 
Last edited:
Level 22
Joined
Feb 6, 2014
Messages
2,466
Here's a summary of what happens:

Regarding Magical damage
JASS:
                    if amount < -1.0 then
                        call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp - 0.125*amount)
                    else
                        call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 0.01)
                    endif

- A storm bolt with 0.037 damage dealt is detected by life change event (Good)
- A storm bolt with 0.75 damage dealt is not detected by life change event (Bad)

Workaround:
For some reason, a life change event of newHP + 0.01 cannot detect damages greater than 0.125 so another condition needs to be added.
JASS:
                    if amount < -1.0 then
                        call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp - 0.125*amount)
                    elseif amount < -0.125 then
                        call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 0.125)
                    else
                        call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 0.01)
                    endif

- A storm bolt with 0.037 damage dealt is detected by life change event (Good)
- A storm bolt with 0.75 damage dealt is detected by life change event (Good)


Regarding Physical Damage
JASS:
                    if amount > 1.0 then
                        call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, LESS_THAN, newHp - 0.125*amount)
                    elseif amount > 0.125 then
                        call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, LESS_THAN, newHp - 0.125)
                    else
                        call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, LESS_THAN, newHp - 0.01)
                    endif

- A footman with 1 damage and crit multiplier of 0.13 is detected by life change event (Good)
- A footman with 1 damage and crit multiplier of 0.12 is not detected by life change event for units with very high hp (Bad)

Workaround:
Because dealing physical damage below 0.13 is highly unlikely (can only happen when unit has 1 damage and has crit multiplier of <0.13), I edited the condition to only register the life change event when physical damage is greater than 0.125. Therefore, physical damages below 0.13 cannot be modified via "set Damage.amount" but they are still detectable.
 
Level 4
Joined
May 18, 2018
Messages
38
I was having issues with this randomly not detecting damage and I've finally figured out what the problem is. (although I'm not sure exactly why it was a problem)
Basically my map creates a new unit after every unit that's killed and there's ~700 units on the map at any given time and with 12 players that means sometimes like 50-100units are being created in a second.(not sure if that's relevant but it might be)
I found that even though I had auto-register enabled some of the freshly created units wouldn't actually be added to a bucket, I managed to track it down to the Damage.add method in DamageEvent where it has an "if DamageBucket.get(unit) != 0 then" statement to check if units that are being added to a bucket are already in a bucket or not, and if not then it adds them to the current bucket.
Somehow some of the created units were already in a bucket, they were returning 13 or 14 from that (always 13 or 14) for some reason, but if I tried to modify damage from or to them it wouldn't work because they actually weren't in a bucket.
So I ended up just removing that if statement and it's working fine now. Not sure if it was important or not, if I understand correctly its there to make sure units weren't being added to multiple buckets, but since it's auto-registering them and I'm not manually adding units anywhere it shouldn't be an issue I imagine. (unless it is, then please correct me)
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
With the new natives, you basically can write it yourself in a few minutes.
The only thing missing for it being even easier is a generic unit takes damage event.
I actually have written one for my own map, in which I also wrote my own specific unit event leak handling. Tho I am just curious whether there is one that utilises the new native.
 

NEL

NEL

Level 6
Joined
Mar 6, 2017
Messages
113
JASS:
        /*
        5. If you want to deal damage inside onDamage callback without causing infinite loops,
           you can do so using Damage.registerTrigger(trigger) and disabling the trigger before
           the new damage is applied then enabling it again after damage is applied. Example:
           */
                library L initializer Init
                  
                    globals
                        private trigger trg = CreateTrigger()
                    endglobals
                  
                    private function OnDamage takes nothing returns boolean
                        call BJDebugMsg(GetUnitName(Damage.target) " takes " + R2S(Damage.amount) + " damage")
                        call DisableTrigger(thistype.trg)
                        call UnitDamageTarget(Damage.source, Damage.target, 42.0, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
                        call EnableTrigger(thistype.trg)
                        call BJDebugMsg(GetUnitName(Damage.target) " takes an extra 42 damage.")
                        return false
                    endfunction
                  
                    private function Init takes nothing returns nothing
                        call Damage.registerTrigger(trg)
                        call TriggerAddCondition(trg, Condition(function OnDamage))
                    endfunction
                  
                endlibrary

I don't know why you use thistype.trg even you are not inside the struct or maybe I'm wrong.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
@BLOKKADE Perhaps you're using a unit recycler or something that makes a unit already existing in the hash table, not really sure. Anyway, I recommend using an updated DDS if I were you.

@NEL my mistake on that part but I think the point was delivered.

@(others) I was not active during the rolling of the new patch but due to failing to update this, I'm pretty sure this is already obsolete. The best it can do now is to serve as a reference for others.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Compatibility version for this script to work with the latest DamageEngine:

JASS:
library DamageEvent requires DamageEngine

globals
    constant integer DAMAGE_TYPE_PHYSICAL = 1
    constant integer DAMAGE_TYPE_MAGICAL = 2
endglobals

//! textmacro DAMAGE_EVENT_PLUGIN_01

static method operator type takes nothing returns integer
    if udg_IsDamageSpell then
        return DAMAGE_TYPE_MAGICAL
    endif
    return DAMAGE_TYPE_PHYSICAL
endmethod

static method registerTrigger takes trigger t returns nothing 
    call Damage.setupEventRobust(t, "udg_DamageEvent", 1.00, false)
endmethod
static method register takes code c returns nothing
    local trigger t = CreateTrigger()
    call TriggerAddCondition(t, Filter(c))
    call registerTrigger(t)
    set t = null
endmethod

static method registerModifierTrigger takes trigger t returns nothing 
    call Damage.setupEventRobust(t, "udg_DamageModifierEvent", 1.00, false)
endmethod
static method registerModifier takes code c returns nothing
    local trigger t = CreateTrigger()
    call TriggerAddCondition(t, Filter(c))
    call registerTrigger(t)
    set t = null
endmethod

//! endtextmacro
endlibrary
library_once DamageModify uses DamageEvent
endlibrary
 
Last edited:
Top