• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[System] Damage Control

Damage Control

This library allows you to manipulate the damage caused and received by units. It works by setting the target's life to the appropriate value before they receive the damage, which effectively produces the same output as dealing that amount of damage.

Of course, there are complications when you hit someone for more than their health or if you want to reduce the damage taken, and that is why this system was created. It eases the process.

This is meant for rapid spell creation, and spell ingenuity. There are some spell ideas that people discard because it is way too much work. For example, turning damage received into damage gained, or increasing the amount of damage a unit deals for their next 3 attacks.. things along those lines.

Before anyone says it, yes this is exactly like DamageModifiers. However, there are some key differences in both the features and the way each is used. I do give due credit to DamageModifiers for the basis, but I felt that I personally wanted a few extra features.

Requirements:

This system is still kind of a work in progress (some features still can be added), but it works and works well.

JASS:
library DamageControl /* v2.1.0.0
*************************************************************************************
*
*   This library allows you to modify the damage output or intake of a unit
*   for any attack, and provides a very neat interface. 
*
*************************************************************************************
*
*   */uses/*
*   
*       */ UnitIndexer /*       hiveworkshop.com/forums/jass-functions-413/system-unit-indexer-172090/
*       */ DamageEvent /*       hiveworkshop.com/forums/jass-functions-413/snippet-damageevent-186829/
*       */ Event /*             hiveworkshop.com/forums/jass-functions-413/snippet-event-186555/
*
************************************************************************************
*
*   Configuration
*/
globals
    private constant integer ABILITY_LIFE_BONUS           = 'DcLB'     // rawcode of the life bonus ability
                     boolean NEGATIVE_DAMAGE_TO_HEAL      = false      // causes negative damage to heal the unit
                                                                       // for the next damaging attack
                     integer DAMAGE_CONTROL_PRIORITY_LAST = 0xaa289    // this will automatically force the damage control
                                                                       // struct to be issued last, after the other ones
endglobals
/*
************************************************************************************
*
*   struct DamageControl extends array
*
*        -   This struct handles the execution of all Damage Control structs,
*        -   and has a few functions and methods for you to work with.
*
*       static Event MOD
*            -   Fired before the system processes damage modifications.
*       static Event FINAL
*            -   Fired after the system completes all damage modifications. 
*       static real result
*            -   The resulting damage of an attack after modifications. Use with
*            -   the FINAL event.
*       static unit source
*            -   The source of the damage. You can use this or DamageEvent.source
*       static unit target
*            -   The target of the damage. You can use this or DamageEvent.target
*       static integer sourceId 
*            -   The index of the source unit. You can use this or DamageEvent.sourceId
*       static integer targetId
*            -   The index of the target unit. You can use this or DamageEvent.targetId
*       static boolean enabled
*            -   Tells whether or not the system is enabled in general.
*       static boolean enabledNext
*            -   Tells whether or not the system is enabled for the next attack.
*
*       static method enable takes nothing returns nothing
*            -   Enables the DamageControl system completely. 
*       static method disable takes nothing returns nothing
*            -   Disables the DamageControl system completely.
*       static method enableNext takes nothing returns nothing
*            -   Enables the DamageControl system for the next damaging attack. This
*            -   function is mostly useless, except in rare cases when you want to
*            -   counteract a disableNext() call.
*       static method disableNext takes nothing returns nothing
*            -   Disables the DamageControl system for the next damaging attack.
*            -   It will automatically reset the system to be enabled afterward.
*
********************************************************************
*
*    module DamageControlModule
*
*        -    To access the damage control features, you must implement this module
*        -    at the bottom of your struct. It unlocks two methods for you to define:
*           static method onDamageDealt takes unit source, unit target, real amount returns real
*           static method onDamageTaken takes unit source, unit target, real amount returns real
*        -    onDamageDealt is executed whenever a unit, source, deals damage.
*        -    onDamageTaken is executed whenever a unit, target, takes damage.
*        -    Both functions should return the resulting amount of damage.
*
*        static boolean array enabled
*             -   To enable your damage control struct for a unit, you simply will set
*             -   this boolean to true, corresponding to the user data of the unit:
*                     set enabled[GetUnitUserData(unit)] = boolean
*             -   All damage control structs are initially disabled for each unit.
*
*        static integer array dealtCount
*        static integer array takenCount
*             -   This keeps track of how many times each unit has had their damage 
*             -   modified by this system. To retrieve each value, simply input the
*             -   user data of the unit as the array value. See the demo spells for
*             -   clear examples on how to use this. Note that these are automatically
*             -   incremented for you every time that struct is used by the unit.
*
********************************************************************
*
*    module DamageControlPriorityModule (added in v.2.0.0.0)
*
*        -    Everything is exactly the same as DamageControlModule, however,
*        -    this module allows you to specify a priority which determines
*        -    when the damage control functions of your struct will be fired
*        -    in relation to the other damage control structs.
*
*        -    You MUST define an integer variable named PRIORITY in your struct
*        -    for this module. For example:
*           static integer PRIORITY = 1
*        -    The number determines what "slot" it will be fit into in terms 
*        -    of evaluation. The lower values are always the first ones to be
*        -    evaluated. For example, if there are 3 structs about to be evaluated,
*        -    and one has a priority of 0, the second a priority of 50, and the 
*        -    third a priority of 2, then it will fire the structs in order of 
*        -    priority: 0, then 2, then 50. By default, all priority structs will
*        -    be fired BEFORE non-priority structs, with one case as an exception:
*
*        -    This will also allow you to evalute structs AFTER all of the other
*        -    structs. To do so, simply use this as the value for PRIORITY:
*           static integer PRIORITY = DAMAGE_CONTROL_PRIORITY_LAST
*        -    This will fire at the end, after all of the integer priority structs
*        -    and after the non-priority structs. 
*
********************************************************************
*
*       struct SampleStruct extends array
*
*           static method onDamageDealt takes unit source, unit target, real amount returns real
*               return amount + 100. 
*           endmethod
*               -   This increases the damage dealt by the unit by 100.
*
*           static method onDamageTaken takes unit source, unit target, real amount returns real
*               if takenCount[DamageEvent.targetId] == 3 then
*                   set takenCount[DamageEvent.targetId] = 0
*                   set enabled[DamageEvent.targetId]    = false
*               endif
*               return amount - 30.
*           endmethod
*               -   This reduces the damage taken by the unit by 30 for the next 3
*               -   damaging attacks he receives. Once it reaches 3, it will disable
*               -   the unit from receiving the damage control of this struct.
*
*           implement DamageControlModule
*
*       endstruct
*
********************************************************************
*
*    Credits
*
*       -   Anitarf: DamageModifiers (ideas and basis)
*       -   Nestharus: AdvDamageEvent (ideas and basis)
*       -   Nestharus: UnitIndexer, DamageEvent, Event
*       -   Bribe: For his ingenious method
*
************************************************************************************/

    globals
        private constant boolean DEFAULT          = NEGATIVE_DAMAGE_TO_HEAL
        private          trigger damageTrigger    = CreateTrigger()
        private          timer   lifeBonusTimer   = CreateTimer()
    endglobals

    private module DamageControlSystemInit
        private static method onInit takes nothing returns nothing
            set  thistype.FINAL = CreateEvent()
            set  thistype.MOD   = CreateEvent()
            call DamageEvent.ANY.register(Condition(function thistype.onDamage))
        endmethod
    endmodule
    
    private struct PriorityList
        static  integer  LAST = DAMAGE_CONTROL_PRIORITY_LAST // random number
    
        static  thistype head = 0
        static  thistype last = 0
        private thistype next
        private integer  prio
        private trigger  curr
        
        static method insert takes integer priority, trigger evaluate returns nothing
            local thistype node = thistype.allocate()
            local thistype this = head.next
            set node.prio = priority
            set node.curr = evaluate
            if this == 0 then
                set head.next = node
                set node.next = 0
                set last      = node
                return
            elseif priority == LAST then
                set last.next = node
                set node.next = 0
                set last      = node
                return
            endif
            loop
                exitwhen this == 0
                if this.prio == priority or this.prio == LAST then
                    set node.next = this.next
                    set this.next = node
                    return
                endif
                set this = this.next
            endloop
        endmethod
        
        static method evaluate takes nothing returns nothing
            local thistype this = head.next
            if this != 0 then
                loop
                    exitwhen this.prio == LAST      // evaluate up until the ones
                    call TriggerEvaluate(this.curr) // that should be last
                    set this = this.next
                endloop
            endif
            call TriggerEvaluate(damageTrigger) // evaluate the non-priority ones
            loop
                exitwhen this == 0              // evaluate the ones that
                call TriggerEvaluate(this.curr) // should be evaluated last
                set this = this.next
            endloop
        endmethod
    endstruct

    struct DamageControl extends array
        static Event   FINAL   = 0
        static Event   MOD     = 0
        static real    result  = 0
        
        private static integer       size        = 0
        private static unit    array lifeUnits
        private static integer array unitSize
        
        static boolean enabled     = true
        static boolean enabledNext = true
        
        static method operator source takes nothing returns unit
            return DamageEvent.source
        endmethod
        static method operator target takes nothing returns unit
            return DamageEvent.target
        endmethod
        static method operator sourceId takes nothing returns integer
            return DamageEvent.sourceId
        endmethod
        static method operator targetId takes nothing returns integer
            return DamageEvent.targetId
        endmethod
        
        static method enable takes nothing returns nothing
            set enabled = true
        endmethod
        static method disable takes nothing returns nothing
            set enabled = false
        endmethod
        static method enableNext takes nothing returns nothing
            set enabledNext = true
        endmethod
        static method disableNext takes nothing returns nothing
            set enabledNext = false
        endmethod
        
        private static method manageLife takes nothing returns nothing
            local real    life
            local integer id
            loop
                exitwhen size == 0
                set id = GetUnitUserData(lifeUnits[size])
                if unitSize[id] == 1 then
                    set life = GetWidgetLife(lifeUnits[size])
                    call UnitRemoveAbility(lifeUnits[size], ABILITY_LIFE_BONUS)
                    call SetWidgetLife(lifeUnits[size], life)
                endif
                set unitSize[id] = unitSize[id] - 1
                set size = size - 1
            endloop
        endmethod
        
        private static method fireEvent takes nothing returns nothing
            if unitSize[DamageEvent.targetId] > 1 then
                call UnitRemoveAbility(DamageEvent.target, ABILITY_LIFE_BONUS)
            endif
            call FINAL.fire()
            if unitSize[DamageEvent.targetId] > 0 then
                call UnitAddAbility(DamageEvent.target, ABILITY_LIFE_BONUS)
            endif
        endmethod
        
        private static method onDamage takes nothing returns boolean
            local unit target   = DamageEvent.target
            local real damage   = DamageEvent.amount
            local real life     = GetWidgetLife(target)
            local real temp     = result
            set result          = damage

            if damage <= 0 or not enabled then
                call thistype.fireEvent()
                set result      = temp
                set target      = null
                return false
            endif
            
            if not enabledNext then       // this is separate from the above block
                set enabledNext = true    // because it is meant to prevent the 
                call thistype.fireEvent() // modification of the next damaging attack,
                set result = temp         // so it should not be used up on 0 damage
                set target = null         // events such as debuffing.
                return false
            endif

            call MOD.fire()
            call PriorityList.evaluate()
            
            if (damage - 0.1) <= result and result <= (damage + 0.1) then
                call thistype.fireEvent()
                set result     = temp
                set target     = null
                return false
            endif
            
            set life = life + damage - result
            if result < damage then
                set size                           = size + 1
                set lifeUnits[size]                = target
                set unitSize[DamageEvent.targetId] = unitSize[DamageEvent.targetId] + 1
                if result < 0 and not NEGATIVE_DAMAGE_TO_HEAL then
                    set result = 0
                    set life   = life + result
                endif
                call TimerStart(lifeBonusTimer, 0., false, function thistype.manageLife)
            elseif life < 0.405 then
                set life = 0.5
            endif
            
            call thistype.fireEvent()
            call SetWidgetLife(target, life)
            set result = temp
            set target = null
            set NEGATIVE_DAMAGE_TO_HEAL = DEFAULT
            return false
        endmethod
        
        implement DamageControlSystemInit
    endstruct
    
    module DamageControlModule
        static boolean array enabled
        
        static if thistype.onDamageDealt.exists() then
            static integer array dealtCount  
        endif
        
        static if thistype.onDamageTaken.exists() then
            static integer array takenCount
        endif
    
        private static method onDamage takes nothing returns boolean
            local real damage = DamageControl.result
            static if thistype.onDamageDealt.exists() then
                if thistype.enabled[DamageEvent.sourceId] then
                    set dealtCount[DamageEvent.sourceId] = dealtCount[DamageEvent.sourceId] + 1
                    set DamageControl.result = thistype.onDamageDealt(DamageEvent.source, DamageEvent.target, damage)
                endif
            endif
            static if thistype.onDamageTaken.exists() then
                if thistype.enabled[DamageEvent.targetId] then
                    set takenCount[DamageEvent.targetId] = takenCount[DamageEvent.targetId] + 1
                    set DamageControl.result = thistype.onDamageTaken(DamageEvent.source, DamageEvent.target, damage)
                endif
            endif
            return false
        endmethod
        
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(damageTrigger, Condition(function thistype.onDamage))
        endmethod
    endmodule
    
    module DamageControlPriorityModule
        static boolean array enabled
        
        static if thistype.onDamageDealt.exists() then
            static integer array dealtCount
        endif
        
        static if thistype.onDamageTaken.exists() then
            static integer array takenCount
        endif
        
        private static method onDamage takes nothing returns boolean
            local real damage = DamageControl.result
            static if thistype.onDamageDealt.exists() then
                if thistype.enabled[DamageEvent.sourceId] then
                    set dealtCount[DamageEvent.sourceId] = dealtCount[DamageEvent.sourceId] + 1
                    set DamageControl.result = thistype.onDamageDealt(DamageEvent.source, DamageEvent.target, damage)
                endif
            endif
            static if thistype.onDamageTaken.exists() then
                if thistype.enabled[DamageEvent.targetId] then
                    set takenCount[DamageEvent.targetId] = takenCount[DamageEvent.targetId] + 1
                    set DamageControl.result = thistype.onDamageTaken(DamageEvent.source, DamageEvent.target, damage)
                endif
            endif
            return false
        endmethod
        
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerAddCondition(t, Condition(function thistype.onDamage))
            call PriorityList.insert(thistype.PRIORITY, t)
            set t = null
        endmethod
    endmodule
endlibrary

As of v.2.0.0.0, priorities have been added. This feature allows you to tell the system in what order you want your damage control structs to be run. For certain ones, you may want it to run first, and for others, you may want it to run last. Now this system has the ability to do that. Read the documentation for more info.

Why are priorities important? Well, resulting damage varies depending on the order in which damage controls are applied. It is sometimes crucial to have certain things run before or after others, else it will lead to the wrong results. With the previous method, there was no way of having that occur without simple luck or extra work. For example, struct A reduces damage taken by 50% and struct B increases damage taken by 25. A unit damages a unit, who has both those structs enabled, for an unmodified 100 damage. If A executes before B, the resulting damage will be 100 * 0.5 + 25 = 75. If B executes before A, the resulting damage will be (100 + 25) * 0.5 = 62.5. See the difference?

Why would someone choose not to use priorities? Well, yes, uniformity is good and I could make it so that all structs require priorities. (and then just add a WHO_KNOWS priority) However, the priority structs have one extra trigger, and they are entered into a list. I decided that if someone doesn't care about priorities, then they can just ignore it and use a normal damage control module. Sometimes, it doesn't really matter, especially when you are not modifying damages by percentages. (flat increases/reductions don't need priorities) That way, the implementation is slightly easier (you don't have to type an extra integer variable, lol) and it is slightly more efficient. Although, I might change this in the future if people feel that uniformity would be the better choice.

