- Joined
- Jan 9, 2005
- Messages
- 2,130
Since there are now events for changing damage without requiring a cheat death ability (which also makes importing less annoying) I figured I'd make this DDS since no one else (except Bribe, whose system is more GUI-friendly) seems to have done it. We don't quite have a spell detection boolean, however, so this still uses the Runed Bracers trick.
Requires Warcraft III version 1.29 or newer.
PS: If you have another idea for a name for this system, I'd love to hear it, if only to avoid confusion with Nestharus' own DamageEvent.
Demo:
Hopefully I haven't missed anything!
Merry Christmas
Requires Warcraft III version 1.29 or newer.
PS: If you have another idea for a name for this system, I'd love to hear it, if only to avoid confusion with Nestharus' own DamageEvent.
JASS:
library DamageEvent /* v1.02.1
by Spellbound
DESCRIPTION
¯¯¯¯¯¯¯¯¯¯¯
DamageEvent is a damage detection system that uses the new native functions that were added with
patch 1.29. Detecting damage and even changing it is now easier than before.
REQUIREMENTS
¯¯¯¯¯¯¯¯¯¯¯¯
*/ uses /*
*/ UnitDex /* Needed to setup units for damage detection and cleanup when they are removed.
// https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
*/ optional /*
*/ RegisterNativeEvent // Recommended if you wish to have finer control over your damage event triggers
// https://www.hiveworkshop.com/threads/snippet-registerevent-pack.250266/
/*
INSTALLATION
¯¯¯¯¯¯¯¯¯¯¯¯
Import UnitDex into your map and then copy the ability Spell Damage Detection (DamageEvent). If
able, change the spell id to SPDD. Otherwise, chance the value of SPELL_DMG_DETECT in the globals
block below to that of Spell Damage Detection (DamageEvent). Ctrl + D allows you to view object
ids in the object editor.
Copy this library into your map and you are ready to start using it.
NOTES
¯¯¯¯¯
1) Spell damage detection works by giving all units a modified Runed Bracer ability that reduces
all spell damage by 200%, thus taking it into the negative. When DamageEvent detects a negative
number it assumes it's magical and sets DmgEvent.class to DAMAGE_CLASS_SPELL and then returns the
damage to a positive number. If you intend to heal using a modified damage-based spell (eg Storm
Bolt), the damage will become a positive number and will instead be detected as physical damage.
Any additional spell-damage reduction like Elune's Grace will still affect the Storm Bolt's
damage but the system will believe it's phyiscal. Healing through negative spell damage won't
work - you will have to trigger it.
2) I have substituted the word 'type' to 'class' to avoid confusion with the native variable type
called damagetype. This is because DamageEvent comes with a custom function to damage targets
that takes a DamageClass instead of a weapontype. An extended version exists that allows you to
input a weapontype as well, should you find the need for it. With this custom damage function,
your DamageClass is preset and when an onDamage event fires, DmgEvent.class will return that
instead of just DAMAGE_CLASS_PHYSICAL or DAMAGE_CLASS_SPELL. You may also use that to force the
system to detect a phyiscal damage as spell by setting it's class to DAMAGE_CLASS_SPELL,
although it will still not be subject to vanilla effects that reduce spell damage.
3) Runed Bracers and Elune's Grace will not work. They have to be triggered.
4) Mana Shield will fail to trigger a damage event. Set all damage reduction values to zero, and
trigger the effects or use the fix provided in onDamage.
5) Life Drain will not do anything. Trigger it or use the fix provided.
6) Damage Return Factor on Locust Swarm must be set to a negative number, or else the Locusts
will damage the Crypt Lord rather than heal him when they return to him.
7) Anti-Magic Shell with a shield life will not work and must be triggered.
API
¯¯¯
EVENT
if using RegisterNativeEvent...
call RegisterNativeEvent(EVENT_PLAYER_UNIT_DAMAGED, function whichFunction)
^ Registers a DamageEvent event that will run whichFunction whenever a unit takes
damage. If a unit is not indexed, it will not fire the event when it takes damage.
if NOT using RegisterNativeEvent...
call DamageEvent.register(function whichFunction)
^ Registers a DamageEvent event that will run whichFunction whenever a unit takes
damage. If a unit is not indexed, it will not fire the event when it takes damage.
EVENT RESPONSE
/* IMPORTANT: /* Always use a LOCAL DamageEvent variable */*/
eg:
local DamageEvent Damage = DmgEvent
you can then proceed to modify its value as you wish:
Damage.amount is the amount inflicted after reduction via armour type and rating. This
variable can be modified to change damage inflicted.
Damage.class is the damage type - either Physical or Spell, but new ones can be added.
Damage.source is the one inflicting the damage
Damage.target is the unit being damaged
Damage.default is a boolean that informs the system that DamageClassOverride is not zero.
This will allow you to check is a damage was inflicted normally (via attack or spell)
and not via triggers, specifically, through UnitApplyDamage() or UnitApplyDamageEx().
UnitDamageTarget() will still be treated as default physical damage.
UTILITY
set YOUR_CUSTOM_DAMAGE_CLASS = DamageClass.create()
^ You can create custom damage classes this way. This system only comes with PHYSICAL and
SPELL, but should you need to, you can make new ones that have their own interactions. An
example would be a system of elemental damage.
Ref NOTES 2) for an explanation of what a damage 'class' is.
call UnitApplyDamage(whichSource, whichTarget, whichDamage, isAttack, isRanged, attackType, damageType, damageClass)
^ Use this instead of UnitDamageTarget. It takes the same arguments, except the last
one is used for your custom DamageClass.
call UnitApplyDamageEx(whichSource, whichTarget, whichDamage, isAttack, isRanged, attackType, damageType, weapontype, damageClass)
^ Use this instead of UnitDamageTarget. It takes the same arguments in addition of a
DamageClass argument.
*/
globals
private constant integer SPELL_DMG_DETECT = 'SPDD'
private constant real ETHEREAL_FACTOR = 1.6666
private constant real REFRESH_PERIOD = 30.
// Do not edit those EVER or everything will break. You have been warned.
DamageClass DAMAGE_CLASS_PHYSICAL = 0
DamageClass DAMAGE_CLASS_SPELL = 0
private DamageClass DamageClassOverride = 0
DamageEvent DmgEvent = 0
private integer Count = 0
private trigger RegTrig = CreateTrigger()
private trigger RunTrig = CreateTrigger()
private timer Clock = CreateTimer()
integer EVENT_PLAYER_UNIT_DAMAGED = 0
endglobals
struct DamageClass
static method create takes nothing returns thistype
return allocate()
endmethod
endstruct
struct DamageEvent
readonly unit source
readonly unit target
readonly DamageClass class
readonly boolean default
real amount
private method destroy takes nothing returns nothing
set this.source = null
set this.target = null
set this.default = false
call this.deallocate()
endmethod
static method onDamage takes nothing returns boolean
local real amt = GetEventDamage()
local thistype this
static if LIBRARY_RegisterNativeEvent then
local integer playerId
endif
if amt == 0. then
return false
endif
set this = allocate()
set DmgEvent = this
set this.source = GetEventDamageSource()
set this.target = GetTriggerUnit()
if amt > 0. then
set this.class = DAMAGE_CLASS_PHYSICAL
elseif amt < 0. then
set this.class = DAMAGE_CLASS_SPELL
if IsUnitType(this.target, UNIT_TYPE_ETHEREAL) then
set amt = amt * ETHEREAL_FACTOR
endif
set amt = -amt
endif
// if a custom damage class was assigned...
if DamageClassOverride != 0 then
set this.class = DamageClassOverride
set this.default = false
else
set this.default = true
endif
set this.amount = amt
static if LIBRARY_RegisterNativeEvent then
set playerId = GetPlayerId(GetOwningPlayer(this.target))
call TriggerEvaluate(GetNativeEventTrigger(EVENT_PLAYER_UNIT_DAMAGED))
if IsNativeEventRegistered(playerId, EVENT_PLAYER_UNIT_DAMAGED) then
call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, EVENT_PLAYER_UNIT_DAMAGED))
endif
else
if IsTriggerEnabled(RunTrig) then
call TriggerEvaluate(RunTrig)
endif
endif
call BlzSetEventDamage(this.amount)
set DmgEvent = 0
call this.destroy()
return false
endmethod
static method register takes code c returns nothing
call TriggerAddCondition(RunTrig, Condition(c))
endmethod
endstruct
private struct Link
unit u
thistype prev
thistype next
method destroy takes nothing returns nothing
set this.u = null
call this.deallocate()
endmethod
static method create takes unit u returns thistype
local thistype this = allocate()
set this.u = u
set this.prev = 0
set this.next = 0
return this
endmethod
endstruct
private module DamageEventInit
private static method onInit takes nothing returns nothing
call TriggerAddCondition(RegTrig, Filter(function DamageEvent.onDamage))
call DisableTrigger(RegTrig)
// If you use a different Unit Indexer, change those two lines below
call OnUnitIndex(function thistype.onIndex)
call OnUnitDeindex(function thistype.onRemove)
set DAMAGE_CLASS_PHYSICAL = DamageClass.create()
set DAMAGE_CLASS_SPELL = DamageClass.create()
static if LIBRARY_RegisterNativeEvent then
set EVENT_PLAYER_UNIT_DAMAGED = CreateNativeEvent()
endif
endmethod
endmodule
private struct DamageEventRegister
private static Link First
private static Link Last
private static Link array LinkId
private static method remove takes unit u returns nothing
local Link link = LinkId[GetUnitId(u)]
if link == First then
set First = link.next
set link.next.prev = 0
elseif link == Last then
set Last = link.prev
set link.prev.next = 0
else
set link.prev.next = link.next
set link.next.prev = link.prev
endif
set Count = Count - 1
if Count == 0 then
call PauseTimer(Clock)
call DisableTrigger(RegTrig)
endif
endmethod
private static method refresh takes nothing returns nothing
local Link link = First
call DestroyTrigger(RegTrig)
set RegTrig = CreateTrigger()
loop
exitwhen link == 0
call TriggerRegisterUnitEvent(RegTrig, link.u, EVENT_UNIT_DAMAGED)
set link = link.next
endloop
call TriggerAddCondition(RegTrig, Filter(function DamageEvent.onDamage))
endmethod
private static method add takes unit u returns Link
local Link link = Link.create(u)
set Count = Count + 1
if Count == 1 then
set First = link
set Last = link
call TimerStart(Clock, REFRESH_PERIOD, true, function thistype.refresh)
call EnableTrigger(RegTrig)
else
set link.prev = Last
set Last.next = link
endif
set Last = link
set link.next = 0
call UnitAddAbility(u, SPELL_DMG_DETECT)
call UnitMakeAbilityPermanent(u, true, SPELL_DMG_DETECT)
return link
endmethod
private static method onIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
call TriggerRegisterUnitEvent(RegTrig, u, EVENT_UNIT_DAMAGED)
set LinkId[GetIndexedUnitId()] = thistype.add(u)
set u = null
endmethod
private static method onRemove takes nothing returns nothing
local integer id = GetIndexedUnitId()
call thistype.remove(GetIndexedUnit())
call LinkId[id].destroy()
set LinkId[id] = 0
endmethod
implement DamageEventInit
endstruct
function UnitApplyDamage takes unit source, unit target, real damage, boolean attack, boolean ranged, attacktype atk, damagetype dmg, DamageClass dc returns nothing
set DamageClassOverride = dc
call UnitDamageTarget(source, target, damage, attack, ranged, atk, dmg, null)
set DamageClassOverride = 0
endfunction
function UnitApplyDamageEx takes unit source, unit target, real damage, boolean attack, boolean ranged, attacktype atk, damagetype dmg, weapontype wpt, DamageClass dc returns nothing
set DamageClassOverride = dc
call UnitDamageTarget(source, target, damage, attack, ranged, atk, dmg, wpt)
set DamageClassOverride = 0
endfunction
endlibrary
Demo:
JASS:
scope onDamage
struct onDamage
private static method onDamage takes nothing returns nothing
local DamageEvent Damage = DmgEvent
local unit source = Damage.source
local unit target = Damage.target
local real damage = Damage.amount
local DamageClass class = Damage.class
// Life Drain
local real healAmount
// Mana Shield
local real mana
local real effectiveMana
local real manaMult
local real dmgReduction
local real dmgToMana
local real dmgToHP
local real dmgResult
local integer manaShieldHero
// Mana Shield fix - based of Bribe's fix from Damage Engine
if GetUnitAbilityLevel(target, 'BNms') > 0 then
set mana = GetUnitState(target, UNIT_STATE_MANA)
set manaShieldHero = GetUnitAbilityLevel(target, 'ANms')
// Hero Mana Sheild (ANms)
if manaShieldHero > 0 then
// find the mana multiplier. If manaMult == 4. then it reduces 1 hp for every 4 points of mana.
set manaMult = .5 + .5 * manaShieldHero
// This will mimic the Damage Absorbed field.
set dmgReduction = 1.
// Unit Mana Shield (ACmf)
else
set manaMult = 2.
set dmgReduction = 1.
endif
set dmgToMana = damage * dmgReduction
set dmgToHP = damage - dmgToMana
set damage = dmgToHP
set effectiveMana = mana * manaMult
if dmgToMana <= effectiveMana then
set effectiveMana = effectiveMana - dmgToMana
set dmgResult = 0.
set mana = effectiveMana / manaMult
else
set dmgResult = dmgToMana - effectiveMana
call IssueImmediateOrderById(target, 852590) // order manashieldoff
set mana = 0.
endif
call SetUnitState(target, UNIT_STATE_MANA, mana)
set damage = damage + dmgResult
set Damage.amount = damage
endif
// if PHYSICAL damage
if Damage.class == DAMAGE_CLASS_PHYSICAL then
call PoppingText.create(I2S(R2I(damage)), GetUnitX(target), GetUnitY(target), 120., 10., .15, COLOUR_PHYSICAL_DMG, GetOwningPlayer(source), VISIBILITY_ALL)
// if SPELL damage
elseif Damage.class == DAMAGE_CLASS_SPELL then
call PoppingText.create(I2S(R2I(damage)), GetUnitX(target), GetUnitY(target), 120., 10., .2, COLOUR_MAGICAL_DMG, GetOwningPlayer(source), VISIBILITY_ALL)
// put your spell damage reduction here
// Life Drain fix - vanilla values
if GetUnitAbilityLevel(source, 'Bdcl') > 0 and GetUnitAbilityLevel(target, 'Bdtl') > 0 then
if GetUnitAbilityLevel(source, 'ANdr') > 0 then // hero life drain ability check
set healAmount = 10. + 15. * GetUnitAbilityLevel(source, 'ANdr')
else
set healAmount = 55.
endif
call SetWidgetLife(source, GetWidgetLife(source) + healAmount)
endif
// if CUSTOM damage class
else
endif
set source = null
set target = null
endmethod
private static method onInit takes nothing returns nothing
static if LIBRARY_RegisterNativeEvent then
call RegisterNativeEvent(EVENT_PLAYER_UNIT_DAMAGED, function thistype.onDamage)
else
call DamageEvent.register(function thistype.onDamage)
endif
endmethod
endstruct
endscope
- v1.02.1 updated documentation to explain how to properly use DamageEvent (use a local variable). DamageEvent variable renamed from Damage to DmgEvent. Also fixed a couple of typos.
- v1.02 Fixed an issue where the links of the list holding all instanced units was not being removed when a unit was deindexed.
- v1.01 Fixed an issue where the wrong node in the global unit list was being destroyed.
- v1.00 initial release.
- v1.02 Fixed an issue where the links of the list holding all instanced units was not being removed when a unit was deindexed.
- v1.01 Fixed an issue where the wrong node in the global unit list was being destroyed.
- v1.00 initial release.
Hopefully I haven't missed anything!
Merry Christmas
Attachments
Last edited: