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

Physical Damage Detection for GUI v1.3.0.0

1. Description

This is a Damage Detection System (DDS) that can differentiate between Physical, Spell and Code damage. It also allows you to modify the damage applied on the target, e.g. increase, decrease, invert or block the damage completly. Finally you can also allocate attacks directly from a running damage event so that its very easy to reflect all spell damage for example.

The system is written in pure JASS and realizes damage handler functions with a variable event. This means that GUI users can easily use this and code their whole damage handler functions in GUI. There is no need for custom scripts with the only exception of allocating attacks from a running damage event (Compare the OnDamage trigger).

There is also a vJASS Version of this system available.


2. System Code and how to use

Enclosed is the system code and an example OnDamage trigger. Just use the variable event "damageEventTrigger becomes equal to 1.0" to add a new damage handler. The OnDamage trigger shown here is from the testmap and shows how easy you can detect the damage type and modify the incoming damage. In this testmap, the following damage modifiers were used:

  • The Archmage deals 0 physical damage.
  • The Mountain King deals double spell damage.
  • The Blademaster heals 50 hitpoints with every hit.
  • The Kodo Beast will reflect 50 damage.

JASS:
////////////////////////////////////////////////////////////////////////////////////
//
//  Physical Damage Detection Engine GUI v 1.3.0.0
//  ----------------------------------------------
//  By looking_for_help aka eey
//
//  This system is able to detect, modify and deal damage. It can differentiate
//  between physical and spell damage, as well as block, reduce or increase the
//  applied damage to a desired value. You can also allocate damage events from
//  running damage events.
//
//  This is the GUI version of the system, meaning that you can use this with the
//  standard editor and basically without any knowledge of JASS.
//
////////////////////////////////////////////////////////////////////////////////////
//
//  Implementation
//  --------------
//  1.  Create all variables that this system uses (compare the variable editor).
//      You don't have to set them to specific values, they get initialized
//      automatically by the system so just create them.
//  2.  Copy this trigger to your map. You can then add damage handlers by using
//      the event "value of real variable becomes equal to 1" with the variable
//      udg_PDD_damageEventTrigger. Compare the OnDamage trigger for an example usage.
//  3.  Copy the two custom abilities to your map and make sure they have the
//      correct ID. You can specify the ID in the function InitGlobalVariables
//      above.
//  4.  Go to the locust swarm ability and invert its damage return portion
//      from (default) 0.75 to -0.75.
//  5.  Remove the spell damage reduction ability from the spell damage reduction
//      items you use (runed bracers). You can configure the resistance of this
//      item in the InitGlobalVariables function, modifying the global variable
//      udg_PDD_BRACERS_SPELL_DMG_RED.
//
////////////////////////////////////////////////////////////////////////////////////
//
//  Important Notes
//  ---------------
//  1.  Life Drain does not work with this system, so you should use a triggered
//      version of this spell if you want to use it.
//  2.  Same for Finger of Death, if you want to use this spell bug free with this
//      system, you should use a triggered version of it.
//  3.  If you use damage modifiers by setting the damage amount variable, you have
//      to use GetUnitLife, GetUnitMaxLife and SetUnitLife instead of GetWidgetLife,
//      GetUnitState for UNIT_STATE_MAX_LIFE and SetWidgetLife in your whole map to
//      ensure there occure no bugs.
//  4.  The boolean udg_PDD_SPELL_RESIST_AUTO_DETECT is only neccessary set to true
//      if you want to use a customized damage table with spell damage resistance
//      above 100%. If this is not the case, it should be set to false, as the
//      system is faster then and still works correct.
//  5.  As already mentioned you can't use the spell reduction ability when using this
//      system (runed bracers and elunes grace). If you want to use them, you can
//      trigger them by using the damage modifiers. Runed bracers is already considered
//      in this system, so you don't have to code it.
//
////////////////////////////////////////////////////////////////////////////////////
//
//  System API
//  ----------
//  real damageEventTrigger
//      - Use the event "value of real variable becomes equal to 1" to detect a damage
//        event. In this event you can use the following API. Compare the OnDamage
//        trigger for an example usage.
//
//  unit target
//      - In this global unit variable, the damaged unit is saved. Don't write any-
//        thing into this variable, use it as readonly.
//
//  unit source
//      - In this global unit variable, the damage source is saved. Don't write any-
//        thing into this variable, use it as readonly.
//
//  real amount
//      - In this global real variable, the amount of damage is saved. This amount
//        can be modified by simply setting it to the desired amount that should be
//        applied to the target. Set the amount to 0.0 to block the damage completly.
//        Negative values will result in heal.
//
//  integer damageType
//      - In this global integer variable, the damage type of the current damage is
//        saved. Use it to differentiate between physical, spell and code damage. The
//        variable can takes the values PHYSICAL == 0, SPELL == 1 and CODE == 2. Don't
//        write anything into this variable, use it as readonly.      
//
//  function GetUnitLife takes unit u returns real
//      - Use this function instead of the GetWidgetLife native. It ensures that you
//        get the correct health value, even when damage modifiers are applied. If
//        you don't use damage modifiers at all, you don't need this function.
//
//  function GetUnitMaxLife takes unit u returns real
//      - Use this function instead of the GetUnitState(u, UNIT_STATE_MAX_LIFE) native.
//        It will return the correct value, even when damage modifiers are applied. If
//        you don't use damage modifiers at all, you don't need this function.
//
//  function SetUnitLife takes unit u, real newLife returns nothing
//      - Use this function instead of the SetWidgetLife(u, newLife) native if you use
//        damage modifiers in your map. Same rules as for the GetUnitMaxLife and the
//        GetUnitMaxLife functions.
//
//  function UnitDamageTargetEx takes unit localSource, unit localTarget, real localAmount, boolean attack, boolean ranged, attacktype localAttackType, damagetype localDamageType, weapontype localWeaponType returns boolean
//      - Use this function to deal damage from a running damage event. It works exactly
//        the same as the native UnitDamageTarget but is recursion safe so that you can
//        realize damage reflection for example with this. Don't ever use this function
//        outside of an onDamage event.
//
//  function AddDamageHandler takes code damageHandler returns nothing
//      - Allows you to add a damage handler function to the system. This is not
//        required for GUI users, only for vanilla JASS users. GUI users should
//        use the real variable damageEventTrigger to add damage handlers to this
//        system as explained above.
//
//  function RemoveDamageHandler takes code damageHandler returns nothing
//      - Allows you to remove a damage handler function from the system dynamic-
//        ally. If you added the same handler function multiple times to the sys-
//        tem, this function will remove all of these equal functions. This is not
//        required for GUI users, only for vanilla JASS users.
//
//////////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////////
//  Configurable globals
//////////////////////////////////////////////////////////////////////////////////

function InitGlobalVariables takes nothing returns nothing
    // Put here the correct ability IDs
    set udg_PDD_DAMAGE_TYPE_DETECTOR = 'A000'
    set udg_PDD_SET_MAX_LIFE = 'A001'
 
    // Here you can configure some stuff, read the documentation for further info
    set udg_PDD_SPELL_DMG_REDUCTION_ITEM = 'brac'
    set udg_PDD_SPELL_RESIST_AUTO_DETECT = false
    set udg_PDD_ETHEREAL_DAMAGE_FACTOR = 1.66
    set udg_PDD_BRACERS_SPELL_DMG_RED = 0.33
    set udg_PDD_TRIGGER_CLEANUP_PERIOD = 60.0
endfunction

//////////////////////////////////////////////////////////////////////////////////
//  End of configurable globals
//////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////
//  User functions
//////////////////////////////////////////////////////////////////////////////////

function AddDamageHandler takes code func returns nothing
    local integer id = GetHandleId(Condition(func))
    local integer index = 0
 
    // Loop to manage equal damage handlers
    loop
        exitwhen ( LoadTriggerConditionHandle(udg_PDD_h, id, index) == null )
        set index = index + 1
    endloop
    // Store the desired damage handler function
    call SaveTriggerConditionHandle(udg_PDD_h, id, index, TriggerAddCondition(udg_PDD_damageHandler, Filter(func)))
endfunction

function RemoveDamageHandler takes code func returns nothing
    local integer id = GetHandleId(Condition(func))
    local integer index = 0
 
    // Loop through all equal damage handlers
    loop
        exitwhen ( LoadTriggerConditionHandle(udg_PDD_h, id, index) == null )
        call TriggerRemoveCondition(udg_PDD_damageHandler, LoadTriggerConditionHandle(udg_PDD_h, id, index))
        set index = index + 1
    endloop
 
    // Clean things up
    call FlushChildHashtable(udg_PDD_h, id)
endfunction

function GetUnitLife takes unit u returns real
    local boolean duringModification
    local integer uId = GetHandleId(u)
    local real health
    local real storedHealth = LoadReal(udg_PDD_h, uId, 2)
    local real storedDamage = LoadReal(udg_PDD_h, uId, 1)
 
    // Check if the unit is being rescued from damage
    set duringModification = GetUnitAbilityLevel(u, udg_PDD_SET_MAX_LIFE) > 0
    if duringModification then
        call UnitRemoveAbility(u, udg_PDD_SET_MAX_LIFE)
    endif

    // Get the correct health value of the unit
    if storedHealth != 0.0 then
        set health = storedHealth - storedDamage
    else
        set health = GetWidgetLife(u) - storedDamage
    endif

    // Restore the rescue ability and return
    if duringModification then
        call UnitAddAbility(u, udg_PDD_SET_MAX_LIFE)
    endif
    return health
endfunction

function GetUnitMaxLife takes unit u returns real
    local real maxHealth
 
    // Check if the unit is being rescued from damage
    if GetUnitAbilityLevel(u, udg_PDD_SET_MAX_LIFE) > 0 then
        call UnitRemoveAbility(u, udg_PDD_SET_MAX_LIFE)
        set maxHealth = GetUnitState(u, UNIT_STATE_MAX_LIFE)
        call UnitAddAbility(u, udg_PDD_SET_MAX_LIFE)
        return maxHealth
    endif
 
    // If the unit isn't being rescued, use the standard native
    return GetUnitState(u, UNIT_STATE_MAX_LIFE)
endfunction

function SetUnitLife takes unit u, real newLife returns nothing
    local integer targetId
    local integer oldTimerId
    local real oldHealth

    // Check if the unit is being rescued from damage
    if GetUnitAbilityLevel(u, udg_PDD_SET_MAX_LIFE) > 0 then
        call UnitRemoveAbility(u, udg_PDD_SET_MAX_LIFE)
        call SetWidgetLife(u, newLife)
        call UnitAddAbility(u, udg_PDD_SET_MAX_LIFE)
     
        // Get the unit specific timer information
        set targetId = GetHandleId(u)
        set oldHealth = LoadReal(udg_PDD_h, targetId, 0)
     
        // Update the unit specific timer information
        if oldHealth != 0.0 then
            set oldTimerId = LoadInteger(udg_PDD_h, targetId, 3)
            call SaveReal(udg_PDD_h, targetId, 2, newLife)
            call SaveReal(udg_PDD_h, targetId, 0, newLife)
            call SaveReal(udg_PDD_h, oldTimerId, 4, newLife)
        endif
        return
    endif
 
    // If the unit isn't being rescued, use the standard native
    call SetWidgetLife(u, newLife)
endfunction

function UnitDamageTargetEx takes unit localSource, unit localTarget, real localAmount, boolean attack, boolean ranged, attacktype localAttackType, damagetype localDamageType, weapontype localWeaponType returns boolean
    // Avoid infinite loop due to recursion
    if udg_PDD_damageType == udg_PDD_CODE then
        return false
    endif
 
    // Avoid allocating attacks on units that are about to be killed
    if ( localTarget == udg_PDD_target and GetUnitLife(localTarget) - udg_PDD_amount < udg_PDD_UNIT_MIN_LIFE ) then
        return false
    endif
 
    // Store all damage parameters determined by the user
    set udg_PDD_allocatedAttacks = udg_PDD_allocatedAttacks + 1
    call SaveUnitHandle(udg_PDD_h, udg_PDD_allocatedAttacks, 0, localSource)
    call SaveUnitHandle(udg_PDD_h, udg_PDD_allocatedAttacks, 1, localTarget)
    call SaveReal(udg_PDD_h, udg_PDD_allocatedAttacks, 2, localAmount)
    call SaveBoolean(udg_PDD_h, udg_PDD_allocatedAttacks, 3, attack)
    call SaveBoolean(udg_PDD_h, udg_PDD_allocatedAttacks, 4, ranged)
    call SaveInteger(udg_PDD_h, udg_PDD_allocatedAttacks, 5, GetHandleId(localAttackType))
    call SaveInteger(udg_PDD_h, udg_PDD_allocatedAttacks, 6, GetHandleId(localDamageType))
    call SaveInteger(udg_PDD_h, udg_PDD_allocatedAttacks, 7, GetHandleId(localWeaponType))

    // Return true if the damage was allocated
    return true
endfunction

////////////////////////////////////////////////////////////////////////////////////
//  Sub functions
////////////////////////////////////////////////////////////////////////////////////

function DealFixDamage takes unit source, unit target, real pureAmount returns nothing
    local real MAX_DAMAGE = 1000000.0
    local real beforeHitpoints
    local real newHitpoints

    // Ensure the amount is positive
    if pureAmount < 0 then
        set pureAmount = -pureAmount
    endif

    // Save the targets hitpoints
    set beforeHitpoints = GetWidgetLife(target)
    set newHitpoints = beforeHitpoints - pureAmount

    // Apply the desired, fixed amount
    if newHitpoints >= udg_PDD_UNIT_MIN_LIFE then
        call SetUnitState(target, UNIT_STATE_LIFE, newHitpoints)
    else
        if ( IsUnitType(target, UNIT_TYPE_ETHEREAL) == false ) then
            call SetWidgetLife(target, 1.0)
            call UnitDamageTarget(source, target, MAX_DAMAGE, true, false, udg_PDD_ATTACK_TYPE_UNIVERSAL, DAMAGE_TYPE_UNIVERSAL, null)
            call SetWidgetLife(target, 0.0)
        else
            call UnitRemoveAbility(target, udg_PDD_DAMAGE_TYPE_DETECTOR)
            call SetWidgetLife(target, 1.0)
            call UnitDamageTarget(source, target, MAX_DAMAGE, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
            call SetWidgetLife(target, 0.0)
        endif
    endif
endfunction

function GetUnitSpellResistance takes unit u returns real
    local real originalHP
    local real beforeHP
    local real afterHP
    local real resistance
    local real DUMMY_DAMAGE = 100
    local real DUMMY_FACTOR = 0.01
 
    // Deal spell damage in order to get the units resistance
    call UnitRemoveAbility(udg_PDD_target, udg_PDD_DAMAGE_TYPE_DETECTOR)
    set originalHP = GetWidgetLife(udg_PDD_target)
    call UnitAddAbility(udg_PDD_target, udg_PDD_SET_MAX_LIFE)
    call SetWidgetLife(udg_PDD_target, 20000.0)
    set beforeHP = GetWidgetLife(udg_PDD_target)
    call DisableTrigger(udg_PDD_damageEvent)
    call UnitDamageTarget(udg_PDD_source, udg_PDD_target, DUMMY_DAMAGE, true, false, null, DAMAGE_TYPE_UNIVERSAL, null)
    call EnableTrigger(udg_PDD_damageEvent)
    set afterHP = GetWidgetLife(udg_PDD_target)
    call UnitRemoveAbility(udg_PDD_target, udg_PDD_SET_MAX_LIFE)
    call SetWidgetLife(udg_PDD_target, originalHP)
    call UnitAddAbility(udg_PDD_target, udg_PDD_DAMAGE_TYPE_DETECTOR)
    call UnitMakeAbilityPermanent(udg_PDD_target, true, udg_PDD_DAMAGE_TYPE_DETECTOR)

    // Calculate this resistance
    set resistance = DUMMY_FACTOR*(beforeHP - afterHP)

    // If the resistance was greater than 100%, return it
    if resistance > 1.0 then
        return resistance
    else
        return 1.0
    endif
endfunction

function UnitHasItemOfType takes unit u, integer itemId returns integer
    local integer index = 0
    local item indexItem

    // Check if the target has a spell damage reducing item
    loop
        set indexItem = UnitItemInSlot(u, index)
        if ( indexItem != null ) and ( GetItemTypeId(indexItem) == itemId ) then
            set indexItem = null
            return index + 1
        endif

        set index = index + 1
        exitwhen index >= bj_MAX_INVENTORY
    endloop
    set indexItem = null
    return 0
endfunction


////////////////////////////////////////////////////////////////////////////////////
//  Damage Engine
////////////////////////////////////////////////////////////////////////////////////

function AfterDamage takes nothing returns nothing
    local timer time = GetExpiredTimer()
    local integer id = GetHandleId(time)
    local unit localSource = LoadUnitHandle(udg_PDD_h, id, 0)
    local unit localTarget = LoadUnitHandle(udg_PDD_h, id, 1)
    local real pureAmount = LoadReal(udg_PDD_h, id, 2)
    local real amount = LoadReal(udg_PDD_h, id, 3)
    local real originalHealth = LoadReal(udg_PDD_h, id, 4)

    // If the damage was modified, restore units health
    if originalHealth != 0.0 then
        call UnitRemoveAbility(localTarget, udg_PDD_SET_MAX_LIFE)
        call SetWidgetLife(localTarget, originalHealth)
    endif

    // Apply the desired amount of damage
    if amount > 0.0 then
        call DisableTrigger(udg_PDD_damageEvent)
        call DealFixDamage(localSource, localTarget, amount)
        call EnableTrigger(udg_PDD_damageEvent)
    else
        call SetWidgetLife(localTarget, originalHealth - amount)
    endif
 
    // Clean things up
    call FlushChildHashtable(udg_PDD_h, id)
    call FlushChildHashtable(udg_PDD_h, GetHandleId(localTarget))
    call DestroyTimer(time)
    set time = null
    set localSource = null
    set localTarget = null
endfunction

function DamageEngine takes nothing returns nothing
    local timer time
    local integer id
    local real health
    local real rawAmount
    local real originalHealth
    local integer targetId
    local integer oldTimerId
    local real oldHealth
    local real oldOriginalHealth
    local real oldAmount
  
    set rawAmount = GetEventDamage()
    if rawAmount == 0.0 then
        return
    endif
    set udg_PDD_source = GetEventDamageSource()
    set udg_PDD_target = GetTriggerUnit()
 
    // Determine the damage type
    if rawAmount > 0.0 then
        if udg_PDD_damageType != udg_PDD_CODE then
            set udg_PDD_damageType = udg_PDD_PHYSICAL
        endif
        set udg_PDD_amount = rawAmount
    else
        if udg_PDD_damageType != udg_PDD_CODE then
            set udg_PDD_damageType = udg_PDD_SPELL
        endif
        set udg_PDD_amount = -rawAmount
    endif
 
    // Register spell reduction above 100%
    if udg_PDD_SPELL_RESIST_AUTO_DETECT then
        set udg_PDD_amount = GetUnitSpellResistance(udg_PDD_target)*udg_PDD_amount
    else
        if ( IsUnitType(udg_PDD_target, UNIT_TYPE_ETHEREAL) and IsUnitEnemy(udg_PDD_target, GetOwningPlayer(udg_PDD_source)) and rawAmount < 0.0 ) then
            set udg_PDD_amount = udg_PDD_ETHEREAL_DAMAGE_FACTOR*udg_PDD_amount
        endif
    endif
 
    // Register spell damage reducing items like runed bracers
    if ( IsUnitType(udg_PDD_target, UNIT_TYPE_HERO) and UnitHasItemOfType(udg_PDD_target, udg_PDD_SPELL_DMG_REDUCTION_ITEM) > 0 ) and rawAmount < 0.0 then
        set udg_PDD_amount = (1 - udg_PDD_BRACERS_SPELL_DMG_RED)*udg_PDD_amount
    endif
 
    // Save health and damage variables
    if udg_PDD_damageType != udg_PDD_CODE then
        call UnitRemoveAbility(udg_PDD_target, udg_PDD_SET_MAX_LIFE)
    endif
    set udg_PDD_pureAmount = udg_PDD_amount
    set originalHealth = GetWidgetLife(udg_PDD_target)
    set oldTimerId = 0
 
    // Call damage handlers
    set udg_PDD_damageEventTrigger = 0.0
    set udg_PDD_damageEventTrigger = 1.0
    set udg_PDD_damageEventTrigger = 0.0

    // If the damage was modified, apply the rescue ability
    if udg_PDD_amount != udg_PDD_pureAmount then
        call UnitAddAbility(udg_PDD_target, udg_PDD_SET_MAX_LIFE)
        call SetWidgetLife(udg_PDD_target, GetWidgetLife(udg_PDD_target) + udg_PDD_pureAmount)
    endif
 
    // Check if a previous timer didn't yet expire
    set targetId = GetHandleId(udg_PDD_target)
    set oldHealth = LoadReal(udg_PDD_h, targetId, 0)
 
    // If this is the case, update the timer information
    if oldHealth != 0.0 then
        set oldTimerId = LoadInteger(udg_PDD_h, targetId, 3)
        set oldOriginalHealth = LoadReal(udg_PDD_h, targetId, 2)
        set oldAmount = LoadReal(udg_PDD_h, targetId, 1)
        set originalHealth = oldOriginalHealth - oldAmount
        call SaveReal(udg_PDD_h, oldTimerId, 4, oldOriginalHealth)
    endif

    // Call after damage event if damage was spell, modified, code or parallel
    if ( rawAmount < 0.0 or udg_PDD_pureAmount != udg_PDD_amount or oldTimerId != 0 or udg_PDD_allocatedAttacks > 0 ) then
        set time = CreateTimer()
        set id = GetHandleId(time)
     
        // Save handles for after damage event
        call SaveUnitHandle(udg_PDD_h, id, 0, udg_PDD_source)
        call SaveUnitHandle(udg_PDD_h, id, 1, udg_PDD_target)
        call SaveReal(udg_PDD_h, id, 2, udg_PDD_pureAmount)
        call SaveReal(udg_PDD_h, id, 3, udg_PDD_amount)
        call SaveReal(udg_PDD_h, id, 4, originalHealth)

        // Save this extra to manage parallel damage instances
        call SaveReal(udg_PDD_h, targetId, 0, GetWidgetLife(udg_PDD_target))
        call SaveReal(udg_PDD_h, targetId, 1, udg_PDD_amount)
        call SaveReal(udg_PDD_h, targetId, 2, originalHealth)
        call SaveInteger(udg_PDD_h, targetId, 3, id)

        // Avoid healing of negative spell damage
        if rawAmount < 0.0 then
            call DisableTrigger(udg_PDD_damageEvent)
            call DealFixDamage(udg_PDD_source, udg_PDD_target, -rawAmount)
            if ( originalHealth - udg_PDD_amount < udg_PDD_UNIT_MIN_LIFE ) then
                call UnitRemoveAbility(udg_PDD_target, udg_PDD_SET_MAX_LIFE)
                call DealFixDamage(udg_PDD_source, udg_PDD_target, udg_PDD_amount)
            endif
            call EnableTrigger(udg_PDD_damageEvent)
        endif
     
        // Guarantee unit exploding
        if originalHealth - udg_PDD_amount < udg_PDD_UNIT_MIN_LIFE then
            if rawAmount > 0.0 then
                call UnitRemoveAbility(udg_PDD_target, udg_PDD_SET_MAX_LIFE)
                call SetWidgetLife(udg_PDD_target, udg_PDD_UNIT_MIN_LIFE)
            endif
        endif
     
        // Start the after damage event
        call TimerStart(time, 0.0, false, function AfterDamage)
    endif

    // Handle allocated attacks from UnitDamageTargetEx
    if udg_PDD_totalAllocs == 0 then
        set udg_PDD_totalAllocs = udg_PDD_allocatedAttacks
    endif
    if udg_PDD_allocatedAttacks > 0 then
        set udg_PDD_allocatedAttacks = udg_PDD_allocatedAttacks - 1
        set udg_PDD_allocCounter = udg_PDD_allocCounter + 1
        call TriggerEvaluate(udg_PDD_runAllocatedAttacks)
    endif

    // Reset all required variables
    set udg_PDD_damageType = -1
    set udg_PDD_totalAllocs = 0
    set udg_PDD_allocCounter = -1
endfunction


////////////////////////////////////////////////////////////////////////////////////
//  Initialization
////////////////////////////////////////////////////////////////////////////////////

function RestoreTriggers takes nothing returns nothing
    local unit enumUnit = GetEnumUnit()

    // Re-register units that are alive
    if GetUnitTypeId(enumUnit) != 0 then
        call TriggerRegisterUnitEvent(udg_PDD_damageEvent, enumUnit, EVENT_UNIT_DAMAGED)
    endif
    set enumUnit = null
endfunction

function ClearMemory_Actions takes nothing returns nothing
    local group g = CreateGroup()
    local code c = function DamageEngine
 
    // Reset the damage event
    call GroupEnumUnitsInRect(g, GetWorldBounds(), null)
    call ResetTrigger(udg_PDD_damageEvent)
    call DestroyTrigger(udg_PDD_damageEvent)
    set udg_PDD_damageEvent = null
 
    // Rebuild it then
    set udg_PDD_damageEvent = CreateTrigger()
    call TriggerAddCondition(udg_PDD_damageEvent, Filter(c))
    call ForGroup(g, function RestoreTriggers)
 
    // Clean things up
    call DestroyGroup(g)
    set g = null
    set c = null
endfunction

function MapInit takes nothing returns nothing
    local unit enumUnit = GetEnumUnit()

    // Register units on map initialization
    call UnitAddAbility(enumUnit, udg_PDD_DAMAGE_TYPE_DETECTOR)
    call UnitMakeAbilityPermanent(enumUnit, true, udg_PDD_DAMAGE_TYPE_DETECTOR)
    call TriggerRegisterUnitEvent(udg_PDD_damageEvent, enumUnit, EVENT_UNIT_DAMAGED)
    set enumUnit = null
endfunction

function UnitEntersMap takes nothing returns nothing
    local unit triggerUnit = GetTriggerUnit()

    // Register units that enter the map
    if ( GetUnitAbilityLevel(triggerUnit, udg_PDD_DAMAGE_TYPE_DETECTOR) < 1 ) then
        call UnitAddAbility(triggerUnit, udg_PDD_DAMAGE_TYPE_DETECTOR)
        call UnitMakeAbilityPermanent(triggerUnit, true, udg_PDD_DAMAGE_TYPE_DETECTOR)
        call TriggerRegisterUnitEvent(udg_PDD_damageEvent, triggerUnit, EVENT_UNIT_DAMAGED)
    endif
    set triggerUnit = null
endfunction

function RunAllocatedAttacks takes nothing returns nothing
    local integer localAllocAttacks = udg_PDD_allocatedAttacks + 1

    // Calculate the correct sequence of allocated attacks
    set localAllocAttacks = localAllocAttacks - udg_PDD_totalAllocs + 1 + 2*udg_PDD_allocCounter
 
    // Deal code damage if the unit isn't exploding
    set udg_PDD_damageType = udg_PDD_CODE
    if GetUnitLife(LoadUnitHandle(udg_PDD_h, localAllocAttacks, 1)) >= udg_PDD_UNIT_MIN_LIFE then
        call UnitDamageTarget(LoadUnitHandle(udg_PDD_h, localAllocAttacks, 0), LoadUnitHandle(udg_PDD_h, localAllocAttacks, 1), LoadReal(udg_PDD_h, localAllocAttacks, 2), LoadBoolean(udg_PDD_h, localAllocAttacks, 3), LoadBoolean(udg_PDD_h, localAllocAttacks, 4), ConvertAttackType(LoadInteger(udg_PDD_h, localAllocAttacks, 5)), ConvertDamageType(LoadInteger(udg_PDD_h, localAllocAttacks, 6)), ConvertWeaponType(LoadInteger(udg_PDD_h, localAllocAttacks, 7)))
    else
        call FlushChildHashtable(udg_PDD_h, localAllocAttacks - 1)
    endif
 
    // Clean things up
    call FlushChildHashtable(udg_PDD_h, localAllocAttacks)
endfunction

function InitTrig_DamageEvent takes nothing returns nothing
    local group g = CreateGroup()
    local region r = CreateRegion()
    local trigger UnitEnters = CreateTrigger()
    local trigger ClearMemory = CreateTrigger()
    local code cDamageEngine = function DamageEngine
    local code cUnitEnters = function UnitEntersMap
    local code cClearMemory = function ClearMemory_Actions
    local code cRunAllocatedAttacks = function RunAllocatedAttacks
 
    // Initialize global variables
    set udg_PDD_h = InitHashtable()
    set udg_PDD_damageEvent = CreateTrigger()
    set udg_PDD_damageHandler = CreateTrigger()
    set udg_PDD_damageType = -1
    set udg_PDD_allocatedAttacks = 0
    set udg_PDD_runAllocatedAttacks = CreateTrigger()
 
    // Initialize global configurable constants
    call InitGlobalVariables()
 
    // Initialize global fixed constants
    set udg_PDD_PHYSICAL = 0
    set udg_PDD_SPELL = 1
    set udg_PDD_CODE = 2
    set udg_PDD_UNIT_MIN_LIFE = 0.406
    set udg_PDD_ATTACK_TYPE_UNIVERSAL = ConvertAttackType(7)
    set udg_PDD_totalAllocs = 0
    set udg_PDD_allocCounter = -1
    set udg_PDD_damageEventTrigger = 0.0
 
    // Register units on map initialization
    call TriggerRegisterVariableEvent(udg_PDD_damageHandler, "udg_PDD_damageEventTrigger", EQUAL, 1.0)
    call TriggerAddCondition(udg_PDD_damageEvent, Filter(cDamageEngine))
    call GroupEnumUnitsInRect(g, GetWorldBounds(), null)
    call ForGroup(g, function MapInit)
 
    // Register units that enter the map
    call RegionAddRect(r, GetWorldBounds())
    call TriggerRegisterEnterRegion(UnitEnters, r, null)
    call TriggerAddCondition(UnitEnters, Filter(cUnitEnters))
 
    // Register trigger for allocated attacks
    call TriggerAddCondition(udg_PDD_runAllocatedAttacks, Filter(cRunAllocatedAttacks))
 
    // Clear memory leaks
    call TriggerRegisterTimerEvent(ClearMemory, udg_PDD_TRIGGER_CLEANUP_PERIOD, true)
    call TriggerAddCondition(ClearMemory, Filter(cClearMemory))

    // Clean things up
    call DestroyGroup(g)
    set UnitEnters = null
    set ClearMemory = null
    set cDamageEngine = null
    set cUnitEnters = null
    set cClearMemory = null
    set cRunAllocatedAttacks = null
    set g = null
    set r = null
endfunction


  • OnDamage
    • Events
      • Game - PDD_damageEventTrigger becomes Equal to 1.00
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • 'IF'-Conditions
          • PDD_damageType Equal to PDD_PHYSICAL
        • 'THEN'-Actions
          • -------- Actions for PHYSICAL damage --------
          • -------- Block all physical damage from the Archmage --------
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • 'IF'-Conditions
              • PDD_source Equal to Archmage 0001 <gen>
            • 'THEN'-Actions
              • Set PDD_amount = 0.00
            • 'ELSE'-Actions
          • -------- Let the Blademaster heal 50 hitpoints on every attack --------
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • 'IF'-Conditions
              • PDD_source Equal to Blademaster 0000 <gen>
            • 'THEN'-Actions
              • Set PDD_amount = -50.00
            • 'ELSE'-Actions
          • Game - Display to (All players) for 10.00 seconds the text: ((Name of PDD_source) + ( damages + ((Name of PDD_target) + ( with damage: + (|cffff0000 + (String(PDD_amount)))))))
          • -------- End of Actions for PHYISCAL damage --------
        • 'ELSE'-Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • 'IF'-Conditions
              • PDD_damageType Equal to PDD_SPELL
            • 'THEN'-Actions
              • -------- Actions for SPELL damage --------
              • -------- Double all spell damage from the Mountain King --------
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • 'IF'-Conditions
                  • PDD_source Equal to Mountainking 0002 <gen>
                • 'THEN'-Actions
                  • Set PDD_amount = (2.00 x PDD_amount)
                • 'ELSE'-Actions
              • Game - Display to (All players) for 10.00 seconds the text: ((Name of PDD_source) + ( damages + ((Name of PDD_target) + ( with damage: + (|cff6495ed + (String(PDD_amount)))))))
              • -------- End of Actions for SPELL damage --------
            • 'ELSE'-Actions
              • -------- Actions for CODE damage --------
              • Game - Display to (All players) for 10.00 seconds the text: ((Name of PDD_source) + ( damages + ((Name of PDD_target) + ( with damage: + (|cff32cd32 + (String(PDD_amount)))))))
              • -------- End of Actions for CODE damage --------
      • -------- Reflect 50 damage from the Kodo back on the damage source --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • 'IF'-Conditions
          • PDD_target Equal to Kodo-Beast 0006 <gen>
        • 'THEN'-Actions
          • Custom script: call UnitDamageTargetEx(udg_PDD_target, udg_PDD_source, 50.0, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
        • 'ELSE'-Actions


real PDD_damageEventTrigger
Use the event "value of real variable becomes equal to 1.0" to detect a damage event. In this event you can use the following API. Compare the OnDamage trigger for an example usage.

unit PDD_target
In this global unit variable, the damaged unit is saved. Don't write anything into this variable, use it as readonly.

unit PDD_source
In this global unit variable, the damage source is saved. Don't write anything into this variable, use it as readonly.

real PDD_amount
In this global real variable, the amount of damage is saved. This amount can be modified by simply setting it to the desired amount that should be applied to the target. Set the amount to 0.0 to block the damage completly. Negative values will result in heal.

integer PDD_damageType
In this global integer variable, the damage type of the current damage is saved. Use it to differentiate between physical, spell and code damage. The variable can takes the values PHYSICAL == 0, SPELL == 1 and CODE == 2. Don't write anything into this variable, use it as readonly.

function UnitDamageTargetEx takes unit localSource, unit localTarget, real localAmount, boolean attack, boolean ranged, attacktype localAttackType, damagetype localDamageType, weapontype localWeaponType returns boolean
Use this function to allocate attacks from a running damage event. You can use it in exactly the same way as the standard native UnitDamageTarget. The function is designed recursive, so you can allocate as many attacks as you want. Do not use this function outside of a damage event.

function GetUnitLife takes unit u returns real
Use this function instead of the GetWidgetLife native. It ensures that you get the correct health value, even when damage modifiers are applied. If you don't use damage modifiers at all, you don't need this function.

function GetUnitMaxLife takes unit u returns real
Use this function instead of the GetUnitState(u, UNIT_STATE_MAX_LIFE) native. It will return the correct value, even when damage modifiers are applied. If you don't use damage modifiers at all, you don't need this function.

function SetUnitLife takes unit u, real newLife returns nothing
Use this function instead of the SetWidgetLife(u, newLife) native if you use damage modifiers in your map. Same rules as for the GetUnitMaxLife and the GetUnitMaxLife functions.

function AddDamageHandler takes code damageHandler returns nothing
Allows you to add a damage handler function to the system. This is not required for GUI users, only for vanilla JASS users. GUI users should use the real variable damageEventTrigger to add damage handlers to this system as explained above.

function RemoveDamageHandler takes code damageHandler returns nothing
Allows you to remove a damage handler function from the system dynamically. If you added the same handler function multiple times to the system, this function will remove all of these equal functions. This is not required for GUI users, only for vanilla JASS users.



3. Implementation and important notes

Follow these steps to implement the system into your map.


  • Create all variables that this system uses. You can use the "VariableCreator" trigger from the testmap to automatically let it create all variables. Make sure you checked "Automatically create unknown variables" under the WorldEditor preferences, then copy the VariableCreator trigger into your map and save it. After the variables are created delete the VariableCreator trigger and continue.
  • Copy the DamageEvent trigger to your map. You can then add damage handlers by using the event "value of real variable becomes equal to 1" with the variable PDD_damageEventTrigger. Compare the OnDamage trigger for an example usage.
  • Copy the two custom abilities to your map and make sure they have the correct ID. You can specify the ID in the function InitGlobalVariables below.
  • Go to the locust swarm ability and invert its damage return portion from (default) 0.75 to -0.75.
  • Remove the spell damage reduction ability from the spell damage reduction items you use (runed bracers). You can configure the resistance of this item in the InitGlobalVariables function, modifying the global variable udg_PDD_BRACERS_SPELL_DMG_RED.



  • Life Drain does not work with this system, so you should use a triggered version of this spell if you want to use it.
  • Same for Finger of Death, if you want to use this spell bug free with this system, you should use a triggered version of it.
  • If you use damage modifiers by setting the damage amount variable, you have to use GetUnitLife, GetUnitMaxLife and SetUnitLife instead of GetWidgetLife, GetUnitState for UNIT_STATE_MAX_LIFE and SetWidgetLife.
  • The boolean udg_PDD_SPELL_RESIST_AUTO_DETECT is only neccessary set to true if you want to use a customized damage table with spell damage resistance above 100%. If this is not the case, it should be set to false, as the system is faster then and still works correct.
  • As already mentioned you can't use the spell reduction ability when using this system (runed bracers and elunes grace). If you want to use them, you can trigger them by using the damage modifiers. Runed bracers is already considered in this system, so you don't have to code it.



4. Screenshots

Enclosed are some screenshots of the testmap showing the detection of damage via text messages. Physical damage is displayed in red, spell damage in blue and code damage in green.


Spell Damage
pdd-spell-damage-png.326453


Physical Damage
pdd-physical-damage-png.326454


Code Damage
upload_2019-6-26_11-22-12-png.326452

physicaldamage.jpg

spelldamage.jpg

codedamage.jpg

spellandphysicaldamage.jpg



5. Special thanks

My special thanks go to Cokemonkey11 and Nestharus for giving tipps, finding bugs, discussing and, by doing so, helping me to develope this system.

Hope you enjoy it!

Greetings,
lfh

Keywords:
Damage Detection System, GUI, DDS, Physical, Spell, DamageType, detect damage type, detect physical damage, block damage, modify damage
Contents

Physical Damage Detection (Map)

Reviews
05:09, 30th Mar 2013 Magtheridon96: Approved. The code is fine and readable and you seem to be maintaining the resource. I recommend this to anyone who wants nice and smooth damage detection with type support included right out of the box...
Level 30
Joined
Jul 23, 2009
Messages
1,029
It should work properly, otherwise it wouldnt be approved.

You could (for a test case) damage the attacking unit to see if you can deal damage.
If so, the problem was probably with your group enumeration.

Are you sure?

This is a crude and leaking, but simple example of what I mean. This chrashes the game.

  • Damage Event
    • Events
      • Game - PDD_damageEventTrigger becomes Equal to 1.00
    • Conditions
    • Actions
      • Unit Group - Pick every unit in (Units within 512.00 of (Position of PDD_source) matching ((((Matching unit) is alive) Equal to True) and (((Matching unit) belongs to an enemy of (Owner of PDD_source)) Equal to True))) and do (Actions)
        • Loop - Actions
          • Unit - Cause PDD_source to damage (Picked unit), dealing 10.00 damage of attack type Normal and damage type Normal
Edit: I read up a bit and realized I need to use the function UnitDamageTargetEx
I'm not that familliar with functions, could someone try and explain to me how to type it in to the GUI action Call custom script?

Edit 2: Nvm I figured out by checking the testmap triggers.
 
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657
Ow wait, sorry, I thought this was Bribe's DDS.

Yea, you have to use a custom script (or maybe a trigger call if lfh implemented that) to deal damage if the event was the onDamage event.
It is not really nice, but it works... to an extend.
 
Sometimes when I cast a spell, it seemingly randomly sometimes heals the enemy rather than damaging it. Doesn't seem to matter whether or not the spell is custom or standard. Also it goes back to working just fine, then back to healing them again, etc. I can't pinpoint any reason that this would happen. It just seems like it all of a sudden starts reversing the damage I should be doing and making it heal instead. I thought it might have been because I was using an older version or I accidentally messed with the code somewhere, but I re-installed the newest version and it still happens. Anybody ever have this problem or know what's causing it?

Something odd happens to me too, I use this system in a Moba map and it seems that from time to time some abilities/heroes start dealing double damage and some things stop working, I don't know why actually, after a few seconds everything goes back to normal e.e
 
Level 4
Joined
Nov 25, 2013
Messages
132
Hi there! Is it possible to make a spell that reduces all incoming damage to zero while reflecting damage at the same time? I tried to make a spell and it reduced the damage to zero the entire test run.
 
Hi there! Is it possible to make a spell that reduces all incoming damage to zero while reflecting damage at the same time? I tried to make a spell and it reduced the damage to zero the entire test run.

Yeah, it is easy to do, just save the amount in a variable before setting it to 0, include the target in a group or make a unit variable to make sure the damage doesnt return eternally (prevent bugs) and cause the target to damage the source by said amount you previously saved in the variable. I recommend you to actually do various triggers when handling the damage detection system; by example I have an entire row of different triggers for A: primal events, b physical damage (modifications, amplifications, reductions, and then backfire damage and life steal) and then I have the row of the spell damage, and also you can create "true damage" by using groups or integers.

You can also use the function damage target ex :V but I don't use functions
 
Level 4
Joined
Nov 25, 2013
Messages
132
Yeah, it is easy to do, just save the amount in a variable before setting it to 0, include the target in a group or make a unit variable to make sure the damage doesnt return eternally (prevent bugs) and cause the target to damage the source by said amount you previously saved in the variable. I recommend you to actually do various triggers when handling the damage detection system; by example I have an entire row of different triggers for A: primal events, b physical damage (modifications, amplifications, reductions, and then backfire damage and life steal) and then I have the row of the spell damage, and also you can create "true damage" by using groups or integers.

You can also use the function damage target ex :V but I don't use functions

Wow thanks sir! A good amount of hours running around in darkness ended in a short beam of light! I'll try it and message you if i have any difficulties.

EDIT: Got confused in the storing damage before setting to zero part. Does it work on continous damage like multiple attackers, DOT, etc.?
 
Last edited:
Wow thanks sir! A good amount of hours running around in darkness ended in a short beam of light! I'll try it and message you if i have any difficulties.

EDIT: Got confused in the storing damage before setting to zero part. Does it work on continous damage like multiple attackers, DOT, etc.?

Damage is just a Real Numbers variable, it should work on any of it, by example in my map if I want to do something specific like adding damage on-hit I just create a dummy attacker for neutral passive with 0 damage (with both a magic attack and a hero attack to attack ethereals if necesary and magic immune), the mana of the dummy is the stored damage, and the custom value serves as the key array for the hero variable I use as reference (but you can use a hashtable and unit ID as key), so when the dummy attacks (event) I cause the hero to deal damage equal to the mana of the dummy, and so on. To set the type of damage I just put them in a group, "OnAttackMagicDamage" "OnAttackPhysicalDamage" "OnAttackTrueDamage", I may also put them on other groups for more special effects.

Another recommendation is to have by example a trigger of primary events in spell damage. In my map I've made my heroes abilities to deal 0.25 damage and things like that (numbers below 1) so I detect them on the early stages and add special things to abilities and modifiers (make them scale with agility, attack damage, or other custom stats).

Other trick to add on hits to basic attacks is to sacrifice either the orb of venom or the orb of corruption and set it's duration to 0.01 seconds, so when the unit has the buff and the attacker is "Xtype" you can run custom events on calculation, you can also use the Poison Arrow ability for this of course to have another different ability.

For on death effects just make a trigger called post damage, in the last events if the damage is bigger than current life run X event.

Btw make sure the dummy unit is a flying unit, worker and a structure (building cast and attack super super quick), make sure it's attack animation has a delay of something like 0.05 or 0.10 to avoid bugs, and create a trigger that causes AI to ignore guard position of any created dummy (entering unit) in the map to avoid further bugs.
 
Last edited:
Level 5
Joined
Feb 18, 2016
Messages
96
I have a problem
I dont know why but the "Damage event" trigger adds a scroll of speed ability to all units (even buildings)

I know is this trigger because i disabled it and the skill was not added
 

Attachments

  • triggerbug.png
    triggerbug.png
    361.6 KB · Views: 87
Level 2
Joined
Nov 2, 2010
Messages
20
Hello, may I ask you a question?
if I have so much hero and spell that need this system, should I include all spell in single trigger?
If yes, this trigger will have so much "if else", may be 100+, will the performance become slow? (Also it will be more difficult to manage spell)
If no, some spell may used to block or decrease damage, but it may overwrite by other trigger that placed below it?

Sorry for bothering you.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
1, may I ask you a question?
No.

2, should I include all spell in single trigger?
Dunno.

3, will the performance become slow?
Not slower, probably faster, but still relatively slow for computers.
If you have separate triggers with a condition (or if statement in the actions) it will run that boolean expression.
In the case of triggers, evaluating the conditions and possibly executing the actions on top of the boolean expression.
In the case of a list of if statements, the overhead of the condition evaluations and action executions is removed.
So in essence, it is more efficient.
However, you pay a lot to get that little efficiency. You pay maintainability.

4, but it may overwrite by other trigger that placed below it?
Depends on how you use it.
If you just use if/then then both effects can be applied.
If the effects are something like "Set Damage = Damage + 10", then the damage will increase in both cases.
So overwriting is not really a thing.
However, some effects may not "work" how you want them to do.

For example, I have a buff that inreases my damage by 10, and I have a buff that increases my damage by 100%.
If I do (dmg + 10) -> (dmg * (1+1.00)) = (2dmg + 20)
If I do (dmg * (1+1.00)) -> (dmg + 10) = (2dmg + 10)
So, depending on which order the effects are applied, it may have different outcome.

That is why I would recommend to have different variables, like flatDamage and percentDamage.
Then, the above example would have (flatDamage + 10) -> (percentDamage * (1+1.00)) -> (flatDamage * percentDamage) = (2dmg + 20)
Ofcourse different variables for everything, including the block you mentioned.

Most times, these are quite hard to fix if you bind yourself to whatever you have.
Most times, the differences arent really notable or even matter (when they do matter however, it is frustrating).
Your game will still run with these flaws.

TL:DR
Dont worry about performance.
Dont worry about logical mistakes. However, do think twice about how your logic works.
Do care about organizing your triggers to make them maintainable.
 
Level 2
Joined
Nov 2, 2010
Messages
20
1, may I ask you a question?
No.

2, should I include all spell in single trigger?
Dunno.

3, will the performance become slow?
Not slower, probably faster, but still relatively slow for computers.
If you have separate triggers with a condition (or if statement in the actions) it will run that boolean expression.
In the case of triggers, evaluating the conditions and possibly executing the actions on top of the boolean expression.
In the case of a list of if statements, the overhead of the condition evaluations and action executions is removed.
So in essence, it is more efficient.
However, you pay a lot to get that little efficiency. You pay maintainability.

4, but it may overwrite by other trigger that placed below it?
Depends on how you use it.
If you just use if/then then both effects can be applied.
If the effects are something like "Set Damage = Damage + 10", then the damage will increase in both cases.
So overwriting is not really a thing.
However, some effects may not "work" how you want them to do.

For example, I have a buff that inreases my damage by 10, and I have a buff that increases my damage by 100%.
If I do (dmg + 10) -> (dmg * (1+1.00)) = (2dmg + 20)
If I do (dmg * (1+1.00)) -> (dmg + 10) = (2dmg + 10)
So, depending on which order the effects are applied, it may have different outcome.

That is why I would recommend to have different variables, like flatDamage and percentDamage.
Then, the above example would have (flatDamage + 10) -> (percentDamage * (1+1.00)) -> (flatDamage * percentDamage) = (2dmg + 20)
Ofcourse different variables for everything, including the block you mentioned.

Most times, these are quite hard to fix if you bind yourself to whatever you have.
Most times, the differences arent really notable or even matter (when they do matter however, it is frustrating).
Your game will still run with these flaws.

TL:DR
Dont worry about performance.
Dont worry about logical mistakes. However, do think twice about how your logic works.
Do care about organizing your triggers to make them maintainable.
Woo my friend, you give me a great inspiration, I'm extremely grateful to you! Thanks!
 
Level 4
Joined
Nov 25, 2013
Messages
132
oooh i see.
And how can i trigger the spell damage for the system. I´ve made some abilities but how can i set the pdd_amount to, for example, magic damage?

Make variables for your spells, for example "Spell_DamageType" or "Spell_DamageAmount" or "Spell_AttackType" and set them to whatever you want. I'm not very familiar with attack and damage types so just explore and figure them out yourself.
 
Level 3
Joined
Apr 2, 2013
Messages
41
Please some1 help me some questions because this is important on my map ?
this system detect physic dmg Before reduction (the actualy att dmg of the unit before check armor type n armor value) or dmg After reduction (the HP actualy loss after take dmg ; hp removal )
Eg, i want to mimic the Cleave ability , unit A attack unit B (X armor) and cleave 100% to unit C (99999 armor) , i know that cleave ignore armor value so in here C still take dmg but not equal to the dmg unit B take ( because B have X armor and C's armor ignore = 0armor )
my english not good ,sry if you dont understand
 
Level 3
Joined
Apr 2, 2013
Messages
41
So with my Cleave ability ,when i hit a illusion that take x4 dmg more than normal (eg 100x4 HP) then nearby normal unit HP will be reduce the same ?
Im looking for the way to separate between dmg Before Reduction and dmg After Reduction in my spells
Eg: with dmg Before Reduction i can make ability that return the dmg and not depend how many HP attacked unit loss ,like X att dmg attack unit have 9999armor but return X dmg not 0 dmg
or some shell ability , dmg go through Shell then go through armor after , ect.
I also has abilities that use dmg After reduction value too . Any suggest for my map ?
Im newbie ,sorry if i request too much
 
Level 5
Joined
Feb 18, 2016
Messages
96
Hello
I have a question about the system and the damage triggers.
I noticed if i use an ability and i trigger the spell damage (example : 200 damage from object editor + 200 damage from triggers) and the event begins it does not detect the damage from the trigger but only from the ability
How can i fix it?

Example trigger that does not run the event:

  • Ability
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Something
    • Actions
      • Unit - Cause (Casting Unit) to damage (Target unit of ability being cast), dealing 200 damage of attack type Spells and damage type Enhanced
Event

  • Detect Spell Damage
    • Events
      • Game - PDD_damageEventTrigger becomes Equal to 1.00
    • Conditions
      • PDD_damageType Equal to PDD_SPELL
    • Actions
      • Set PDD_amount = (PDD_amount x 0.50)
Recapitulating: The trigger reduces the ability damage but not the triggered damage

Is There Anything That i did Badly?
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
I think because Enhanced damage is not considered spell damage.
The "Spells" part in your damage action is not a damage type, it is an attack type.

So, with that in mind, it doesnt pass the condition, so doesnt apply the actions.
Try with "Magic" damage type.
 
Level 5
Joined
Feb 18, 2016
Messages
96
Hello
I need some help with the custom script because i want to modify the PDD_AMOUNT damage type (like doing bonus magic damage on auto attacks)

  • Custom script: call UnitDamageTargetEx(udg_source, udg_target, stored_amount, false, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_UNIVERSAL, null)
How can i set the amount to a Variable like this

  • DA
    • Events
      • Game - PDD_damageEventTrigger becomes Equal to 1.00
    • Conditions
      • PDD_damageType Equal to PDD_PHYSICAL
      • PDD_source Equal to Hero
    • Actions
      • Set QDamage = (0.40 x ((Real((Intelligence of Hero (Include bonuses))))))
      • Custom script: call UnitDamageTargetEx(udg_source, udg_target, udg_QDamage, false, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_UNIVERSAL, null)
Whatever number i put here it keeps doing 0 damage or auto disabling the trigger because of code errors
 
Level 5
Joined
Feb 18, 2016
Messages
96
It did not work but it seems i have found a way to do it correctly

I am posting it for everybody that had the same issue

If you use a regular...
  • Unit - Cause PDD_SOURCE to damage PDD_TARGET, dealing Variable damage of attack type Spells and damage type Magic
...with the trigger damage event

The game would crash instantly
Instead you can store another variable and use the trigger with no problem at all

  • NigromanteDA
    • Events
      • Game - PDD_damageEventTrigger becomes Equal to 1.00
    • Conditions
      • PDD_damageType Equal to PDD_PHYSICAL
    • Actions
      • Set QSrc = PDD_source
      • Set QObj = PDD_target
      • Set QDamage = X
      • Set PDD_AMOUNT = PDD_AMOUNT
      • Unit - Cause QSrc to damage QObj, dealing QDamage damage of attack type Spells and damage type Magic
Note : You should set the pdd amount like i did because if you dont it will be replaced by the damage trigger
 
Last edited:
Level 21
Joined
Mar 27, 2012
Messages
3,232
That's not what the real problem is. Why it breaks is that you cause damage from a damage handler, which in turns runs the damage handles, which causes damage, which runs the handler... It's an infinite loop.
To prevent that you need to use UnitDamageTargetEx or otherwise avoid causing the handler to run again.
 
Level 5
Joined
Feb 18, 2016
Messages
96
That's not what the real problem is. Why it breaks is that you cause damage from a damage handler, which in turns runs the damage handles, which causes damage, which runs the handler... It's an infinite loop.
To prevent that you need to use UnitDamageTargetEx or otherwise avoid causing the handler to run again.
I understand, but it seems that with the trigger I made it can be done without using a TargetEx custom script
 
Level 5
Joined
Feb 18, 2016
Messages
96
Hello!

I have been doing some triggers with the system and i have found a problem that i can not solve.

It follows that the PDD_amount action for modify the damage from a source works perfectly fine with all of my custom abilities but this one



  • Pasaje HDps
    • Events
      • Time - Every 0.24 seconds of game time
    • Conditions
    • Actions
      • -------- DMG --------
      • Unit - Cause Ability to damage AbilityQTarget, dealing (3.25 x (Real((Level of Pasaje for Ability)))) damage of attack type Spells and damage type Magic
      • Unit - Cause Ability to damage AbilityQTarget, dealing (((0.01 / 4.00) x AbilityQTargetMAXHP) + 0.00) damage of attack type Spells and damage type Magic
      • -------- DMG% --------
      • Unit - Cause Ability to damage AbilityQTarget, dealing (((AbilityQTargetMAXHP - (Life of AbilityQTarget)) / AbilityQTargetMAXHP) x (0.50 x (3.25 x (Real((Level of Pasaje for Ability)))))) damage of attack type Spells and damage type Magic
      • -------- DMG%% --------
      • Unit - Cause Ability to damage AbilityQTarget, dealing (((AbilityQTargetMAXHP - (Life of AbilityQTarget)) / AbilityQTargetMAXHP) x (0.50 x ((0.01 / 4.00) x AbilityQTargetMAXHP))) damage of attack type Spells and damage type Magic
      • Set QPasajeCount = (QPasajeCount + 0.25)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • QPasajeCount Equal to 5.00
        • Then - Actions
          • Trigger - Turn off (This trigger)
        • Else - Actions

The previous trigger is turned on by this one, just in case
  • Pasaje
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Pasaje
    • Actions
      • Set IncreaserQTarget = (Target unit of ability being cast)
      • Set IncreaserQTargetMAXHP = (Max life of IncreaserQTarget)
      • Set QPasajeCount = 0.00
      • Trigger - Turn on Pasaje HDps <gen>

This damage over time works the same with every character in the map, except those with a trigger of PDD_amount damage reduction

  • piel de roca
    • Events
      • Game - PDD_damageEventTrigger becomes Equal to 1.00
    • Conditions
      • PDD_target Equal to whatever
      • PDD_damageType Equal to PDD_SPELL
    • Actions
      • Set PDD_amount = (PDD_amount x 0.40)
May someone tell me how to solve this?
 
Level 5
Joined
Feb 18, 2016
Messages
96
It seems that this system is somehow broken.
With this system it is simply impossible to create an absorbing shield.

Here is the shield i promise


With these triggers the shield increases its life every time <your unit> casts a spell
The text will change its color from green to red based on the life remaining
The shield only protects your unit from Physical damage (you are able to change it)


ShieldVariables
  • enginer variable
    • Events
      • Unit - A unit Learns a skill
    • Conditions
      • (Learned Hero Skill) Equal to Shield of Fate
    • Actions
      • Set SF_Caster = (Learning Hero)
      • Set SF_EscudoMaxReal[1] = 500.00
      • Set SF_EscudoMaxReal[2] = 850.00
      • Set SF_EscudoMaxReal[3] = 1100.00
      • Trigger - Turn off (This trigger)
Shield cast
  • SF init
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Casting unit) Equal to SF_Caster
    • Actions
      • Floating Text - Destroy SF_EscudoText
      • Set SF_Real = (SF_Real + (Real((Intelligence of SF_Caster (Include bonuses)))))
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • SF_Real Greater than SF_EscudoMaxReal[(Level of Shield of Fate for SF_Caster)]
        • Then - Actions
          • Set SF_Real = SF_EscudoMaxReal[(Level of Shield of Fate for SF_Caster)]
        • Else - Actions
      • Special Effect - Create a special effect attached to the origin of SF_Caster using Abilities\Spells\Undead\AntiMagicShell\AntiMagicShell.mdl
      • Set SF_EscudoSE = (Last created special effect)
      • Floating Text - Create floating text that reads (String(SF_Real)) above SF_Caster with Z offset 0.00, using font size 10.00, color (100.00%, 100.00%, 100.00%), and 0.00% transparency
      • Set SF_EscudoText = (Last created floating text)
      • Trigger - Turn on SF text <gen>

Shield Current Life
  • SF text
    • Events
      • Time - Every 0.05 seconds of game time
    • Conditions
    • Actions
      • Floating Text - Change the position of SF_EscudoText to SF_Caster with Z offset 0.00


OnDamageShield
  • OnDamage Copiar
    • Events
      • Game - PDD_damageEventTrigger becomes Equal to 1.00
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • PDD_damageType Equal to PDD_PHYSICAL
        • Then - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • PDD_target Equal to SF_Caster
            • Then - Actions
              • Set SF_Real = (SF_Real - PDD_amount)
              • Set PDD_amount = (PDD_amount - SF_Real)
              • Floating Text - Change text of SF_EscudoText to (String(SF_Real)) using font size 10.00
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Level of Shield of Fate for SF_Caster) Greater than or equal to 1
                • Then - Actions
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • SF_Real Less than or equal to (SF_EscudoMaxReal[(Level of Shield of Fate for SF_Caster)] x 1.00)
                      • SF_Real Greater than (SF_EscudoMaxReal[(Level of Shield of Fate for SF_Caster)] x 0.50)
                    • Then - Actions
                      • Floating Text - Change the color of SF_EscudoText to (0.00%, 100.00%, 0.00%) with 0.00% transparency
                    • Else - Actions
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • SF_Real Less than or equal to (SF_EscudoMaxReal[(Level of Shield of Fate for SF_Caster)] x 0.50)
                      • SF_Real Greater than (SF_EscudoMaxReal[(Level of Shield of Fate for SF_Caster)] x 0.25)
                    • Then - Actions
                      • Floating Text - Change the color of SF_EscudoText to (100.00%, 100.00%, 0.00%) with 0.00% transparency
                    • Else - Actions
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • SF_Real Less than or equal to (SF_EscudoMaxReal[(Level of Shield of Fate for SF_Caster)] x 0.25)
                    • Then - Actions
                      • Floating Text - Change the color of SF_EscudoText to (100.00%, 0.00%, 0.00%) with 0.00% transparency
                    • Else - Actions
                • Else - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • PDD_amount Less than or equal to 0.00
                • Then - Actions
                  • Set PDD_amount = 0.00
                • Else - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • SF_Real Less than or equal to 0.00
                • Then - Actions
                  • Set PDD_amount = PDD_amount
                  • Set SF_Real = 0.00
                • Else - Actions
            • Else - Actions
        • Else - Actions
I know about leak in memory and so but you may fix them
 
Level 6
Joined
Oct 31, 2015
Messages
95
Well, have anyone tested if this is still working? I have tested the system in patch 1.30.1 and when a unit kills a foe using magic damage the XP and gold are not credited for the killing unit. You can easily test this by adding neutral hostile units. I highly prefer this system so I would like to see this issue fixed.
 
Level 20
Joined
May 16, 2012
Messages
635
Well, have anyone tested if this is still working? I have tested the system in patch 1.30.1 and when a unit kills a foe using magic damage the XP and gold are not credited for the killing unit. You can easily test this by adding neutral hostile units. I highly prefer this system so I would like to see this issue fixed.

I have the same problem. Recently imported this system into my map and it was doing great until i tested it with the newest version 1.30.2. Also if you create units at the exact frequency that the system clear memory, 60.0 seconds i think, the system do not work properly for those units. Plz mrs looking_for_help try fixing it, your system is awesome and very usefull, but if we cannot credit killing units than its usefullness gets greatly reduced.
 
Level 6
Joined
Oct 31, 2015
Messages
95
The way I found was by manually add gold and xp to the killer. I've posted this so long ago that I almost forgot. (i never noticed the memory clear stuff, maybe it's a 1.30.2 bug idk)
 
Level 4
Joined
Sep 29, 2018
Messages
74
I just noticed that damaging low hp units with spells will likely not give you bounty/gold for the kill
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
it seems to not work properly when i use the variables more than 1 time in the same trigger at the same time

I'm not sure what the exact problem is, but it could be that your resource is dealing recursive damage. I recommend using Damage Engine 5.2.0.1 as it compensates for all possible recursive situations - and even has backwards-compatibility with this resource.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I would like to contribute this script which uses the anti-recursion method introduced in Damage Engine 5.1 which works thanks to the new 1.31 natives.

One need only to replace the PDD script with the below, as well as revert any changes to Runed Bracers/Locust Swarm and perhaps delete the 2 custom abilities as they are no longer needed.

This script is the lightest possible version of PDD and has been tested.

JASS:
globals
    constant integer PDD_MAX_RECURSION = 16 //looking_for_help recommends using UnitDamageTargetEx to avoid recursion.
    timer PDD_timer = CreateTimer()
    boolean PDD_started = false
    boolean PDD_recursive = false
    unit array PDD_sources
    unit array PDD_targets
    real array PDD_amounts
    attacktype array PDD_attacks
    damagetype array PDD_damages
    weapontype array PDD_weapons
endglobals

//////////////////////////////////////////////////////////////////////////////////
//  User functions
//////////////////////////////////////////////////////////////////////////////////