In addition, I made a very detailed demo map. It features 4 basic spells:
  • Enrage - Causes the unit to deal 50 extra damage. Lasts for the next 4 attacks.
  • Mutated Blood - Causes any attacks the caster receives to heal the caster instead, for twice as much damage as it would have done. Lasts for the next 5 attacks received.
  • Phoenix Blast - Deals 125 damage to a target, but causes target's next 2 attacks to deal 30 extra damage.
  • Lightning Armor - Creates a protective barrier around the caster, reducing the damage he takes by 50%. In addition, all attackers are struck back for 50 damage.

All of those spells are made relatively easily, and require only a fraction of the code it would normally require. They are heavily commented and each demonstrate interesting ways to take advantage of the system's features.

Attached below is the demo map. Enjoy!

2.1.0.0
  • Added better multiple-instance support.
  • Added operators to read the event data to avoid unnecessary mixing.
  • The event now fires in the correct order, allowing GetWidgetLife() to read the appropriate life before the actual modifications. Please update to 2.1.0.0 if you are using any earlier version.
2.0.0.0
  • Added priorities and reworked the system using Bribe's method to eliminate the need for custom SetUnitLife/GetUnitLife functions.
1.1.2.0
  • Code Optimizations and redocumented.
1.1.1.1
  • Big bug fix. Please update to 1.1.1.1 if you are using 1.1.01.
  • Added a GetUnitMaxLife function to receive a unit's maximum hp without interference from the system.
1.1.0.1
  • Minor Code Optimizations
1.1.0.0
  • Bug Fix
1.0.0.0
  • Initial Release
 

Attachments

  • DamageControl.w3x
    61 KB · Views: 200
Last edited by a moderator:
Level 7
Joined
Dec 3, 2006
Messages
339
So you finally developed a new system out of all that custom damage modifiers stuff sorta. Looks very nice.

On a side note:
Does this mean your still working on Ardent Heroes? The forums for it seem dead sorta. If you want you can contact me on skype and we can collaborate on some stuff.
 
Level 6
Joined
Jun 20, 2011
Messages
249
static method onDamage should be private.

I guess this is a lot faster than the OnUnitEvent snippet i submitted recently since it avoids trigger evaluations.

Personally I like it and use it, after some clean up and updates
 
This is actually well coded except that this part can be slightly improved =)

-> call ResumeTimer(lifeBonusTimer)

That is actually slower than TimerStart.

Is the speed difference marginal or considerable? :p

JASS:
call DamageEvent.ANY.registerTrigger(systemTrigger)
call TriggerAddCondition( systemTrigger, Condition(function thistype.onDamage) )

->

call DamageEvent.ANY.register(Filter(function thistype.onDamage))
 
Level 6
Joined
Jun 20, 2011
Messages
249
This system would cause lots of problems if AoE damage is done: Notice how the timer part works, in order to remove the ability from an unit it reaches for lifeUnits[size] and since the damage done runs first and after that the timers expire the lifeUnits[size] aims towards only one unit, not every unit that had AoE damage received. IMO this system needs TimerUtils.

Here, a version that fixes this issue plus some other stuff
EDIT: code removed.
 
Last edited:
It doesn't need TimerUtils -.-, it just needs a stack.


Dirac, you have much to learn : P



Also, we are experienced enough to code this stuff on our own. Don't you have better things to do than recode people's resources for them? I personally wouldn't waste my time fixing someone else's resource up. Let them fix it themselves ; ). You can just tell them what needs to be fixed and then it's their responsibility to do so.
 
Level 6
Joined
Jun 20, 2011
Messages
249
It doesn't need TimerUtils -.-, it just needs a stack.


Dirac, you have much to learn : P



Also, we are experienced enough to code this stuff on our own. Don't you have better things to do than recode people's resources for them? I personally wouldn't waste my time fixing someone else's resource up. Let them fix it themselves ; ). You can just tell them what needs to be fixed and then it's their responsibility to do so.
You speak truth. I just needs a stack. I fixed it because i need it and currently malfunctions.
 
Ty guys for the responses, I'll fix them tomorrow.

This system would cause lots of problems if AoE damage is done: Notice how the timer part works, in order to remove the ability from an unit it reaches for lifeUnits[size] and since the damage done runs first and after that the timers expire the lifeUnits[size] aims towards only one unit, not every unit that had AoE damage received. IMO this system needs TimerUtils.

Hmm... I don't know if this problem applies. It should work fine with AoE damage, as each unit who received the AoE should be assigned a slot in the lifeUnits[] array. Then it will just loop through them and reduce the hp accordingly if necessary.

I am not 100% sure though (since my brain is kind of not working at the moment), so I will run more bug-tests tomorrow.
 
I've made the necessary optimizations. :D

On a side note:
Does this mean your still working on Ardent Heroes? The forums for it seem dead sorta. If you want you can contact me on skype and we can collaborate on some stuff.

Yes, I am still working on it. Although, progress is at snail's pace, but I don't think there is any rush. I haven't heard from anyone for a while, but I have been still working on it here and there whenever I have spare time. Sadly, the amount of spare time I've had has become drastically lower. :(
 
Level 7
Joined
Dec 3, 2006
Messages
339
JASS:
library Offense initializer Init requires DamageControl, UnitIndexer
    globals
        Event MissEvent
        Event CritEvent
    endglobals

    
    struct Miss extends array
        static boolean is = false
        static real array chance
        
        static method onDamageDealt takes unit source, unit target, real amount returns real
            local integer rand = GetRandomInt(1,100)
            if rand < chance[DamageEvent.sourceId] then
                call MissEvent.fire()
                set Miss.is = true // set the miss hit to true for the TextCombat library
                set amount = amount * 0   // multiply the damage by 0
           endif
            return amount // return the amount of damage done
        endmethod
        implement DamageControlModule
    endstruct    
  
    struct CriticalHit extends array // extend array so it doesn't create useless create/destroy funcs
        static boolean is = false // tells TextCombat whether or not the attack was a critical hit
        static real array chance
        static real array percentage
        
        static method onDamageDealt takes unit source, unit target, real amount returns real
            local integer rand = GetRandomInt(1,100)
            if rand < chance[DamageEvent.sourceId] and Miss.is == false then 
                call CritEvent.fire()
                set CriticalHit.is = true // set the critical hit to true for the TextCombat library
                set amount = amount * percentage[DamageEvent.sourceId]   // multiply the damage by critical strike multiplier
            endif
            return amount // return the amount of damage done
        endmethod
    
        implement DamageControlModule //implement the damage control module at the bottom of your struct
    endstruct
    
 
    
    private function Init takes nothing returns nothing
        set MissEvent = Event.create()
        set CritEvent = Event.create()
    endfunction
    
     
    //Trigger wrappers
    function TriggerRegisterMissEvent takes trigger t returns nothing
        call MissEvent.registerTrigger(t)
    endfunction
    function TriggerRegisterCritEvent takes trigger t returns nothing
        call CritEvent.registerTrigger(t)
    endfunction
    //Data Wrappers
    function GetOffenseUnit takes nothing returns unit
        return DamageEvent.source //the unit who missed the attack or caused the critical strike
    endfunction
    function GetOffenseAttacked takes nothing returns unit
        return DamageEvent.target //returns the unit who was attacked by the misser/critical striker
    endfunction
    function GetCritDamage takes nothing returns real
        return DamageEvent.amount //returns the amount of damage dealt by the crit
    endfunction    
endlibrary

Would something like this work?

Mostly this part:
JASS:
 if rand < chance[DamageEvent.sourceId] and Miss.is == false then
Is the part that i'm wondering about. Cause the syntax for it seems fine; but i'm just not sure if its really how i'm thinking it would work.
 
Last edited:
Level 7
Joined
Dec 3, 2006
Messages
339
That'd work right, you just aren't modding the damage anywhere ;p.
Ok good. Yeah i haven't added in setting any of the chance variables or anything yet. I just was wondering if i understood it right. I'm gonna have to re-code it more to add in damage types like Fire(a pure dmg over time attack), Cold(a slowing attack), Light(a more randomish type damage), Nature(poison), and Magic for my map but this helps me understand how to do it.

You don't need a variable to store the chance, you can just calculate it in the if statement
I think i do if i want items to effect it. :D
 
You could change one of your blocks to this:

JASS:
                if lifeBonus[id] then
                    call UnitRemoveAbility(curr, ABILITY_LIFE_BONUS)
                    call SetWidgetLife(curr, 9999999)
                    set lifeBonus[id] = false
                endif

If the amount is higher than his max life, then SetWidgetLife simply sets its health to its max life. I am not sure why you need to use the minimum between the two.
 
Fixed. I used a different value though because it is supposed to have current life - 100000 instead of max life. That if-block was there before because I was trying to troubleshoot a problem and forgot to take it out lol.

A unit can have more than 9999999 life :p
2^31 is suitable.

Heh, I suppose I can add more. Or I will just make it configurable or something. Although, if I make it at 2^31 it might end up hitting the cap if they have their own life bonus applied. So I might just change it to something higher, but not too high in the next update.
 
The two damage reducing/negating abilities in the test map (Shaman's W and R) cause him to die instantly when struck. For me at least...

This looks nice though. Too bad I can't be bothered installing JNGP :L

Ty for the reporting that! I didn't notice it, I'll fix it asap.

EDIT: Updated to fix that lol. Please update your script if you used version 1.1.0.1 because I just did stupid coding. (I had put the SetWidgetLife after the remove ability so it ended up giving like -99000 hp) Also, I had to go back to that weird check again to see if the result hp is greater than the max life, because it kept bugging out for me. (It would set it in a ratio according to the current life compared to the max life so the unit would end up with like 11 hp)

Also added a GetUnitMaxLife(unit) function if you need to get the correct maximum hp of a unit without interference from the life bonus ability of this system. I didn't bother with the hp change at the moment. It is fairly easy to change, but there are some problems to think about. (eg: if the unit has hp added in the same thread using the SetUnitLife function, I have to accommodate for that, it is hard to explain)
 
Last edited:
Can you do this style of documentation please?

JASS:
library Lib /* v1.0.0.0
*************************************************************************************
*
*   Description
*
*************************************************************************************
*
*   */uses/*
*   
*       */ Library /*       url
*
************************************************************************************
*
*   SETTINGS
*/
globals
    private constant integer BLEH = 1        //how much bleh to get into the map
endglobals
/*
************************************************************************************
*
*    Functions
*
*        function MyFunction takes nothing returns integer
*            -   Description
*        function MyFunction2 takes unit u returns nothing
*            -   Description
*
*******************************************************************
*
*   struct MyStruct extends array
*
*        -    Description
*        -    Description line 2
*
*       static method operator [] takes integer unitTypeId returns HeroTypeStat
*            -   Description
*            -
*            -   integer unitTypeId:             Description
*            -                                   Description line 2
*       integer beans
*            -    Description
*            -    Description line 2
*       real beans2
*            -    Description
*            -    Description line 2
*       method doNothing takes nothing returns nothing
*            -    Description
*       method doSomething takes nothing returns nothing
*            -    Description
*
********************************************************************
*
*   struct MyStruct2 extends array
*
*        -    Description
*        -    Description line 2
*
*       static method operator [] takes integer unitTypeId returns HeroTypeStat
*            -   Description
*            -
*            -   integer unitTypeId:             Description
*            -                                   Description line 2
*       integer beans
*            -    Description
*            -    Description line 2
*       real beans2
*            -    Description
*            -    Description line 2
*       method doNothing takes nothing returns nothing
*            -    Description
*       method doSomething takes nothing returns nothing
*            -    Description
*
************************************************************************************/

I can't even read what you have up right now.. it's a huge wall of text >.<...
 
JASS:
        static method setUnitLife takes unit u, real value returns nothing
            local integer id    = GetUnitUserData(u)
            if lifeBonus[id] then
                call SetWidgetLife( u, value + 100000 )
                set health[id] = value
            else
                call SetWidgetLife( u, value )
            endif
        endmethod
        
        static method getUnitMaxLife takes unit u returns real
            local real maxLife = GetUnitState(u, UNIT_STATE_MAX_LIFE)
            if lifeBonus[GetUnitUserData(u)] then
                return maxLife - 500000

I see 100,000 and 500,000, which one is correct?
 
Level 6
Joined
Jun 20, 2011
Messages
249
So i wrote this down, i think it's a more "direct" way to get to the whole unit deals damage event using a module, mucho more direct than checking for every unit that deals damage for "enabled" with their user data.
JASS:
//! zinc
library SelectiveDamage requires DamageEvent, UnitIndexer

/*
    Module SD expects:
        static method onDamage takes nothing returns boolean
    Use enable(unit) or disable(unit) to make your struct consider when the
    given unit deals damage. (I do not recommend to call these on unit index
    events.)
*/


{
    private module Init{
        static method onInit(){
            RegisterEvent(Condition(function thistype.onDamage),DamageEvent.ANY);
            RegisterUnitIndexEvent(Condition(function thistype.onIndex),UnitIndexer.INDEX);}
    }
    struct DamageControl[]{
        trigger e;
        private static method onDamage()->boolean{
            TriggerEvaluate(thistype[DamageEvent.sourceId].e);
            return false;}
        private static method onIndex()->boolean{
            thistype[GetIndexedUnitId()].e=CreateTrigger();
            return false;}
        module Init;
    }
            
    module SD{
        private static triggercondition t[];
        
        static method enable(unit u){
            t[GetUnitUserData(u)]=TriggerAddCondition(DamageControl[GetUnitUserData(u)].e,Condition(function thistype.onDamage));}
            
        static method disable(unit u){
            TriggerRemoveCondition(DamageControl[GetUnitUserData(u)].e,t[GetUnitUserData(u)]);}
            
        private static method onInit(){
            RegisterEvent(Condition(function thistype.onIndex),DamageControl.ANY);}
    }
}
//! endzinc
 
Can you do this style of documentation please?

Sure, I'll change it later.

I see 100,000 and 500,000, which one is correct?

At the moment if someone wants to set a widget's life while the damage control is being done, (aka, before the 0 second timer expires) then it will set the unit's life to <Input Life> + 100,000. That is so I can revert the life to normal just by subtracting 100,000. It is 100,000 and not 500,000 so that there is room for extra life to be added. It is a little hard to explain since there are only a few cases for it (however, they are important with spells like "Mutated Blood" included in the demo map).

Don't worry, those values are only temporary. I am going to make a permanent solution in the next update.

So i wrote this down, i think it's a more "direct" way to get to the whole unit deals damage event using a module, mucho more direct than checking for every unit that deals damage for "enabled" with their user data.

For normal damage events I agree that the process should be more direct as the conditions involved are varying and can sometimes lead to a lot of unnecessary processing. That is the reason why you made that one other script.

However, in this case it is just 2 boolean checks per module implementation. That is incredibly fast, and I believe that in most people's favor it will be nicer to have fewer handles.

The code you have posted is short, yes, but if I were to implement it to have the same features, I would need to add other lines to compensate. Yeah, the API of having the functions take arguments and whatnot is unnecessary, but I like its explicity about how the functions should be formatted/used. In the end, it would maybe have about 2 fewer lines, +1 triggercondition for each unit applied, +1 boolexpr per implementation, and +1 trigger for each unit in total.

It is a good idea. =) But in this case, it might not be beneficial to implement it and it would require more work to maintain the current API with triggers.
 
Level 6
Joined
Jun 20, 2011
Messages
249
I finished coding my own damage control system (obviously inspired from this one) and while coding i found some new interesting stuff:
The method you use to prevent damage can be highly improved, look at this:
JASS:
    unit    w[];
    real    l[];
    integer s=0;
    timer   t=CreateTimer();
    
    function handleDamage(){
        real d;
        while(s>0){
            s-=1;
            UnitRemoveAbility(w[s],ABILITY_LIFE_BONUS);
            SetWidgetLife(w[s],l[s]);}
    }
    
    public function PreventDamage(real d){
        DamageControl.amount=DamageEvent.amount-d;
        if(DamageEvent.amount<=GetWidgetLife(DamageEvent.source)) return;
        if(0==s) TimerStart(t,0,false,function handleDamage);
        w[s]=DamageEvent.target;
        l[s]=GetWidgetLife(DamageEvent.target)-DamageControl.amount;
        UnitAddAbility(DamageEvent.target,ABILITY_LIFE_BONUS);
        s=s+1;}
}
Works like a charm and it's way simpler (it's even more accurate)
 
Mine modifies damage, it isn't restricted to solely preventing it. Also, make sure you add a custom SetWidgetLife() function, because if they use that before the 0 second timer expires, then it will give the unit an incorrect amount of life. ;)

I'll look into optimizations a bit more, but at the moment I'm trying to achieve failsafeness, as that is one of the main problems with damage controlling. It is long, however with reason, as that length is required to prevent potential bugs and for the system to function properly. :)
 
Updated to v1.1.2.0.

I reverted to the old method of saving a variable and then just retrieving it after the 0 second timer. I couldn't create a situation where a unit deals 2 different instances of damage at once (while still being registered by the system), so I don't think that I need to do the whole +100000/-100000 method as a safety measure. (correct me if I'm wrong)

I redocumented it and made minor code optimizations. Please report any bugs you encounter.
 
Purge, yes there are cases where it can happen, but there is a better way to do it than using max life/current life operators from what you and Nestharus have done. On some rare free time I had a couple weeks ago, when I was converting my GUI things to vJass, I learned a good trick at working around the need for operators.

So this would be an alternate damage system entirely. I will submit it soon.
 
Updated. I decided to use your technique, Bribe, or at least the one used for DamageEngine. Hopefully that is okay with you, I gave you credits! :grin:

I've also added priorities, this version is now v2.0.0.0 and is ready for approval if there are no problems/bugs/optimizations.

2.0.0.0
  • Added priorities and reworked the system using Bribe's method to eliminate the need for custom SetUnitLife/GetUnitLife functions.
1.1.2.0
  • Code Optimizations and redocumented.
1.1.1.1
  • Big bug fix. Please update to 1.1.1.1 if you are using 1.1.01.
  • Added a GetUnitMaxLife function to receive a unit's maximum hp without interference from the system.
1.1.0.1
  • Minor Code Optimizations
1.1.0.0
  • Bug Fix
1.0.0.0
  • Initial Release


EDIT: Now that I think about it, I might need to readd a GetUnitMaxLife function. I'll add it later.
 
Level 6
Joined
Jun 20, 2011
Messages
249
Woah what's that priority thing for? Are you expecting the user to be so picky that it would actually decide which damage fires first? Sick.
I still don't get why is the bonus applied from the life ability bonus a constant since it shouldn't matter at all.
 
Woah what's that priority thing for? Are you expecting the user to be so picky that it would actually decide which damage fires first? Sick.
I still don't get why is the bonus applied from the life ability bonus a constant since it shouldn't matter at all.

Heh, pretty much. But the main reason is explained in the first post.

For the second part, yeah I forgot to remove that. I'll remove it later.
 
Purge, if you take a look at the model I made in GUI Damage Engine, you can see how to do this without the operators. But I also recommend getting Nestharus to inject it directly into his Damage system because that will produce fewer bugs. Someone who uses only Nestharus' system and happens to have your system in the map could get weird readings because they won't know to use the operators you put in place.

If the unit has the blocking ability, you should remove it, fire the events, then re-add it. But there are some tricks you have to consider with the HP so there are a couple extra variables needed.

http://www.hiveworkshop.com/forums/spells-569/gui-damage-engine-201016/
 
Bribe said:
Purge, if you take a look at the model I made in GUI Damage Engine, you can see how to do this without the operators.

Yes, I added that in the previous update. :)

Bribe said:
But I also recommend getting Nestharus to inject it directly into his Damage system because that will produce fewer bugs. Someone who uses only Nestharus' system and happens to have your system in the map could get weird readings because they won't know to use the operators you put in place.

I made some fixes to add to the intuitiveness. It now has separate operators to read the data so that instead of having event data as:
JASS:
DamageEvent.source
DamageEvent.target
DamageEvent.sourceId
DamageEvent.targetId
DamageControl.result
It now can also be written as:
JASS:
DamageControl.source
DamageControl.target
DamageControl.sourceId
DamageControl.targetId
DamageControl.result

It makes for uniformity. I suppose I could always ask Nestharus to implement this or make it an optional textmacro to make it more easier to use... (and so that there won't be extra work required to make other systems that use DamageEvent to become compatible with this) I'll do that later on.

Bribe said:
If the unit has the blocking ability, you should remove it, fire the events, then re-add it. But there are some tricks you have to consider with the HP so there are a couple extra variables needed.

Fixed.

2.1.0.0
  • Added better multiple-instance support.
  • Added operators to read the event data to avoid unnecessary mixing.
  • The event now fires in the correct order, allowing GetWidgetLife() to read the appropriate life before the actual modifications. Please update to 2.1.0.0 if you are using any earlier version.
2.0.0.0
  • Added priorities and reworked the system using Bribe's method to eliminate the need for custom SetUnitLife/GetUnitLife functions.
1.1.2.0
  • Code Optimizations and redocumented.
1.1.1.1
  • Big bug fix. Please update to 1.1.1.1 if you are using 1.1.01.
  • Added a GetUnitMaxLife function to receive a unit's maximum hp without interference from the system.
1.1.0.1
  • Minor Code Optimizations
1.1.0.0
  • Bug Fix
1.0.0.0
  • Initial Release
 
I suppose I could always ask Nestharus to implement this or make it an optional textmacro to make it more easier to use... (and so that there won't be extra work required to make other systems that use DamageEvent to become compatible with this) I'll do that later on.

You update DamageEvent to include this as an optional macro and I'll gladly update the thread =P.
 
Top