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

[System] DamageType (StructuredDD Extension)

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,516
DamageType

Preface:

DamageType is a resource which extends the functionality of StructuredDD, a damage detection engine. DamageType allows the client to detect whether the damage a unit receives is:

  • An attack
  • A spell
  • From code

The advantage of this system is that it is incredibly user friendly while still providing the maximum amount of functionality for the user.

Design Explanation:

On 2013.01.09, looking_for_help wrote up a detailed explanation about a new method of detecting damage type, which uses the Spell Damage Reduction ability. The design works as follows:

  1. All units are given a modified version of Spell Damage Reduction, set to 200% reduction.
  2. A Damage Detection handler recognizes negative damage as resulting from spell damage reduction, and inverts it.
  3. If a user's Damage Detection handler requests the damage type, the automatic handler will know if it was a spell, physical damage, or code based.

Before this process was discovered, damage type detection had to be done with other methods like orb abilities and splash detection dummies. This is all mitigated with the new design, but not without some flaws of its own (see Limitations).

Limitations:

  • DamageType requires vJass to pre-process.
  • DamageType requires StructuredDD and is therefore incompatible with other damage detection systems.
  • This script uses Vexorian's Table library and thus other versions of Table may be incompatible.
  • DamageType is not perfectly optimized as it aims to balance code quality and readability with optimization.
  • The script requires a custom version of the 'Spell Damage Reduction' ability, thus complicating the implementation slightly.
  • Stacked Spell Damage reduction abilities will glitch the system, thus requiring such abilities to be scripted.
  • Some standard warcraft spells may contain bugs, such as Locust Swarm. I don't try to mitigate this as it just adds code complexity for what's certainly a fixable issue. Known bugs:
    • Carrion Swarm can be fixed by changing its Damage return factor to a negative value
    • Life Drain does not work. (Scripted Version Here)
  • The system is designed to ignore damage values of 0 since many spells impart multiple damage events, for which one is usually a 0 damage event. Thus, if your map uses units which deal 0 damage as a way of customized damage, or spells that do 0 damage as a way of checking their effect hitting only, you will have to script your own pattern recognition, as I have no way of knowing which clients are doing either or neither of these methods. Support for this could be added, however, and thus if you are interested in such capabilities, please contact me.
  • The handler defined in this script must be the first handler added to the handler list using StructuredDD.addHandler(). Thus, if your map contains a library that also uses a StructuredDD handler at map initialization, the library must requires DamageType in order to avoid a race condition.

API:


  • USE_BONUS_CALCULATOR- Set to true if your map contains many damage table fields with values greater than 1.0 - this will automatically calculate a unit's spell resistance at the cost of performance.
  • DAMAGE_TYPE_CHECK_ID- Set this to the rawcode of your modified Spell Damage reduction ability. This ability must be copied from the demo map and configured!



  • DamageType.NULLED- Value returned when DamageType cannot conclusively recognize damage type
  • DamageType.ATTACK- Value returned when DamageType recognizes the damage type as a physical attack
  • DamageType.SPELL- Value returned when DamageType recognizes the damage type as a spell
  • DamageType.CODE- Value returned when DamageType recognizes the damage type as having been dealt via DamageType.dealCodeDamage() (See Methods)



  • DamageType.get()- Requests what type of damage was dealt in the scope of a StructuredDD handler
  • DamageType.dealCodeDamage(unit,unit,real)- Deals an exact amount of damage to a unit. Arguments are given as the first unit deals real damage to the second unit.


The script:

JASS:
//*         API
//*     DamageType.NULLED           Value returned when DamageType cannot conclusively
//*                                     recognize damage type
//*     DamageType.ATTACK           Value returned when DamageType recognizes the
//*                                     damage type as a physical attack
//*     DamageType.SPELL            Value returned when DamageType recognizes the
//*                                     damage type as a spell
//*     DamageType.CODE             Value returned when DamageType recognizes the
//*                                     damage type as having been dealt via dealCodeDamage()
//*     DamageType.get()            Requests what type of damage was dealt in the
//*                                     scope of a StructuredDD handler
//*     DamageType.dealCodeDamage() Deals an exact amount of damage to a unit. Arguments
//*                                     are of the form (unit,unit,real) where the
//*                                     first unit deals real damage to the second
//*                                     unit.
library DamageType requires StructuredDD, Table
    //* This struct contains data to deal damage to a target that would otherwise 
    //* die when hit with double damage in correction of spell damage recognition. 
    //* This is to counter the effect of a unit instantly healing some damage due 
    //* to negative spell damage, without using any unnecessary triggers.
    private struct delayDat
        unit target
        unit source
        real damage
    endstruct
    
    //* This is a struct shim which won't be instanciated; it is only useful for
    //* making the pretty API like DamageType.doSomething()
    struct DamageType
    
        ///< BEGIN CUSTOMIZE SECTION
        
        //* One limitation of the spell damage reduction ability is that if a unit
        //* is meant to receive bonus damage due to the damage table, such that the
        //* result is greater than 100% damage, the unit would incorrectly take
        //* damage capped at 100%. Using standard melee armor tables, this can only
        //* occur when a damaged unit is ethereal; a simple ethereal shim no more
        //* than 20 lines can be created, but for maps with custom damage tables,
        //* this can be a useful option. If USE_BONUS_CALCULATOR is true, units that
        //* would take greater than 100% spell damage will properly exhibit such
        //* properly. (Disable this feature to improve performance)
        private static constant boolean USE_BONUS_CALCULATOR=true
        
        //* Make sure this matches the rawcode/id of your version of the DamageTypeCheck 
        //* ability (you must copy/paste it from the editor to your map)
        private static constant integer DAMAGE_TYPE_CHECK_ID='A000'
        
        ///> END CUSTOMIZE SECTION
        
        public static constant integer NULLED=-1
        public static constant integer ATTACK= 0
        public static constant integer SPELL = 1
        public static constant integer CODE  = 2
        
        //ConvertAttackType(7) is a hidden attack type with some unique properties
        //that make it very useful for us. For more information see goo.gl/9k8tn
        private static constant attacktype ATTACK_TYPE_UNIVERSAL=ConvertAttackType(7)
        
        //How long to delay before dealing the second half of the "correction" damage.
        //Significant tests have shown that a delay of 0. will have no issues.
        private static constant real DELAY_AMOUNT=0.
        
        private static integer lastDamageType=thistype.NULLED
        private static HandleTable delayed
    
        //* Use this to get damage type in a damage event handler.
        public static method get takes nothing returns integer
            local real sourceDamage=GetEventDamage()
            if thistype.lastDamageType==thistype.CODE then
                return thistype.CODE
            elseif sourceDamage>0. then
                return thistype.ATTACK
            elseif sourceDamage<0. then
                return thistype.SPELL
            endif
            return thistype.NULLED
        endmethod
    
        //* Use this to damage units by trigger in your map without causing an infinite 
        //* loop.
        public static method dealCodeDamage takes unit who, unit target, real damage returns nothing
            local integer prevType=thistype.lastDamageType
            local real hp=GetWidgetLife(target)-.405
            local real d=damage
            set thistype.lastDamageType=thistype.CODE
            if hp>d then
                call SetWidgetLife(target,hp-d+.405)
                call UnitDamageTarget(who,target,0.,true,false,thistype.ATTACK_TYPE_UNIVERSAL,DAMAGE_TYPE_UNIVERSAL,null)
            else
                call UnitDamageTarget(who,target,1000000.+d,true,false,thistype.ATTACK_TYPE_UNIVERSAL,DAMAGE_TYPE_UNIVERSAL,null)
                //Also deal magic damage for the special (unmodifiable) case of ethereal
                //units.
                call UnitDamageTarget(who,target,1000000.+d,true,false,ATTACK_TYPE_MAGIC,DAMAGE_TYPE_UNIVERSAL,null)
            endif
            set thistype.lastDamageType=prevType
        endmethod
    
        //* Auxillary function for adding the dummy ability to all units.
        private static method c takes nothing returns boolean
            local unit tU=GetTriggerUnit()
            call UnitAddAbility(tU,thistype.DAMAGE_TYPE_CHECK_ID)
            call UnitMakeAbilityPermanent(tU,true,thistype.DAMAGE_TYPE_CHECK_ID)
            set tU=null
            return false
        endmethod
    
        //* This is the function that occurs 0 seconds after damaging a unit, for the 
        //* case that damaging it by twice as much at the same time would kill it 
        //* unexpectedly.
        private static method after takes nothing returns nothing
            local timer time=GetExpiredTimer()
            local delayDat tempDat=thistype.delayed[time]
            call thistype.dealCodeDamage(tempDat.source,tempDat.target,tempDat.damage)
            call thistype.delayed.flush(time)
            call tempDat.destroy()
            call DestroyTimer(time)
            set time=null
        endmethod
        
        private static method getUnitBonusSpellResistance takes unit u returns real
            local integer prevType=thistype.lastDamageType
            local real life=GetWidgetLife(u)
            local real scale=GetUnitState(u,UNIT_STATE_MAX_LIFE)
            call SetWidgetLife(u,scale)
            set thistype.lastDamageType=thistype.CODE
            call UnitDamageTarget(u,u,-scale/2.,false,false,null,DAMAGE_TYPE_UNIVERSAL,null)
            set scale=2.*(scale-GetWidgetLife(u))/scale
            call SetWidgetLife(u,life)
            set thistype.lastDamageType=prevType
            return scale
        endmethod
        
        //* This is the method that will invert any negative spell damage. It MUST be 
        //* StructuredDD.conditions[0] to function properly; thus, any library using a
        //* StructuredDD handler should 'requires DamageType' (!)
        private static method handler takes nothing returns nothing
            local timer time
            local delayDat tempDat
            local real attemptedDamage=-GetEventDamage()
            local unit tU
            local real sampledLife
            local real scale
            if thistype.get()==thistype.SPELL then
                set tU=GetTriggerUnit()
                static if thistype.USE_BONUS_CALCULATOR then
                    set scale=thistype.getUnitBonusSpellResistance(tU)
                    if scale>1. then
                        set attemptedDamage=attemptedDamage*(scale+1.)/2.
                    endif
                endif
                set sampledLife=GetWidgetLife(tU)-.405
                if sampledLife>=attemptedDamage and sampledLife<=2.*attemptedDamage then
                    call SetWidgetLife(tU,sampledLife-attemptedDamage)
                    set time=CreateTimer()
                    set tempDat=delayDat.create()
                    set tempDat.target=tU
                    set tempDat.source=GetEventDamageSource()
                    set tempDat.damage=attemptedDamage
                    set thistype.delayed[time]=tempDat
                    call TimerStart(time,DELAY_AMOUNT,false,function thistype.after)
                    set time=null
                else
                    call thistype.dealCodeDamage(GetEventDamageSource(),tU,2.*attemptedDamage)
                endif
                set tU=null
            endif
        endmethod
    
        //* Initialization method to enable the system.
        private static method onInit takes nothing returns nothing
            local group grp=CreateGroup()
            local region reg=CreateRegion()
            local trigger addBracer=CreateTrigger()
            local unit FoG
            call RegionAddRect(reg,bj_mapInitialPlayableArea)
            call TriggerRegisterEnterRegion(addBracer,reg,null)
            call TriggerAddCondition(addBracer,Condition(function thistype.c))
            call StructuredDD.addHandler(function thistype.handler)
            call GroupEnumUnitsInRect(grp,bj_mapInitialPlayableArea,null)
            loop
                set FoG=FirstOfGroup(grp)
                exitwhen FoG==null
                call UnitAddAbility(FoG,thistype.DAMAGE_TYPE_CHECK_ID)
                call UnitMakeAbilityPermanent(FoG,true,thistype.DAMAGE_TYPE_CHECK_ID)
                call GroupRemoveUnit(grp,FoG)
            endloop
            set thistype.delayed=HandleTable.create()
            call DestroyGroup(grp)
            set grp=null
            set reg=null
            set addBracer=null
        endmethod
    endstruct
endlibrary

Example Test Scope:


JASS:
//* test scope to only detect when mountain kings attack. (Assumes that
//* StructuredDD.ADD_ALL_UNITS is true)
scope test initializer i
    private function handler takes nothing returns nothing
        if DamageType.get()==DamageType.ATTACK and GetUnitTypeId(GetEventDamageSource())=='Hmkg' then
            call SetUnitVertexColor(GetTriggerUnit(),255,0,0,255)
        endif
    endfunction
    
    private function i takes nothing returns nothing
        call DisplayTextToPlayer(GetLocalPlayer(),0.,0.,"If the mountain king attacks a unit, the target will turn red.")
        call StructuredDD.addHandler(function handler)
    endfunction
endscope

(Yes, it's that simple)


Installation:
  1. Make sure you have an up-to-date copy of StructuredDD installed http://www.hiveworkshop.com/forums/...uctureddd-structured-damage-detection-216968/
  2. Make sure you have an up-to-date copy of Vexorian's Table installed http://www.wc3c.net/showthread.php?t=101246
  3. Make sure you copy the DamageTypeCheck ability from the object editor (see attached map), and set the value of DAMAGE_TYPE_CHECK_ID to your ability's rawcode.
  4. Make sure all damage dealt by triggers in your map against units referenced by StructuredDD is dealt using DamageType.dealCodeDamage().
  5. If your map contains any libraries that use StructuredDD.addHandler, set those libraries to requires DamageType to avoid a race condition.

Change Log:

2013.05.24 - small change to initialization enumeration (thanks to Ruke for noticing this mistake) - in turn reduces the memory footprint and length of the script slightly. (This update does not affect the API)
2013.05.21 - fixed the DamageType struct shim to extends array
2013.05.15 - Updated the API and introduced automatic spell damage reduction calculation, a feature that has already been in looking_for_help and Nestharus' systems for quite some time.
2013.01.28 - Updated API and fixed one small but potentially game-breaking bug.
2013.01.18 - Initial submission to Hive: Jass Resources: Submissions

Special Thanks:

  • looking_for_help who discovered the method of damage type recognition and potentially revolutionizing maps to come.
  • -Kobas- for creating a map submission template, which turned out useful for system submissions as well.
  • Vexorian for developing JassHelper. Without vJass, I almost certainly would not still be scripting for wc3. He also created the version of Table used in this script.
  • The various developers of JNGP including PitzerMike and MindworX. Integrating JassHelper and TESH is a godsend.
  • Nestharus for providing useful insight in regards to some unit testing, as well as convincing me to add the bonus spell damage calculator due to its simplicity.

Todo:

Add functionality for dealing damage by code which is influenced by armor tables, by utilizing damagetype weapontype attacktype datatypes.
 

Attachments

  • (1)DamageType007.w3x
    33.7 KB · Views: 324
Last edited:

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,516
whats advantage of this system over http://www.hiveworkshop.com/forums/submissions-414/extension-damage-source-type-228545/ ?
Im asking because those 2 systems are based on the same idea, do the same stuff and very simlilarily and I think its useless to have 2 exact same systems for 1 thing

Hi edo494,

The difference is that DamageSourceType uses DamageEvent while DamageType uses StructuredDD. (99% of the time, a map will never have both DamageEvent and StructuredDD)

Let me know if you have any other questions.
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,516
Hi, as stated in the limitations:

  • The scrpt does not contain code to calculate any second instance of the spell damage reduction ability, thus it is incompatible with maps that use this for any other purpose, including the Runed Bracer item.

You cannot use magic damage reduction in parallel with this system, however it should be trivially easy to code such a spell using DamageType.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Hi again,

Limitations:

  • ...
  • Some standard warcraft spells may contain bugs, such as Carrion Swarm. I don't try to mitigate this as it just adds code complexity for what's certainly a fixable issue.
  • ...

this is not really true, as carrion swarm works exactly as it is supposed to if you implemented the system correct. All you have to do is to make the damage return factor negativ in the object editor. So its rather an "implementation rule" than a limitation. So no additional code complexity required ;)

looking_for_help: Great job. I am interested in helping you.

Is this offer still valid? Because after checking all native spells , it seems that all work as they are supposed to, except Life Drain. The problem is that the caster doesn't get healed.

If this issue gets fixed the system should work perfect with all spells, but I don't have the time right now to do this... So any help would be appreciated :)

Greetings,
lfh
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,516
this is not really true, as carrion swarm works exactly as it is supposed to if you implemented the system correct. All you have to do is to make the damage return factor negativ in the object editor. So its rather an "implementation rule" than a limitation. So no additional code complexity required ;)

Hi, I've listed it as a limitation because it is my intention to reduce object data modifications as much as possible - if it was the case that carrion swarm was impossible to reproduce otherwise (for example just using breath of fire or scripting it from scratch) - I would include the object data changes.

Is this offer still valid? Because after checking all native spells , it seems that all work as they are supposed to, except Life Drain. The problem is that the caster doesn't get healed.

If this issue gets fixed the system should work perfect with all spells, but I don't have the time right now to do this... So any help would be appreciated :)

I would fix this by scripting a replacement spell for life drain - let me know if that interests you at all, as I don't foresee any realistic approach in the object editor.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Hi, I've listed it as a limitation because it is my intention to reduce object data modifications as much as possible - if it was the case that carrion swarm was impossible to reproduce otherwise (for example just using breath of fire or scripting it from scratch) - I would include the object data changes.

Ok, but then I would mention the solution as people who want to use this system may have problems to find it.

I would fix this by scripting a replacement spell for life drain - let me know if that interests you at all, as I don't foresee any realistic approach in the object editor.

Yes, I would need such a replaced life drain. Although I think it can be done within the object editor as you can change the stolen life to a negative value. Then their function gets somehow inverted, but LifeDrain seems to have quite a special behaviour. Just haven't figured it out untill now, unfortunatly I won't have much time the next days...
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,516
Ok, but then I would mention the solution as people who want to use this system may have problems to find it.

You're right. I've just done that.

Yes, I would need such a replaced life drain. Although I think it can be done within the object editor as you can change the stolen life to a negative value. Then their function gets somehow inverted, but LifeDrain seems to have quite a special behaviour. Just haven't figured it out untill now, unfortunatly I won't have much time the next days...

See the attached map file. It should behave just as life drain.
 

Attachments

  • (1)LifeDrainForDamageType001.w3x
    37.7 KB · Views: 201

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,516
I really don't like your API :(
Either use structs + methods with the lowerCamelCase syntax or normal API functions (DamageType.get() vs GetDamageType()).

This DamageType_get() is sort of a mix of both.

I don't like it either, but let's not kid ourselves - that's the way vJass was designed.

get is a public function inside the DamageType library. Anything different would be a misuse of JassHelper just for prettier function calls.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Hi,

Since you obviously have a test map set up, can you replace this line:

if sampledLife>-attemptedDamage and sampledLife<-2*attemptedDamage then

with this:

if sampledLife>=-attemptedDamage and sampledLife<=-2*attemptedDamage then

And test it again? If that doesn't work I have another option.

Thanks,

Tested and it doesn't work.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Hi,

I did some further testing with your system and somehow the spell damage for heros is completly wrong.

If I throw a level 3 bolt on an enemy hero, it should deal 0.7*350 = 245 damage, but in fact it deals 130 damage. Also, the displayed value from GetEventDamage is wrong, it shows 262.5.

I think one of the problems is this:

JASS:
call UnitDamageTarget(who,target,damage,true,true,ATTACK_TYPE_NORMAL,how,null)

You deal Spell damage with the amount of "damage". But "damage" is already the reduced damage, so you will reduce it again by the spell reduction factor. That was one of the reasons why I made the DealFixDamage function. But then again it shouldn't be 130, so there must be some further errors in the code.

Greetings,
lfh
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,516
Hi,

I did some further testing with your system and somehow the spell damage for heros is completly wrong.

If I throw a level 3 bolt on an enemy hero, it should deal 0.7*350 = 245 damage, but in fact it deals 130 damage. Also, the displayed value from GetEventDamage is wrong, it shows 262.5.

I think one of the problems is this:

JASS:
call UnitDamageTarget(who,target,damage,true,true,ATTACK_TYPE_NORMAL,how,null)

You deal Spell damage with the amount of "damage". But "damage" is already the reduced damage, so you will reduce it again by the spell reduction factor. That was one of the reasons why I made the DealFixDamage function. But then again it shouldn't be 130, so there must be some further errors in the code.

Greetings,
lfh

Hello,

Thanks for the bug report. I don't doubt in the slightest that the dealCodeDamage function may be flawed, as I've triggered "normal" damage to occur - that means that with "standard" damage tables, the system should work improperly against units with fortified/divine armor types.

I should fix this at some point for maps that use custom armor tables, as well as for abilities that affect structures (like earthquake and flame strike).

However, I was unable to duplicate your results. I tested with and without the system using level 3 storm bolt vs both heroes and normal units of various armor values (all using the "hero" armor type), and was never able to calculate a damage value of 130.

In fact, both maps with and without the system dealt exactly 262.5 damage. This is somehow the correct value (I don't doubt your math that it 70% of 350 should be 245, but that's how it works in ladder too).

Have you done anything "special" such that only 130 damage would be dealt?

I'll have to test this again with fortified and divine armors to make sure it works improperly before I update it, but somehow I think it might work properly regardless.

Thanks,
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
In fact, both maps with and without the system dealt exactly 262.5 damage. This is somehow the correct value (I don't doubt your math that it 70% of 350 should be 245, but that's how it works in ladder too).

No its not. ;)

I just checked again your map and you don't use the latest patch, that explaines at least the 262.5 damage. In ladder its 245 damage.

Hello,
However, I was unable to duplicate your results. I tested with and without the system using level 3 storm bolt vs both heroes and normal units of various armor values (all using the "hero" armor type), and was never able to calculate a damage value of 130.

Have you done anything "special" such that only 130 damage would be dealt?

No, I just placed a Level 10 MK and a Level 10 BM on your test map, nothing more. I attached the map that you can check.

Just throw a bolt on the BM and check his health right after the hit - he has 870 HP which means 130 damage.

You can do what was done in AdvDamageEvent. Always save the unit and use SetWidgetLife to set the unit's hp directly. If the unit is to die, set the life to something very small and then deal enough damage to kill it =).

Hm, setting the unit HP to a very low value before dealing the final damage is actually a good idea, I didn't thought of that in my DealFixDamage function... I will implement it like this too, as otherwise it might get problematic with very high HP and Spell damage values.

Greetings,
lfh


EDIT: With latest patch activated, it deals 97 damage... So still wrong.
 

Attachments

  • DamageType002.w3x
    35.4 KB · Views: 136
Level 14
Joined
Dec 12, 2012
Messages
1,007
yes, that would increase the damage taken by spells. but how would that detect it? I mean less likely dont store the default damage of everything and then check if the damage dealt is bigger than normal

What are you actually talking about? I can't really follow^^

The damage is automatically transformed to its correct value (at least when the mentioned bug is fixed).
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,516
You can do what was done in AdvDamageEvent. Always save the unit and use SetWidgetLife to set the unit's hp directly. If the unit is to die, set the life to something very small and then deal enough damage to kill it =).

You're close, but actually once again lfh has one-upped you. Your system will not kill the unit if the damage tables are set so that some attacktype deals 0 damage to some armortype. The solution is to use ConvertAttackType(7) as discovered by looking_for_help here: http://www.hiveworkshop.com/forums/triggers-scripts-269/hidden-attack_type-world-editor-227993/

No its not. ;)

I just checked again your map and you don't use the latest patch, that explaines at least the 262.5 damage. In ladder its 245 damage.





No, I just placed a Level 10 MK and a Level 10 BM on your test map, nothing more. I attached the map that you can check.

Just throw a bolt on the BM and check his health right after the hit - he has 870 HP which means 130 damage.



Hm, setting the unit HP to a very low value before dealing the final damage is actually a good idea, I didn't thought of that in my DealFixDamage function... I will implement it like this too, as otherwise it might get problematic with very high HP and Spell damage values.

Greetings,
lfh


EDIT: With latest patch activated, it deals 97 damage... So still wrong.

Have you received my PM? I fixed this issue on my local end but I want to make sure it's good for you too before I publicize it.

Im curious how this works you got a moded ability and then what?

You don't have to do anything. If you enable the system per the installation instructions, it will work exactly as described in the design explanation.

You will have to replace the positive damage to negative damage.
Good spells have one function that returns the dealt dmg. Simply put * -1 there and you fixed it.

Nope, you don't have to replace anything. The only thing you have to do is use DamageType_dealCodeDamage() from the API.

Edit: I think that was a little bit poorly worded by myself. You *do* have to replace something - your "unit damage target" calls throughout your map. Replace them with the function in the API to prevent infinite loop.

yes, that would increase the damage taken by spells. but how would that detect it? I mean less likely dont store the default damage of everything and then check if the damage dealt is bigger than normal

It doesn't increase spell damage. It reduces it by 200% effectively making all spells heal for their normal value. We detect a negative damage event and then invert it.


---

@looking_for_help let me know if you haven't received my pastebin link.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
couldnt you implement some contsant boolean like HOOK_DAMAGE_SPELL and when it is true then (in static if) hook the UnitDamageTarget to your function?

it will make changing shit loads of values(for some people) to single variable change
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Hello again,

I did some really deep testing and research on the three DamageType-Detection systems in the last days. I will show you what problems I found and we should discuss the best way to solve them.

First the general problems that affect all the three systems.

The solution is to use ConvertAttackType(7) as discovered by looking_for_help here: http://www.hiveworkshop.com/forums/triggers-scripts-269/hidden-attack_type-world-editor-227993/

In general this is true if you really want to deal a completly fixed amount of damage; in this specific case though, it doesn't matter as all Spells have the ATTACK_TYPE_NORMAL, so the technique used in AdvDamageEvent is fine (for this specific purpose).

For a general approach to guarantee a fixed damage on a target which is independent of an armor table (which is also something I think should be supported by a DDS), ConvertAttackType(7) should be used, but only IF the target is non-ethereal. It seems ConvertAttackType(7) is of general type physical, so it cannot damage ethereal units.

The best way to do this should be like this function I wrote:

JASS:
    function DealFixDamage takes unit source, unit target, real amount returns nothing

        local real UNIT_MIN_LIFE = 0.406
        local real MAX_DAMAGE = 1000000
        local attacktype ATTACK_TYPE_UNIVERSAL = ConvertAttackType(7)
        
        local real beforeHitpoints
        local real newHitpoints


        set beforeHitpoints = GetUnitState(target, UNIT_STATE_LIFE)
        set newHitpoints = beforeHitpoints - amount

        if newHitpoints >= UNIT_MIN_LIFE then
            call SetUnitState(target,UNIT_STATE_LIFE,newHitpoints)
        else
            if ( IsUnitType(target, UNIT_TYPE_ETHEREAL) == false ) then
                call SetUnitState(target, UNIT_STATE_LIFE, 1.0)
                call UnitDamageTarget(source, target, MAX_DAMAGE, true, false, ATTACK_TYPE_UNIVERSAL, DAMAGE_TYPE_UNIVERSAL, null)
            else
                call UnitRemoveAbility(target, DAMAGE_TYPE_DETECTOR)
                call SetUnitState(target, UNIT_STATE_LIFE, 1.0)
                call UnitDamageTarget(source, target, MAX_DAMAGE, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)

                if GetUnitState(target, UNIT_STATE_LIFE) >= UNIT_MIN_LIFE then
                    call UnitDamageTarget(source, target, MAX_DAMAGE, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_UNIVERSAL, null)
                endif
            endif
        endif

        set ATTACK_TYPE_UNIVERSAL = null

    endfunction

By doing so you make sure the damage is dealt correctly to the unit; in case the damaged unit is ethereal, you first try spell damage, if that doesn't work you try magic damage (the zero damage event from the first try should then be filtered out, of course). As those two are the only AttackTypes that can damage ethereal units at all, one of them has to apply damage. Best would be of course to remove the ethereal classification of the unit, but although such a function exist, it doesn't seem to work.


But ethereality leads to our next problem.

Somehow, damage applied to units with a spell damage portion of more than 100%, doesn't get applied to the total amount of damage. That means, that enemy ethereal units, or units with a modified damage table (spell damage above 100%) only get a maximum of 100% damage.

E.g. if you throw a level 3 bolt on an enemy ethereal unit, it should deal 1.66*350 = 581 damage, but in fact it deals 350 damage (notice that this only applies to enemy ethereal units, as the spell damage bonus from ethereality doesn't affect friendly units).

The only solution to this problem is to detect the spell factor dynamically, as I did with this function:

JASS:
    function GetUnitSpellResistance takes unit u returns real
        local real originalHP
        local real beforeHP
        local real afterHP
        local real resistance
        local real DUMMY_DAMAGE = 100000
        local real DUMMY_FACTOR = 0.00001
        
        call UnitRemoveAbility(target, DAMAGE_TYPE_DETECTOR)

        set originalHP = GetWidgetLife(target)
        call UnitAddAbility(target, SET_MAX_LIFE)
        set beforeHP = GetWidgetLife(target)
        call DisableTrigger(DamageEvent)
        call UnitDamageTarget(source, target, DUMMY_DAMAGE, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
        call EnableTrigger(DamageEvent)
        set afterHP = GetWidgetLife(target)
        call UnitRemoveAbility(target, SET_MAX_LIFE)
        call SetWidgetLife(target, originalHP)

        call UnitAddAbility(target, DAMAGE_TYPE_DETECTOR)
        

        set resistance = DUMMY_FACTOR*(beforeHP - afterHP)
      
        if resistance > 1.0 then
            return resistance
        else
            return 1.0
        endif
    endfunction

Notice that we only need this when the resistance is bigger than 1.0, so if thats not the case we can just return 1.0 as the damage table gets applied automatically.

I allready fixed those issues in my system which I will update soon.
It works now exactly as it is supposed to.

And here are the system specific problems I found:

AdvDamageEvent
- If a unit gets killed by an artillery unit, it should explode. This doesn't happen.

StructuredDD Extension
- I tested the code you send me and now all units die instantly when taking spell damage, so this is still wrong.
- When damaging a walker with spells, then transforming it to corporal and then killing him with a spell results in a wrong detection (physical instead of spell).


Ok, thats basically all untill now. Feel free to discuss and comment!

Greetings,
lfh
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
Well, I made this AttackType global in my script, so it doesn't appear in the function thats right.

I changed the code to make the principle clearer :p

Btw: Of course there is no native ATTACK_TYPE_UNIVERSAL, the used AttackType just has no defined name. I just named it like this because it can damage all armor types independent of a customized damage table. So "universal" seems to me a good choice. You could of course choose another name.
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,516
For a general approach to guarantee a fixed damage on a target which is independent of an armor table (which is also something I think should be supported by a DDS), ConvertAttackType(7) should be used, but only IF the target is non-ethereal. It seems ConvertAttackType(7) is of general type physical, so it cannot damage ethereal units.

I didn't experience this issue in my latest tests.

Somehow, damage applied to units with a spell damage portion of more than 100%, doesn't get applied to the total amount of damage. That means, that enemy ethereal units, or units with a modified damage table (spell damage above 100%) only get a maximum of 100% damage.

I didn't experience this either.

I tested the code you send me and now all units die instantly when taking spell damage, so this is still wrong.

It worked for me.

Here's what I've been doing in the last hour:

I set up a system which spawns some dummy units with various factors:

  • Different armor types including:
    • Divine
    • Fortified
    • Hero
    • Large (also known as Heavy)
    • Medium
    • Normal
    • Small (also known as Light)
    • Unarmored
  • Different values of armor (0 or 10)
  • Ethereal and Corporeal (for all armor sets)

What I've come up with is that the system in the following script will deal exactly the same amount of damage when the DamageType extension is enabled or disabled, regardless of "Game Data Set".

What I haven't tested:
  • Whether damage varies depending on the targets initial HP, or if the spell kills it
  • Different versions of worldedit.exe

Here is the script:

JASS:
//*         API
//* integer NULLED => returned by get when the GetEventDamage() type can not be concluded.
//* integer ATTACK => returned by get when the GetEventDamage() is found to be a physical attack.
//* integer SPELL => returned by get when the GetEventDamage() is found to be a spell.
//* integer CODE => returned by get when the GetEventDamage() is found to be a spell.
//* public function get(real) => returns a DAMAGE_TYPE_* given GetEventDamage().
//* public function dealCodeDamage(unit,unit,real,damagetype) => causes the first unit to deal real damage of type damagetype to the second unit.
//*
//*     Installation
//* -Make sure you have an up-to-date copy of StructuredDD:
//*     [url]http://www.hiveworkshop.com/forums/jass-resources-412/system-structureddd-structured-damage-detection-216968/[/url]
//* -Make sure you have an up-to-date copy of Vexorian's Table: [url]http://www.wc3c.net/showthread.php?t=101246[/url]
//* -Make sure you copy the DamageTypeCheck unit from the object editor, and set the value of DAMAGE_TYPE_CHECK_ID to your unit's rawcode.
//* -Make sure all damage dealt by triggers in your map against units referenced by StructuredDD is dealt using dealCodeDamage.
//* -If your map contains any libraries that use StructuredDD's addHanler, set those libraries to 'require DamageType' to avoid a race condition.
//*
//*     Final Notes
//* Updated versions can be found at [url]http://www.hiveworkshop.com/forums/submissions-414/system-damagetype-structureddd-extension-228883/[/url] - please report any
//* bugs there.
library DamageType initializer i requires StructuredDD, Table

    //* This struct contains the data to deal damage to a target that would otherwise die when hit with double damage. This is to counter the effect of a unit
    //* instantly healing some damage due to negative spell damage, without using any unnecessary triggers.
    private struct delayDat
        unit target
        unit source
        real damage
    endstruct
    
    globals
        /// BEGIN CUSTOMIZE SECTION
        
        //* Make sure this matches the rawcode/id of your version of the DamageTypeCheck ability (you must copy/paste it from the editor to your map)
        private constant integer DAMAGE_TYPE_CHECK_ID='A003'
        
        /// END CUSTOMIZE SECTION
        
        //* One of the following 4 variables will be returned by get() depending on what type of damage was found.
        public constant integer NULLED=-1
        public constant integer ATTACK= 0
        public constant integer SPELL = 1
        public constant integer CODE  = 2
        
        private constant attacktype ATTACK_TYPE_UNIVERSAL=ConvertAttackType(7)
        private constant real DELAY_AMOUNT=0.
        private integer lastDamageType=NULLED
        private group grp=CreateGroup()
        private HandleTable delayed
    endglobals
    
    //* Use this to get the damage type in a damage event handler.
    public function get takes real sourceDamage returns integer
        if lastDamageType==CODE then
            return CODE
        elseif sourceDamage>0. then
            return ATTACK
        elseif sourceDamage<0. then
            return SPELL
        endif
        return NULLED
    endfunction
    
    //* Use this to damage units by trigger in your map without causing an infinite loop.
    public function dealCodeDamage takes unit who, unit target, real damage, damagetype how returns nothing
        local integer prevType=lastDamageType
        local real hp=GetUnitState(target,UNIT_STATE_LIFE)-.405
        local real d=-damage
        set lastDamageType=CODE
        if hp>d then
            call SetWidgetLife(target,hp-d+.405)
            call UnitDamageTarget(who,target,d,true,false,ATTACK_TYPE_UNIVERSAL,DAMAGE_TYPE_UNIVERSAL,null)
        else
            call UnitDamageTarget(who,target,1000000.+d,true,false,ATTACK_TYPE_UNIVERSAL,DAMAGE_TYPE_UNIVERSAL,null)
        endif
        set lastDamageType=prevType
    endfunction
    
    //* One of the auxillary functions for adding the dummy ability to all units.
    private function f takes nothing returns boolean
        call UnitAddAbility(GetFilterUnit(),DAMAGE_TYPE_CHECK_ID)
        return false
    endfunction
    
    //* One of the auxillary functions for adding the dummy ability to all units.
    private function c takes nothing returns boolean
        call UnitAddAbility(GetTriggerUnit(),DAMAGE_TYPE_CHECK_ID)
        return false
    endfunction
    
    //* This is the function that occurs 0. seconds after damaging a unit, in the case that damaging it by twice as much at the same time would kill it 
    //* unexpectedly.
    private function after takes nothing returns nothing
        local timer time=GetExpiredTimer()
        local delayDat tempDat=delayed[time]
        call dealCodeDamage(tempDat.source,tempDat.target,tempDat.damage,DAMAGE_TYPE_UNIVERSAL)
        call tempDat.destroy()
        call DestroyTimer(time)
        set time=null
    endfunction
    
    //* This is the function that will invert any negative spell damage. it must be StructuredDD_ddBucket.conditions[0] to function properly.
    private function handler takes nothing returns nothing
        local timer time
        local delayDat tempDat
        local real attemptedDamage=GetEventDamage()
        local unit tU
        local real sampledLife
        if get(attemptedDamage)==SPELL then
            set tU=GetTriggerUnit()
            set sampledLife=GetWidgetLife(tU)-.405
            if sampledLife>=-attemptedDamage and sampledLife<=-2*attemptedDamage then
                call SetWidgetLife(tU,sampledLife+attemptedDamage)
                set time=CreateTimer()
                set tempDat=delayDat.create()
                set tempDat.target=tU
                set tempDat.source=GetEventDamageSource()
                set tempDat.damage=attemptedDamage
                set delayed[time]=tempDat
                call TimerStart(time,DELAY_AMOUNT,false,function after)
                set time=null
            else
                call dealCodeDamage(GetEventDamageSource(),tU,2.*attemptedDamage,DAMAGE_TYPE_UNIVERSAL)
            endif
            set tU=null
        endif
    endfunction
    
    //* Initialization function to enable the system.
    private function i takes nothing returns nothing
        local region reg=CreateRegion()
        local trigger addBracer=CreateTrigger()
        call RegionAddRect(reg,bj_mapInitialPlayableArea)
        call TriggerRegisterEnterRegion(addBracer,reg,null)
        call TriggerAddCondition(addBracer,Condition(function c))
        call StructuredDD_ddBucket.addHandler(function handler)
        call GroupEnumUnitsInRect(grp,bj_mapInitialPlayableArea,Filter(function f))
        set delayed=HandleTable.create()
        set reg=null
        set addBracer=null
    endfunction
endlibrary

Here is a gallery showing exact unit health values:

http://imgur.com/a/hRZBk

And be sure to take a look at the test map - here is what you can do to test the same thing as I:
  • Turn DamageType library on or off
  • Set "Game Data Set" to what you like
  • Start the map and use storm bolt once on each dummy
  • Press escape to concatenate a string of their hp values
 

Attachments

  • DamageTypeTestEnvironment.w3x
    32.6 KB · Views: 104
Level 14
Joined
Dec 12, 2012
Messages
1,007
I didn't experience this issue in my latest tests.



I didn't experience this either.



It worked for me.

Ok, you put quite a lot work into this, so how will I put this... Ok look:

In your testmap you declare:

JASS:
//* Make sure this matches the rawcode/id of your version of the DamageTypeCheck ability (you must copy/paste it from the editor to your map)
        private constant integer DAMAGE_TYPE_CHECK_ID='A003'

But the actual ID of your damage type detection ability is 'A000' so it never gets added to any of your units on the map.

Just try this scope:

JASS:
scope test initializer i
    private function handler takes nothing returns nothing
        if DamageType_get(GetEventDamage())==DamageType_ATTACK then
            call DisplayTimedTextToPlayer( GetLocalPlayer(), 0, 0, 10.0, GetObjectName(GetUnitTypeId(GetEventDamageSource()))+" damages "+GetObjectName(GetUnitTypeId(GetTriggerUnit()))+" with PHYSICAL damage: "+"|cffff0000"+R2S(GetEventDamage())+"|r")
        elseif DamageType_get(GetEventDamage())==DamageType_SPELL then
            call DisplayTimedTextToPlayer( GetLocalPlayer(), 0, 0, 10.0, GetObjectName(GetUnitTypeId(GetEventDamageSource()))+" damages "+GetObjectName(GetUnitTypeId(GetTriggerUnit()))+" with SPELL damage: "+"|cff6495ed"+R2S(-GetEventDamage())+"|r")
        endif
    endfunction
    
    private function i takes nothing returns nothing
        call StructuredDD_ddBucket.addHandler(function handler)
    endfunction
endscope

It will allways return PHYSICAL damage, as there is never a negative damage event and therefore of course all problems that come along with it do not occur.
When I fixed the ID in your test map, I got exactly the described errors.

Greetings,
lfh
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,516
Oh my god, what an epic fail ;( That's what I get for staying up late testing this stuff.

I'll take another look at it, thanks.

Edit: Okay, so here's where I am now:

you first try spell damage, if that doesn't work you try magic damage

Why? I can't think of any examples when an ethereal unit can receive spell or magic damage but not both. It seems like one of these should be the correct answer.

Somehow, damage applied to units with a spell damage portion of more than 100%, doesn't get applied to the total amount of damage. That means, that enemy ethereal units, or units with a modified damage table (spell damage above 100%) only get a maximum of 100% damage.

This is a good spot - and I've played with the figures and decided that the best approach is to just call this a limitation of the system. Why? Because there are a few methods to fixing this:

  • You can detect the spell resistance of the unit using something like the system you've made, but this is an expensive process considering 99% of maps have static spell damage across the armor tables, and don't use ethereal at all.
  • You can hard-code the ethereal factoring so that it works standalone with vanilla warcraft, but this just adds to the customization process of the system and once again, most people don't even use ethereal in their maps.
  • You can map a list of unit types which have spell resistances of various values and check for them in the system

But the best part is, you can do any of these things completely separate from the system, and that's exactly what I've done.

The version of the script below will not properly damage ethereal units for the intended value in vanilla warcraft, but I've followed it with a demonstration of how trivial it is to do so from outside the scope of the system.

With this setup, I was able to make spell damage the same for all armor types, regardless of ethereality or DamageType being enabled.

JASS:
//*         API
//* integer NULLED => returned by get when the GetEventDamage() type can not be concluded.
//* integer ATTACK => returned by get when the GetEventDamage() is found to be a physical attack.
//* integer SPELL => returned by get when the GetEventDamage() is found to be a spell.
//* integer CODE => returned by get when the GetEventDamage() is found to be a spell.
//* public function get(real) => returns a DAMAGE_TYPE_* given GetEventDamage().
//* public function dealCodeDamage(unit,unit,real,damagetype) => causes the first unit to deal real damage of type damagetype to the second unit.
//*
//*     Installation
//* -Make sure you have an up-to-date copy of StructuredDD:
//*     [url]http://www.hiveworkshop.com/forums/jass-resources-412/system-structureddd-structured-damage-detection-216968/[/url]
//* -Make sure you have an up-to-date copy of Vexorian's Table: [url]http://www.wc3c.net/showthread.php?t=101246[/url]
//* -Make sure you copy the DamageTypeCheck unit from the object editor, and set the value of DAMAGE_TYPE_CHECK_ID to your unit's rawcode.
//* -Make sure all damage dealt by triggers in your map against units referenced by StructuredDD is dealt using dealCodeDamage.
//* -If your map contains any libraries that use StructuredDD's addHanler, set those libraries to 'require DamageType' to avoid a race condition.
//*
//*     Final Notes
//* Updated versions can be found at [url]http://www.hiveworkshop.com/forums/submissions-414/system-damagetype-structureddd-extension-228883/[/url] - please report any
//* bugs there.
library DamageType initializer i requires StructuredDD, Table

    //* This struct contains the data to deal damage to a target that would otherwise die when hit with double damage. This is to counter the effect of a unit
    //* instantly healing some damage due to negative spell damage, without using any unnecessary triggers.
    private struct delayDat
        unit target
        unit source
        real damage
    endstruct
    
    globals
        /// BEGIN CUSTOMIZE SECTION
        
        //* Make sure this matches the rawcode/id of your version of the DamageTypeCheck ability (you must copy/paste it from the editor to your map)
        private constant integer DAMAGE_TYPE_CHECK_ID='A000'
        
        /// END CUSTOMIZE SECTION
        
        //* One of the following 4 variables will be returned by get() depending on what type of damage was found.
        public constant integer NULLED=-1
        public constant integer ATTACK= 0
        public constant integer SPELL = 1
        public constant integer CODE  = 2
        
        private constant attacktype ATTACK_TYPE_UNIVERSAL=ConvertAttackType(7)
        private constant real DELAY_AMOUNT=0.
        private integer lastDamageType=NULLED
        private group grp=CreateGroup()
        private HandleTable delayed
    endglobals
    
    //* Use this to get the damage type in a damage event handler.
    public function get takes real sourceDamage returns integer
        if lastDamageType==CODE then
            return CODE
        elseif sourceDamage>0. then
            return ATTACK
        elseif sourceDamage<0. then
            return SPELL
        endif
        return NULLED
    endfunction
    
    //* Use this to damage units by trigger in your map without causing an infinite loop.
    public function dealCodeDamage takes unit who, unit target, real damage, damagetype how returns nothing
        local integer prevType=lastDamageType
        local real hp=GetUnitState(target,UNIT_STATE_LIFE)-.405
        local real d=damage
        set lastDamageType=CODE
        if hp>d then
            call SetWidgetLife(target,hp-d+.405)
            call UnitDamageTarget(who,target,0.,true,false,ATTACK_TYPE_UNIVERSAL,DAMAGE_TYPE_UNIVERSAL,null)
        else
            call UnitDamageTarget(who,target,1000000.+d,true,false,ATTACK_TYPE_UNIVERSAL,DAMAGE_TYPE_UNIVERSAL,null)
            call UnitDamageTarget(who,target,1000000.+d,true,false,ATTACK_TYPE_MAGIC,DAMAGE_TYPE_UNIVERSAL,null)
        endif
        set lastDamageType=prevType
    endfunction
    
    //* One of the auxillary functions for adding the dummy ability to all units.
    private function f takes nothing returns boolean
        local unit fU=GetFilterUnit()
        call UnitAddAbility(fU,DAMAGE_TYPE_CHECK_ID)
        call UnitMakeAbilityPermanent(fU,true,DAMAGE_TYPE_CHECK_ID)
        set fU=null
        return false
    endfunction
    
    //* One of the auxillary functions for adding the dummy ability to all units.
    private function c takes nothing returns boolean
        local unit tU=GetTriggerUnit()
        call UnitAddAbility(tU,DAMAGE_TYPE_CHECK_ID)
        call UnitMakeAbilityPermanent(tU,true,DAMAGE_TYPE_CHECK_ID)
        set tU=null
        return false
    endfunction
    
    //* This is the function that occurs 0. seconds after damaging a unit, in the case that damaging it by twice as much at the same time would kill it 
    //* unexpectedly.
    private function after takes nothing returns nothing
        local timer time=GetExpiredTimer()
        local delayDat tempDat=delayed[time]
        call dealCodeDamage(tempDat.source,tempDat.target,tempDat.damage,DAMAGE_TYPE_UNIVERSAL)
        call tempDat.destroy()
        call DestroyTimer(time)
        set time=null
    endfunction
    
    //* This is the function that will invert any negative spell damage. it must be StructuredDD_ddBucket.conditions[0] to function properly.
    private function handler takes nothing returns nothing
        local timer time
        local delayDat tempDat
        local real attemptedDamage=-GetEventDamage()
        local unit tU
        local real sampledLife
        if get(-attemptedDamage)==SPELL then
            set tU=GetTriggerUnit()
            set sampledLife=GetWidgetLife(tU)-.405
            if sampledLife>=attemptedDamage and sampledLife<=2*attemptedDamage then
                call SetWidgetLife(tU,sampledLife-attemptedDamage)
                set time=CreateTimer()
                set tempDat=delayDat.create()
                set tempDat.target=tU
                set tempDat.source=GetEventDamageSource()
                set tempDat.damage=attemptedDamage
                set delayed[time]=tempDat
                call TimerStart(time,DELAY_AMOUNT,false,function after)
                set time=null
            else
                call dealCodeDamage(GetEventDamageSource(),tU,2.*attemptedDamage,DAMAGE_TYPE_UNIVERSAL)
            endif
            set tU=null
        endif
    endfunction
    
    //* Initialization function to enable the system.
    private function i takes nothing returns nothing
        local region reg=CreateRegion()
        local trigger addBracer=CreateTrigger()
        call RegionAddRect(reg,bj_mapInitialPlayableArea)
        call TriggerRegisterEnterRegion(addBracer,reg,null)
        call TriggerAddCondition(addBracer,Condition(function c))
        call StructuredDD_ddBucket.addHandler(function handler)
        call GroupEnumUnitsInRect(grp,bj_mapInitialPlayableArea,Filter(function f))
        set delayed=HandleTable.create()
        set reg=null
        set addBracer=null
    endfunction
endlibrary

And here's how you should go about factoring something like ethereal bonus damage:

JASS:
scope EtherealBonusDamage initializer i
    globals
        private constant real BONUS_MULTIPLIER=.66
    endglobals
    
    private function h takes nothing returns nothing
        local unit tU=GetTriggerUnit()
        local real damage=GetEventDamage()
        if IsUnitType(tU,UNIT_TYPE_ETHEREAL) and DamageType_get(damage)==DamageType_SPELL then
            call DamageType_dealCodeDamage(GetEventDamageSource(),tU,-damage*BONUS_MULTIPLIER,DAMAGE_TYPE_UNIVERSAL)
        endif
        set tU=null
    endfunction
    
    private function i takes nothing returns nothing
        call StructuredDD_ddBucket.addHandler(function h)
    endfunction
endscope

Easy as π now!

One last thing -

When damaging a walker with spells, then transforming it to corporal and then killing him with a spell results in a wrong detection (physical instead of spell).

Sounds like an issue of UnitMakeAbilityPermanant - should be fixed in this version.
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
Why? I can't think of any examples when an ethereal unit can receive spell or magic damage but not both. It seems like one of these should be the correct answer.

Thats why both should be used in a general approach.


This is a good spot - and I've played with the figures and decided that the best approach is to just call this a limitation of the system. Why? Because there are a few methods to fixing this:

Well, thats of course your decision. I for myself prefer systems which I don't have to modify much. Especially with a customized armor table this will get ugly.

But thats the cool thing: everybody chooses another approach which might have its advantages or disadvantages.


  • You can detect the spell resistance of the unit using something like the system you've made, but this is an expensive process considering 99% of maps have static spell damage across the armor tables, and don't use ethereal at all.

Hm, its basically only some calculation of a few real values which should be completley insignificant to the performance, especially as the calculation only happens with spell damage.

And here's how you should go about factoring something like ethereal bonus damage:

Just to mention it, this will also not work as intended ;)

The damage factor of ethereality only gets applied to enemy units, so you would have to check too if the target is an enemy.

Greetings,
lfh
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,516
Thats why both should be used in a general approach.

I don't understand. Why would you want to call two lines if one will suffice?

Hm, its basically only some calculation of a few real values which should be completley insignificant to the performance, especially as the calculation only happens with spell damage.

If I have some time I'll take some metrics and come to a conclusion, but for now I'm still open to the idea of detecting this. I'd love some input from other experienced users as well.

Just to mention it, this will also not work as intended ;)

The damage factor of ethereality only gets applied to enemy units, so you would have to check too if the target is an enemy.

So you're saying it will not work if you cast a damaging spell on an ethereal ally (like flamestrike/blizzard on a friendly spirit walker?) I'm willing to accept that small problem as a limitation as well.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
I don't understand. Why would you want to call two lines if one will suffice?

Well, it depends how you define the your requirements for a function called DealFixDamage. For me it is: Deal the given amount of damage independend of anything. So no matter what the user does with resistances, armor values etc., the given damage is always applied.

Now, as a user could set the damage table for spell damage to 0.0, a unit would not take damage by spell damage. This is normally no problem, as they all can get damaged by the ConvertAttacktype(7), with the only exception of ethereal units.

So if you have an ethereal unit with damage table set to 0.0 for spell damage it won't get killed by DealFixDamage. Thats why you should deal magic damage if spell damage was blocked. Of course this is no perfect solution as magic damage could be also set to 0.0, but an ethereal unit with 0.0 spell and magic damage can not be damaged by any ingame mechanism (at least AFAIK). So we can assume this will never happen in any map and therefore the approach is ok. I mean you can also just forget this, as it will most likely never happen in any map, but thats the reason why I put that second line there.

But this has all nothing to do with the Damage Type Detection system, as units from whose you want to detect spell damage have to affected by spell damage anyway, else you have a zero damage event which gets filtered out.


So you're saying it will not work if you cast a damaging spell on an ethereal ally (like flamestrike/blizzard on a friendly spirit walker?) I'm willing to accept that small problem as a limitation as well.

Yes, the damage bonus of ethereality only gets applied to enemy units, not to allied units. But thats not a limitation, you can solve it easily in your scope, I just wanted to mention it. Just put:

JASS:
if IsUnitType(tU,UNIT_TYPE_ETHEREAL) and IsUnitEnemy(tU, GetOwningPlayer(GetEventDamageSource())) and DamageType_get(damage)==DamageType_SPELL then
    call DamageType_dealCodeDamage(GetEventDamageSource(),tU,-damage*BONUS_MULTIPLIER,DAMAGE_TYPE_UNIVERSAL)
endif

instead of

JASS:
if IsUnitType(tU,UNIT_TYPE_ETHEREAL) and DamageType_get(damage)==DamageType_SPELL then
    call DamageType_dealCodeDamage(GetEventDamageSource(),tU,-damage*BONUS_MULTIPLIER,DAMAGE_TYPE_UNIVERSAL)
endif
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,516
Well, it depends how you define the your requirements for a function called DealFixDamage. For me it is: Deal the given amount of damage independend of anything. So no matter what the user does with resistances, armor values etc., the given damage is always applied.

Now, as a user could set the damage table for spell damage to 0.0, a unit would not take damage by spell damage. This is normally no problem, as they all can get damaged by the ConvertAttacktype(7), with the only exception of ethereal units.

So if you have an ethereal unit with damage table set to 0.0 for spell damage it won't get killed by DealFixDamage. Thats why you should deal magic damage if spell damage was blocked. Of course this is no perfect solution as magic damage could be also set to 0.0, but an ethereal unit with 0.0 spell and magic damage can not be damaged by any ingame mechanism (at least AFAIK). So we can assume this will never happen in any map and therefore the approach is ok. I mean you can also just forget this, as it will most likely never happen in any map, but thats the reason why I put that second line there.

But this has all nothing to do with the Damage Type Detection system, as units from whose you want to detect spell damage have to affected by spell damage anyway, else you have a zero damage event which gets filtered out.

That makes more sense now. Basically we have to use both spells and magic because there is not "non-physical" equivalent of the "physical" ConvertAttackType(7). I'll keep this in mind.

Yes, the damage bonus of ethereality only gets applied to enemy units, not to allied units. But thats not a limitation, you can solve it easily in your scope, I just wanted to mention it. Just put:

JASS:
if IsUnitType(tU,UNIT_TYPE_ETHEREAL) and IsUnitEnemy(tU, GetOwningPlayer(GetEventDamageSource())) and DamageType_get(damage)==DamageType_SPELL then
    call DamageType_dealCodeDamage(GetEventDamageSource(),tU,-damage*BONUS_MULTIPLIER,DAMAGE_TYPE_UNIVERSAL)
endif

instead of

JASS:
if IsUnitType(tU,UNIT_TYPE_ETHEREAL) and DamageType_get(damage)==DamageType_SPELL then
    call DamageType_dealCodeDamage(GetEventDamageSource(),tU,-damage*BONUS_MULTIPLIER,DAMAGE_TYPE_UNIVERSAL)
endif

Ah okay, I understand now. You're saying that in wc3, flamestrike will do 166% damage to enemy ethereal units, but only 100% damage to friendly ethereal units. Right?

----

I've been doing some testing of the GetSpellResistance() function. It's even more expensive than I thought, which is a huge shame. I think I will consider including it as an optional extension.

Here are the results of my test (W7-x64 SP1, Intel [email protected], 6GB DDR3-SDRAM):

Code:
DamageType_dealCodeDamage():	60fps at 20,000 iterations per second
				30fps at 25,000 iterations per second

GetSpellResistance_fromUnit():	60fps at 5,000 iterations per second
				18fps at 13,000 iterations per second

I also did some tests of the two in parallel, vs dealCodeDamage in parallel with DoNothing().

And here is my test map:
 

Attachments

  • getSpellResistanceTest.w3x
    35.2 KB · Views: 87
Level 14
Joined
Dec 12, 2012
Messages
1,007
I also did some tests of the two in parallel, vs dealCodeDamage in parallel with DoNothing().

Ok, now this is interesting, I didn't expect that.

I mean 5000 spell damage events per second are allready beyond any realistic value, but I think I will make it then optional too.
Not as an extra extension but just a boolean flag like private constant boolean USE_SPELL_RESISTANCE_AUTO_DETECT or
something like that, so that it is easier to use for people.
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,516
Ok, now this is interesting, I didn't expect that.

I mean 5000 spell damage events per second are allready beyond any realistic value, but I think I will make it then optional too.
Not as an extra extension but just a boolean flag like private constant boolean USE_SPELL_RESISTANCE_AUTO_DETECT or
something like that, so that it is easier to use for people.

Yep, that's exactly what I was thinking.

---

Anyway, now that I've figured out all the internal code to implement, the only other thing I am considering for this system at the moment is API.

Therefore I'd like some opinions:

  1. Do you prefer the API style DamageType.get() over the current style?
  2. Is there any functionality which has not henceforth been discussed which could be added to the API?
 
Top