function GetUnitLife takes unit u returns real //not needed, exists for backwards-compatibility
    return GetWidgetLife(u)
endfunction

function GetUnitMaxLife takes unit u returns real //not needed, exists for backwards-compatibility
    return GetUnitState(u, UNIT_STATE_MAX_LIFE)
endfunction

function SetUnitLife takes unit u, real newLife returns nothing //not needed, exists for backwards-compatibility
    call SetWidgetLife(u, newLife)
endfunction

//not really needed, exists for backwards-compatibility and recursion protection
function UnitDamageTargetEx takes unit source, unit target, real amount, boolean attack, boolean ranged, attacktype attackType, damagetype damageType, weapontype weaponType returns boolean
    local integer i = udg_PDD_damageType
    if i != udg_PDD_CODE then
        set udg_PDD_damageType = udg_PDD_CODE
        call UnitDamageTarget(source, target, amount, attack, ranged, attackType, damageType, weaponType)
        set udg_PDD_damageType = i
        return true
    endif
    return false
endfunction

////////////////////////////////////////////////////////////////////////////////////
//  Damage Engine
////////////////////////////////////////////////////////////////////////////////////

function AfterDamage takes nothing returns nothing
    local integer i = 0
    if udg_PDD_damageType < 0 then
        loop
            exitwhen i == udg_PDD_allocatedAttacks
            set udg_PDD_damageType = udg_PDD_CODE
            call UnitDamageTarget(PDD_sources[i], PDD_targets[i], PDD_amounts[i], true, false, PDD_attacks[i], PDD_damages[i], PDD_weapons[i])
            set i = i + 1
        endloop
        set udg_PDD_allocatedAttacks = 0
        set udg_PDD_damageType = -1
    endif
endfunction

function AfterDamagePrep takes nothing returns nothing
    set PDD_started = false
    call AfterDamage()
endfunction

function DamageEngine takes nothing returns boolean
    local real r = GetEventDamage()
    local integer i
    call AfterDamage()
    if r != 0.00 then
        if not PDD_recursive then
            set udg_PDD_amount = r
            set udg_PDD_source = GetEventDamageSource()
            set udg_PDD_target = GetTriggerUnit()
     
            // Determine the damage type
            if udg_PDD_damageType != udg_PDD_CODE then
                if BlzGetEventAttackType() != ATTACK_TYPE_NORMAL then
                    set udg_PDD_damageType = udg_PDD_PHYSICAL
                else
                    set udg_PDD_damageType = udg_PDD_SPELL
                endif
            endif

            set PDD_recursive = true
            set udg_PDD_damageEventTrigger = 0.00
            set udg_PDD_damageEventTrigger = 1.00
            set PDD_recursive = false

            call BlzSetEventDamage(udg_PDD_amount)
            set udg_PDD_damageType = -1
            if udg_PDD_amount <= 0.00 then
                call AfterDamage()
            endif
        else
            set i = udg_PDD_allocatedAttacks
            if i < PDD_MAX_RECURSION then
                set udg_PDD_allocatedAttacks = i + 1
                set PDD_sources[i] = GetEventDamageSource()
                set PDD_targets[i] = GetTriggerUnit()
                set PDD_amounts[i] = r
                set PDD_attacks[i] = BlzGetEventAttackType()
                set PDD_damages[i] = BlzGetEventDamageType()
                set PDD_weapons[i] = BlzGetEventWeaponType()
                if not PDD_started then
                    set PDD_started = true
                    call TimerStart(PDD_timer, 0.00, false, function AfterDamagePrep)
                endif
            endif
            call BlzSetEventDamage(0.00)
        endif
    endif
    return false
endfunction

////////////////////////////////////////////////////////////////////////////////////
//  Initialization
////////////////////////////////////////////////////////////////////////////////////

function InitTrig_DamageEvent takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DAMAGED)
    call TriggerAddCondition(t, Filter(function DamageEngine))
    set t = null

    set udg_PDD_damageType = -1
    set udg_PDD_PHYSICAL = 0
    set udg_PDD_SPELL = 1
    set udg_PDD_CODE = 2
endfunction
 
Last edited:
Top