Cokemonkey11
Spell Reviewer
- Joined
- May 9, 2006
- Messages
- 3,575
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:
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:
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:
API:
The script:
Example Test Scope:
(Yes, it's that simple)
Installation:
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
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:
Todo:
Add functionality for dealing damage by code which is influenced by armor tables, by utilizing
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:
- All units are given a modified version of Spell Damage Reduction, set to 200% reduction.
- A Damage Detection handler recognizes negative damage as resulting from spell damage reduction, and inverts it.
- 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 mustrequires 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 typeDamageType.ATTACK
- Value returned when DamageType recognizes the damage type as a physical attackDamageType.SPELL
- Value returned when DamageType recognizes the damage type as a spellDamageType.CODE
- Value returned when DamageType recognizes the damage type as having been dealt viaDamageType.dealCodeDamage()
(See Methods)
DamageType.get()
- Requests what type of damage was dealt in the scope of a StructuredDD handlerDamageType.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:
- Make sure you have an up-to-date copy of StructuredDD installed http://www.hiveworkshop.com/forums/...uctureddd-structured-damage-detection-216968/
- Make sure you have an up-to-date copy of Vexorian's Table installed http://www.wc3c.net/showthread.php?t=101246
- 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.
- Make sure all damage dealt by triggers in your map against units referenced by StructuredDD is dealt using
DamageType.dealCodeDamage()
. - If your map contains any libraries that use
StructuredDD.addHandler
, set those libraries torequires 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
Last edited: