System Damage Struct

Dirac

22710180
Reaction score
147
v1.05
-Fixed all bugs regarding prevented damage.
-Fixed a minor bug on clearing the registration of damage taken of deindexed units
-Changed the way Damage.enable works
-Added the static methods "add" and "reduction" to the Damage struct
-Changed the name of "UnitDamage[unitId].done" to "UnitDamage[unitId].dealt"

The system is now safe to use.
 

Techide

New Member
Reaction score
2
Is there a way that I use the RegisterGlobalDamage for preventing the damage, store it, recalculate it and then deal the recalculated amount?
 

Techide

New Member
Reaction score
2
sorry, what I meant was if it was possible to do it for any unit? cause the way I understand it is that the RegisterGlobalDamage fires the function after damagemods and unit damages, meaning that all damage gets prevented if I use it for a global blocker?
 

Techide

New Member
Reaction score
2
no idea actually since I'm kinda new to this kind of approach to scripting it. Structs confuse me a bit I think.

Can you come up with a small example of how and/or what I should actually use...

My idea is to make a scope with an initializing function calling RegisterGlobalDamage() using another function as the filter to do stuff (initially just write a message to see if it works when any unit gets damaged)?

ok got this working:

JASS:
scope tester initializer init

    function myDamage takes nothing returns nothing
    
        
        call BJDebugMsg("ouch! ")
        
    endfunction

    function init takes nothing returns nothing
        call RegisterGlobalDamage(function myDamage)
    endfunction
    
endscope


Now how to get a hold of the different units.... That's the next one :)
ok, found out about that. Damage.target/source... nice

So, preventing the damage seems a bit trickier for me to do. Got the shield struct to work somewhat. Won't fire untill after at least 1 hit. Might be that I'm using it wrong...
 

Dirac

22710180
Reaction score
147
@Laiev
prevent is an static method

@Techide
Don't create shields inside damage handling methods.
Is this an ability or is it a global event? If it's an ability i advise you to use the DamageStruct module. (Learn a bit about structs)
Oh and one little thing, every damage handling method must return a boolean (false would do the trick).

I just realized i need to add Shield an event system to detect when the shield expires.
 

Techide

New Member
Reaction score
2
It's a global event. It is going to be used as a temporary buffer for custom damage calculation of non-scripted damage aka. melee and ranged attacks.

So I should prevent the damage instead of creating a shield I guess?

And as for structs.... I kinda think I know how they work... They're like classes in Java right(if you know java). I'm only just beginning my BA in Computer Science and we're learning about java as first language.

Ok, I think I got it preventing in the right way now:
JASS:
scope tester initializer init

    function myPreventDamage takes nothing returns nothing
        local real damageToPrevent
        local real damageToDeal
        
        set damageToPrevent = Damage[Damage.sourceId].dealt
        call Damage.prevent(damageToPrevent)
        
        call BJDebugMsg(I2S(Damage.sourceId) + " hits " + I2S(Damage.targetId) + " for " + R2S(damageToPrevent) + "damage, and " + R2S(Damage.dealt) + " got through.")
    endfunction

    function init takes nothing returns nothing
        call RegisterGlobalDamage(function myPreventDamage)
    endfunction
    
endscope


So, next step would be to distinguish between attacks and triggered damage, and divide the damage into different custom damage types......

By the way, I hope you don't mind I use this thread as a journal concerning learning how to use your system. I figured that it might help others who wish to use it also.
 

Dirac

22710180
Reaction score
147
If it's a global event is much easier. There are 2 approaches to it, DamageMod or RegisterGlobalDamageEvent (remember that damage mods are run before any other damage event), just pick one.

If you wish to change the damage dealt do something like this
JASS:
private function onDamage takes nothing returns boolean
    call Damage.prevent(Damage.amount)
    set Damage.enable=false
    call UnitDamageTarget(Damage.source,Damage.target,Damage.amount*12894,true,false,ATTACK_TYPE_CHAOS,DAMAGE_TYPE_NORMAL,null)
    set Damage.enable=true
    return false
endfunction

Also i updated the system just now

v1.06
-Fixed a major bug regarding damage reduction calculation
-Changed the name and API of the static method Damage.reduction(attacktype,damagetype) method to public function GetUnitDamageReduction(unit,attacktype,damagetype)
-Added the new GetUnitArmor function
 

Laiev

Hey Listen!!
Reaction score
188
Just a suggestion...

If you want to make sure that the target is the real target, you could do this:

JASS:
private function onDamage takes nothing returns boolean
    if <someTarget> == Damage.target or <someSource> == Damage.source then
        call Damage.prevent(Damage.amount)
        set Damage.enable=false
        call UnitDamageTarget(Damage.source,Damage.target,<someDamage>,true,false,ATTACK_TYPE_CHAOS,DAMAGE_TYPE_NORMAL,null)
        set Damage.enable=true
    endif
    return false
endfunction


EDIT:

@Dirac

Can you post some example of usage of the Mods?

And how the 'priority' work?
 

Techide

New Member
Reaction score
2
So, I decided to go with the damageMod struct with a priority of 0, since as you mentioned (and that makes sense as this is to be the first thing getting checked) that it runs before anything else.
JASS:

scope tester initializer init

    private function OnInitialDamage takes nothing returns boolean
        local real damageToPrevent
        local real targetDamaged
        
        set damageToPrevent = Damage[Damage.sourceId].dealt 
        call Damage.prevent(damageToPrevent)
        set targetDamaged = GetUnitState(Damage.target, UNIT_STATE_MAX_LIFE) - GetWidgetLife(Damage.target)
        
        call BJDebugMsg(I2S(Damage.sourceId) + " hits " + I2S(Damage.targetId) + " for " + R2S(damageToPrevent) + "damage, and " + R2S(targetDamaged) + " got through.")
        return false
    endfunction

    function init takes nothing returns nothing
        call DamageMod.create(function OnInitialDamage, 0)
    endfunction
    
endscope


@Laiev
As I understand this, there is no reason to actually check whether it is the right target since the Damage struct takes care of identifying that.
And about the priority: the lower the number the higher priority it has in the stack to be run. Well at least that's what I got out of it.
 

Dirac

22710180
Reaction score
147
Please don't ever do what Laiev told you to, thats why there is an very useful DamageStruct module to detect specific unit damage. (RegisterUnitDamageDealt and RegisterUnitDamageTaken)

Example of mods:
Lets say you have a hero with 2 abilities, A and B.
A increases damage done by 50%
B increases damage done by exactly 20.
If the hero hits for 30 each attack and casts A then it would be hitting 45 now, if it were to cast B it would hit 50 damage instead. So far so good, but what happens if the hero casts both?
Scenario 1:
A increases damage up to 45 and B increases it to 65.
Scenario 2:
B increases damage up to 50 and A increases it to 75.
How to control it? Use the damage mod priority, if A has a higher priority than B then scenario 1 would happen, or else scenario 2 would happen.

EDIT: didn't see your post there

That whole code seems a little redundant, all you're doing is preventing the damage done just to later on substract it from the unit's life, plus remember that after preventing damage the UNIT_STATE_MAX_LIFE is modified and no longer provides the unit's actual max HP.
Another important thing is that Damage.dealt retrieves the damage that got through, not the actual damage dealt. To calculate this "actual" damage i invite you to use the GetUnitDamageReduction function as in -> [ljass]local real actual=Damage.dealt/GetUnitDamageReduction(Damage.target,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL)[/ljass]
also [ljass]Damage[Damage.sourceId].dealt [/ljass] -> [ljass]Damage.dealt[/ljass]
 

Techide

New Member
Reaction score
2
The only reason I'm doing this deduction is to debug if what I had done worked. This is the base of my initial damageprevention. I plan to use my own custom damage and armor types.
Maybe you can implement a module checking or handling custom types? I've seen it handled in IDDS which is actually nice, though that's only a damage detection system.

EDIT:

hmmm can I add this struct to my units userdata via the DamageData module to specify the damagetypes each unit has?

JASS:
struct UnitDamageData
        // -- damage types --
        integer physDamMin = 0
        integer physDamMax = 0
        integer acidDamMin = 0
        integer acidDamMax = 0
        integer coldDamMin = 0
        integer coldDamMax = 0
        integer elecDamMin = 0
        integer elecDamMax = 0
        integer fireDamMin = 0
        integer fireDamMax = 0
        integer chaosDamMin = 0
        integer chaosDamMax = 0
        integer divineDamMin = 0
        integer divineDamMax = 0
        integer arcaneDamMin = 0
        integer arcaneDamMax = 0
        // -- armor/defense types --
        integer physArmor = 0
        integer physResistance = 0
        integer acidArmor = 0
        integer acidResistance = 0
        integer coldArmor = 0
        integer coldResistance = 0
        integer elecArmor = 0
        integer elecResistance = 0
        integer fireArmor = 0
        integer fireResistance = 0
        integer chaosArmor = 0
        integer chaosResistance = 0
        integer divineArmor = 0
        integer divineResistance = 0
        integer arcaneArmor = 0
        integer arcaneResistance = 0
    endstruct
 

Dirac

22710180
Reaction score
147
Ok let me help you out a little here:

No need to use the DamageData module, you can reach those variables through the "targetId" or "sourceId" integer.
That struct should extend array
None of those variables have to be initialized ("=0") because they're arrays and their default value is 0 always.
I suggest you to use reals instead of integers.

On behalf on what you said about IDDS, that system works pretty much like DamageMod, but includes a lot of useless globals to determine which damage type was done through triggers, this kind of process can and should be user-specific.
If you wish to create "custom" damage types i suggest you to do the following.

Create X amount of new abilities based off Item Attack Frost Bonus, each one with a different buff, and name them according to each new damage type you wish to have.
Add those abilities to the corresponding units through the object editor.
Whenever an unit deals damage, check for all of the buffs those buffs on the target, if it has any of them it means the unit received damage from that "custom" type, and remove the buff.

Example:
New ability named FrostFire damage, with buff "FrostFire detection"
Unit takes damage ->
if Unit has buff (FrostFire detection) then
remove the buff
do actions

Hope it served you some use.

Update.

v1.07
-Changed the way damage is prevented to a more ordered and intuitive way.

EDIT:
v1.08
-Reworked some damage prevention mechanism
-Added UnitDamageTargetEx function and the Damage.type variable
-Added support for custom damage creation
-Added the OVERHEAL constant

The system has been tested and it's now safe to use.
 

Techide

New Member
Reaction score
2
Well I don't want to go down that road with the frost ability thingie, sounds like what nesth did with his adv damageevent, which led to a few surprises :) (Those pesky wc3 bugs).

Anyways, I'm well on my way to making custom damage types. Though my eye just caught your damage.types varable implementation. Haven't looked yet though.
 

Techide

New Member
Reaction score
2
Oooooh you son of a sneaky dog :)

Got this so far when you released your damage type variables... guess this is no longer needed... :/

JASS:
scope GlobalDamageHandling initializer init

    struct UnitDamageData extends array
    // -- damage types --
        real physDamMin
        real physDamMax
        real acidDamMin
        real acidDamMax
        real coldDamMin
        real coldDamMax
        real elecDamMin
        real elecDamMax
        real fireDamMin
        real fireDamMax
        real chaosDamMin
        real chaosDamMax
        real divineDamMin
        real divineDamMax
        real arcaneDamMin
        real arcaneDamMax
        string nativeDamageType
    // -- armor/defense/resistance/whatever types --
        real physArmor
        real physResistance
        real acidArmor
        real acidResistance
        real coldArmor
        real coldResistance
        real elecArmor
        real elecResistance
        real fireArmor
        real fireResistance
        real chaosArmor
        real chaosResistance
        real divineArmor
        real divineResistance
        real arcaneArmor
        real arcaneResistance
        string defaultArmorType
        
        static method getTotalMinDamage takes integer unitId returns real
            local thistype this = unitId
            return this.physDamMin + this.acidDamMin + this.coldDamMin + this.elecDamMin + this.fireDamMin + this.chaosDamMin + this.divineDamMin + this.arcaneDamMin
        endmethod
        
        static method getTotalMaxDamage takes integer unitId returns real
            local thistype this = unitId
            return this.physDamMax + this.acidDamMax + this.coldDamMax + this.elecDamMax + this.fireDamMax + this.chaosDamMax + this.divineDamMax + this.arcaneDamMax
        endmethod
        
        static method getMainBonusDamageType takes integer unitId returns string
            local thistype this = unitId
            local integer i = 1
            local integer index = 0
            local real highestIndexValue = 0
            local real array list
            local string array damageTypeName
            
            set list[1] = this.physDamMin + this.physDamMax
            set list[2] = this.acidDamMin + this.acidDamMax
            set list[3] = this.coldDamMin + this.coldDamMax
            set list[4] = this.elecDamMin + this.elecDamMax
            set list[5] = this.fireDamMin + this.fireDamMax
            set list[6] = this.chaosDamMin + this.chaosDamMax
            set list[7] = this.divineDamMin + this.divineDamMax
            set list[8] = this.arcaneDamMin + this.arcaneDamMax

            set damageTypeName[0] = "INVALID"
            set damageTypeName[1] = "physical"
            set damageTypeName[2] = "acid"
            set damageTypeName[3] = "cold"
            set damageTypeName[4] = "lightning"
            set damageTypeName[5] = "fire"
            set damageTypeName[6] = "chaos"
            set damageTypeName[7] = "divine"
            set damageTypeName[8] = "arcane"

            if this.nativeDamageType == "physical" then
                set index = 1
            elseif this.nativeDamageType == "acid" then
                set index = 2
            elseif this.nativeDamageType == "cold" then
                set index = 3
            elseif this.nativeDamageType == "lightning" then
                set index = 4
            elseif this.nativeDamageType == "fire" then
                set index = 5
            elseif this.nativeDamageType == "chaos" then
                set index = 6
            elseif this.nativeDamageType == "divine" then
                set index = 7
            elseif this.nativeDamageType == "arcane" then
                set index = 8
            else
                set index = 0
            endif
            
            loop
                exitwhen i > 8
                if list<i> &gt; highestIndexValue then
                    set highestIndexValue = list<i>
                    set index = i
                endif
                set i = i + 1
            endloop
            
            return damageTypeName[index]
            
        endmethod
        
    endstruct

    private function onInitialDamage takes nothing returns boolean
        local real damageToPrevent
        
        set damageToPrevent = Damage[Damage.sourceId].dealt 
        call Damage.prevent(damageToPrevent)
        
        // TODO:
        // calculate damage types vs. armor types and deal damage here
        // thinking of getting a struct with damagetype set for unit.
        
        call TextTag_DmgTxtUnit(Damage.target, Damage.dealt, UnitDamageData.getMainBonusDamageType(Damage.sourceId))
        
        return false
        
    endfunction

    private function init takes nothing returns nothing
        call DamageMod.create(function onInitialDamage, 0)
    endfunction
    
endscope
</i></i>


EDIT:
oh but I can use it after all.... stupid me...
 

Dirac

22710180
Reaction score
147
I'm sure this system would work a loot smoother if i turned every single unit argument into integer argument to later retrieve the unit through the proper get-unit-through-index function, but AIDS and UnitIndexer have different API on this one, and personally, i like unit indexer better.

v1.09
-Removed all the custom damage type support, as it now has it's own system.
-Added support for the new DamageType system.

To mods: i can't upload a demo map, the upload fails every time.
 

GFreak45

I didnt slap you, i high 5'd your face.
Reaction score
130
if you cant upload it to this site, check your upload capacity it may be full, if it is delete something or post it on another downoad site and link to it, or if its not then it may be something the mods can help you with, but i would just overall advise you to use another site to upload it : /
 

Switch33

New Member
Reaction score
12
This dynamic approach to damage detection seems to only make sense in games like tower defenses(or where only some units need damage detection/modification). Though I really do like the style of this it's much easier to deal with full encompassing where all units are effected on default like using Purge's Damage Control with Damage Event than this for when you need damage detected for every unit. I mean this dynamic approach doesn't seem useful for rpgs where every unit needs damage detection/modification in my opinion.

Also I don't like that you implemented the Armor checks in this because you are basically using an single damage and check reduction approach. The problem arises with varying damage like a unit deals 50-100 dmg the unit hit may get like 60 dmg or 80 dmg and it could still be the same armor value. The only right way to deal with armor and armor value would be to store unit's armor values in tables; but that gets kinda ugly as well.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top