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...

Kazeon

Hosted Project: EC
Level 34
Joined
Oct 12, 2011
Messages
3,449
The problem with bracers is, that it is overwritten. All units have a bracer item with 200% spell reduction (thats required to detect damage type). If you now pic up another bracer item with, say 33% spell reduction, it overwrites the previous ability. Then spell damage detection won't work anymore.

Therefore you have to remove the ability from the item and code the reduction yourself. Then you can use it just in the standard way.

Again, if you don't need bracers, you can omit this installation step, but that should be obvious.
I get it, that's why I thought bracer convert spell damages to physical.
 
Could you help me understand why this fails? The unit doesn't take proper damage from the damage source, like the main damage gets healed while only the passive deals like 10 damage.

[trigger=Second event trigger]
Slashing
Events
Game - PDD_damageEventTrigger becomes Equal to 1.00
Conditions
PDD_damageType Equal to PDD_PHYSICAL
(Level of Slash and Double Slash and Triple Slash for PDD_source) Greater than 0
Actions
-------- --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
Then - Actions
Unit - Cause skilldmger1[(Player number of (Owner of PDD_source))] to damage PDD_target, dealing (Askilldamage[(Player number of (Owner of PDD_source))] x 0.25) damage of attack type Spells and damage type Normal
Special Effect - Create a special effect attached to the chest of PDD_target using Objects\Spawnmodels\Critters\Albatross\CritterBloodAlbatross.mdl
Special Effect - Destroy (Last created special effect)
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Random real number between 0.00 and 100.00) Less than or equal to 20.00
Then - Actions
Unit - Cause skilldmger1[(Player number of (Owner of PDD_source))] to damage PDD_target, dealing (Askilldamage[(Player number of (Owner of PDD_source))] x 0.25) damage of attack type Spells and damage type Normal
Special Effect - Create a special effect attached to the chest of PDD_target using Abilities\Weapons\RedDragonBreath\RedDragonMissile.mdl
Special Effect - Destroy (Last created special effect)
Else - Actions
Else - Actions
-------- --------
-------- Triple Slash --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Level of Triple Edge/Slasher buff' for PDD_source) Greater than 0
Then - Actions
Special Effect - Create a special effect attached to the chest of PDD_target using Abilities\Weapons\Bolt\BoltImpact.mdl
Special Effect - Destroy (Last created special effect)
Unit - Cause skilldmger1[(Player number of (Owner of PDD_source))] to damage PDD_target, dealing (Askilldamage[(Player number of (Owner of PDD_source))] x 0.75) damage of attack type Hero and damage type Normal
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Random real number between 0.00 and 100.00) Less than or equal to 20.00
Then - Actions
Unit - Cause skilldmger1[(Player number of (Owner of PDD_source))] to damage PDD_target, dealing (Askilldamage[(Player number of (Owner of PDD_source))] x 0.25) damage of attack type Spells and damage type Normal
Else - Actions
Else - Actions
-------- --------
-------- Second Slash Passive --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Level of Second Slash for PDD_source) Greater than 0
Zsecondslashoncooldown[(Player number of (Owner of PDD_source))] Equal to False
(Random real number between 0.00 and 100.00) Less than or equal to (Critchance[(Player number of (Owner of PDD_source))] / 2.00)
Then - Actions
Unit - Cause skilldmger1[(Player number of (Owner of PDD_source))] to damage PDD_target, dealing (Askilldamage[(Player number of (Owner of PDD_source))] + 0.00) damage of attack type Magic and damage type Normal
Special Effect - Create a special effect attached to the chest of PDD_target using Abilities\Spells\Human\ManaFlare\ManaFlareBoltImpact.mdl
Special Effect - Destroy (Last created special effect)
Else - Actions
-------- --------
[/trigger]
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Could you help me understand why this fails? The unit doesn't take proper damage from the damage source, like the main damage gets healed while only the passive deals like 10 damage.

  1. You should not put conditions in the onDamage event. Instead, use an if-block in the Actions block.
  2. You must not use the GUI version to cause damage in an onDamage handler. Instead use the systems UnitDamageTargetEx in a custom script for recursion safety.

Also please read the documentation and look at the test map which should clear things up.
 
Level 11
Joined
Dec 3, 2011
Messages
366
So did I understand wrong? plz correct some for me
- The UnitDamageTargetEx is only used inside a damage handler. Outside from it, i can only used UnitDamageTarget so the system will detect it as "SPELL"?
- There should be only 1 damage handler, and all functions related to returning damage, extra damage should be in it?
- Block damage is used, so I need to change all Get/SetWidgetLife i've used into Get/SetUnitLife. And also in spells and DoT system i imported so far as well?
 
Level 9
Joined
Jun 10, 2013
Messages
473
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.

May I ask whats the point in modifying the locust swarm ability? and if my map does not use items must I do the second point?
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
So did I understand wrong? plz correct some for me
- The UnitDamageTargetEx is only used inside a damage handler. Outside from it, i can only used UnitDamageTarget so the system will detect it as "SPELL"?
- There should be only 1 damage handler, and all functions related to returning damage, extra damage should be in it?
- Block damage is used, so I need to change all Get/SetWidgetLife i've used into Get/SetUnitLife. And also in spells and DoT system i imported so far as well?

- The system will detect it as physical or magical based on what you input as the parameters. UnitDamageTargetEx is always considered the code damage type.
- You can have multiple damage handlers if it's okay for them to run in an undefined order.
- Yes. Also, if you use the vJASS version of this system, you can use static ifs to make your code use GetWidgetLife instead when this system doesn't exist in the map. (Only matters for code that is meant to be copied from map to map)
May I ask whats the point in modifying the locust swarm ability? and if my map does not use items must I do the second point?

This system makes spells heal damage instead of deal it. This messes with the damage counting functionality of locust swarm, causing it to remove health instead of adding it when locusts return.
If you don't use items, then no, you don't need to set the bracer item.
 
Level 11
Joined
Dec 3, 2011
Messages
366
The most probable reason for this is that you try to use UnitDamageTargetEx from outside a damage handler. Yes, it's not intuitive to have 2 separate functions, but that's how this system works.
I use a default UnitDamageTarget and he system read it as "SPELL". But that's not the main point, can you elaborate the problem with my test map then?
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
This part is not inside a damage handler. Use UnitDamageTargetEx only when responding to a damage event from the DDS. In other cases(such as here) use UnitDamageTarget.
JASS:
method onUnitHit takes unit u returns nothing
        if IsUnitEnemy(u,GetOwningPlayer(this.caster)) then
            call DestroyEffect( AddSpecialEffectTarget( HIT, u, "chest") )
            call UnitDamageTargetEx(this.caster,u,this.damage,true,false,ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
        endif
    endmethod
 
Level 24
Joined
Aug 1, 2013
Messages
4,658
The system detects damage.
When damage is taken from basic attacks or spells (basically every single damage type), the system's main function "OnDamage()" is called.
This one calls the onDamage event.

As in the map, all units are given the spell "reduction" ability, the spell damage is actually the negative value of the original amount.
This means that all negative damage is spell damage and all positive damage is physical damage.
The system itself multiplies the spell damage with -1 so the damage will be positive again.

The problem with UnitDamageTarget() is that the system detects it as normal damage done by normal units.
However the function UnitDamageTarget() is able to do much more than a simple spell damage or auto-attack.
The system is not made to handle it properly and therefor detects it as simple spell damage (if your damage type is spell or magic or whatever) or as physical damage.
This is sometimes not what you inteded to have.

Coded damagetype is only used when the system's replacement for UnitDamageTarget() and UnitDamageTargetBJ() is called.
As you use the normal native (or GUI action), you still just deal damage on which the system responds.
That is why it is spell damage instead of coded damage.
(However I still am not convinced that coded damage should be an actual damage type next to physical or spell damage. But that is just how I see it.)

UnitDamageTargetEx() should only be called when you are inside a trigger that runs on the damage event... I still have no idea why it is not allowed to have a direct attack instead of running it in a specific order but that is how this system works.
When outside of the damage event, you seem to have to use the normal action.

UnitDamageTargetEx() stacks attacks and releases (applies) them when the next onDamage event has ended (at least that is what I remember).
So that it took 1s for it to run means that it took 1s to have the next normal damage been applied.

So after all, reason to use UnitDamageTargetEx() and UnitDamageTarget() in one map is pretty messed up.
 
Level 11
Joined
Dec 3, 2011
Messages
366
Well, hi again, I don't know if this was discussed or not. It's about the locust swarm ability. I change the Locust swarms to negative and warms unit with hero attack type will reduce the hero HP instead of healing him. So I kept the Damage Return Factor possitive (in my case 0.25) and things went normal. The warning about Locust Swarm is right when locust unit has Spells attack type. (didnt try the rest, though)
 
Again me ;]

I use library SetUnitMaxState. Just want to be sure it will not interfere with this DDS


JASS:
//////////////////////////////////////////////////////////////////////////////////////////
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//@     SetUnitMaxState
//@=======================================================================================
//@ Credits:
//@---------------------------------------------------------------------------------------
//@     Written by:
//@         Earth-Fury
//@     Based on the work of:
//@         Blade.dk
//@
//@ If you use this system, please credit all of the people mentioned above in your map.
//@=======================================================================================
//@ SetUnitMaxState Readme
//@---------------------------------------------------------------------------------------
//@
//@ SetUnitMaxState() is a function origionally written by Blade.dk. It takes advantage of
//@ a bug which was introduced in one of the patches: Bonus life and mana abilitys will
//@ only ever add the bonus ammount for level 1. However, when removed, they will remove
//@ the ammount they should have added at their current level. This allows you to change a
//@ units maximum life and mana, without adding a perminent ability to the unit.
//@
//@---------------------------------------------------------------------------------------
//@ Adding SetUnitMaxState to your map:
//@ 
//@ Simply copy this library in to a trigger which has been converted to custom text.
//@ After that, you must copy over the abilitys. This is made easy by the ObjectMerger in
//@ JASS NewGen. Distributed with this system are //! external calls to the ObjectMerger.
//@ Simply copy both of them in to your map, save your map, close and reopen your map in
//@ the editor, and remove the external calls. (Or otherwise disable them. Removing the !
//@ after the // works.)
//@
//@---------------------------------------------------------------------------------------
//@ Using SetUnitMaxState:
//@
//@ nothing SetUnitMaxState(unit <target>, unitstate <state>, real <new value>)
//@
//@     This function changes <target>'s unitstate <state> to be eqal to <new value>. Note
//@ that the only valid unitstates this function will use are UNIT_STATE_MAX_MAN and
//@ UNIT_STATE_MAX_LIFE. Use SetUnitState() to change other unitstates.
//@
//@ nothing AddUnitMaxState(unit <target>, unitstate <state>, real <add value>)
//@
//@     This function adds <add value> to <target>'s <state> unitstate. <add value> can be
//@ less than 0, making this function reduce the specified unitstate. This function will
//@ only work with the unitstates UNIT_STATE_MAX_LIFE and UNIT_STATE_MAX_MANA.
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//////////////////////////////////////////////////////////////////////////////////////////
library SetUnitMaxState initializer Initialize
globals
//========================================================================================
// Configuration
//========================================================================================

    // The rawcode of the life ability:
    private constant integer MAX_STATE_LIFE_ABILITY = 'A0I8'
    
    // The rawcode of the mana ability:
    private constant integer MAX_STATE_MANA_ABILITY = 'A0I9'
    
    // The maximum power of two the abilitys use:
    private constant integer MAX_STATE_MAX_POWER = 8
endglobals

//========================================================================================
// System Code
//----------------------------------------------------------------------------------------
// Do not edit below this line unless you wish to change the way the system works.
//========================================================================================

globals
    private integer array PowersOf2
endglobals

function SetUnitMaxState takes unit u, unitstate state, real newValue returns nothing
    local integer stateAbility
    local integer newVal = R2I(newValue)
    local integer i = MAX_STATE_MAX_POWER
    local integer offset
    
    if state == UNIT_STATE_MAX_LIFE then
        set stateAbility = MAX_STATE_LIFE_ABILITY
    elseif state == UNIT_STATE_MAX_MANA then
        set stateAbility = MAX_STATE_MANA_ABILITY
    else
        debug call BJDebugMsg("SetUnitMaxState Error: Invalid unitstate")
        return
    endif
    
    set newVal = newVal - R2I(GetUnitState(u, state))
    
    if newVal > 0 then
        set offset = MAX_STATE_MAX_POWER + 3
    elseif newVal < 0 then
        set offset = 2
        set newVal = -newVal
    else
        return
    endif
    
    loop
        exitwhen newVal == 0 or i < 0
        if newVal >= PowersOf2[i] then
            call UnitAddAbility(u, stateAbility)
            call SetUnitAbilityLevel(u, stateAbility, offset + i)
            call UnitRemoveAbility(u, stateAbility)
            set newVal = newVal - PowersOf2[i]
        else
            set i = i - 1
        endif
    endloop
endfunction

function AddUnitMaxState takes unit u, unitstate state, real addValue returns nothing
    call SetUnitMaxState(u, state, GetUnitState(u, state) + addValue)
endfunction

private function Initialize takes nothing returns nothing
    local integer i = 1
    
    set PowersOf2[0] = 1
    loop
        set PowersOf2[i] = PowersOf2[i - 1] * 2
        set i = i + 1
        exitwhen i == MAX_STATE_MAX_POWER + 3
    endloop
endfunction
endlibrary

 
Well, I'm not so sure about that,
lfh told us :
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.
that's why Im asking, and I begin to think that I have to edit SetUnitMaxState library to match DDS requirements..
 
If you didn't edit Gameplay Constant in section: Combat-Damage Bonus Table Chaos use this: (by default its set to 1.00 in all fields)
  • Untitled Trigger 002
    • Events
    • Conditions
    • Actions
      • Unit - Cause (Triggering unit) to damage (Triggering unit), dealing 500.00 damage of attack type Chaos and damage type Enhanced
or from running damage event trigger:
JASS:
call UnitDamageTargetEx(udg_PDD_source, udg_PDD_target, 500.00, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_ENHANCED, null)
so you got "pure" damage ignoring armor also hitting spell immune enemies
 
Level 6
Joined
Jan 4, 2014
Messages
227
Exactly ZiBitheWand3r3r and Edo, Pure damage = chaos + universal, but i want a way to detect it.

ps : pure damage is the same as HP removal but it can kill units
 
Level 6
Joined
Jan 4, 2014
Messages
227
Sorry for the Double post, but the system did duplicate my last post, so i'm gonna just edit the second copy.

i found this :

call UnitDamageTargetEx(udg_target, udg_source, 50.0, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)

i understand that you can deal undetectable damage with that, and you can ofc detect pure damage with the system, but what i want to know, is if there is a way to detect if the damage is chaos or universal ?

Thanks
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
Pure damage = chaos + universal, but i want a way to detect it.

Only if you didn't modify the gameplay constants table though.

i understand that you can deal undetectable damage with that

No, thats wrong. UnitDamageTargetEx is not for dealing undetectable damage. It is for dealing damage from a running onDamage handler (and should not be used in a different place).

but i want a way to detect it.

The system detects chaos + universal damage just as any other damage too.

is if there is a way to detect if the damage is chaos or universal

No.

However, if your damage is triggered, you can just set a global integer variable to your type before dealing the damage and then read that variable in the onDamage event. If you have nested (recursive) damage events you will need some kind of indexing, but with that you can detect any triggered damage.
 
Level 6
Joined
Jan 4, 2014
Messages
227
OK, thanks looking_for_help, but i have a question, if i have modified game play constants like how much INT will give mana or how much EXP i gain from killing units or heroes will this make chaos + universal not dealing pure damage ?

Wietlol, i'm coding blade mail wich returns the same amount of damage dealt to the hero which is pure damage and it must no be redetected back
 
Level 24
Joined
Aug 1, 2013
Messages
4,658
You would have to create another kind of damage type.
I call it a Hit-Type.

Most used Hit-Types are BasicAttack, SpellDamage or PureDamage/HiddenDamage.

It is quite easy to implement... if you understand how the system works.

(Shame UnitDamageTargetEx() doesnt work with DisableTrigger() and EnableTrigger())
 
Level 11
Joined
Dec 3, 2011
Messages
366
OK, thanks looking_for_help, but i have a question, if i have modified game play constants like how much INT will give mana or how much EXP i gain from killing units or heroes will this make chaos + universal not dealing pure damage ?

Wietlol, i'm coding blade mail wich returns the same amount of damage dealt to the hero which is pure damage and it must no be redetected back

To prevent the redirected, add the condition
  • PDD_damageType not Equal to PDD_CODE
since the damage you deal by the blade mail is detected as CODE
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
OK, thanks looking_for_help, but i have a question, if i have modified game play constants like how much INT will give mana or how much EXP i gain from killing units or heroes will this make chaos + universal not dealing pure damage ?

No that won't have any effect. Only if you modify the armor values in the game play constants table it might be affected.
 
Level 13
Joined
Sep 25, 2013
Messages
717
Adds Defend to all Units

Hello! I really like this system, its going to be very useful for me. I just installed it in my map and I as testing it out and it seems to work perfectly. However, i have this one problem. The system added a custom defend ability that i had made to every unit. I have no experience in working with Jass and I'm a moderate GUI user at best. This problem has something to do with a spell ID code. Here's my map if you have time to take a look. The system is at the bottom category of the triggers. Thanks for making this great system!
 

Attachments

  • Lotr Conquest v 1.5 Jangus the 3rd.w3x
    7.8 MB · Views: 76
Level 6
Joined
Jun 11, 2014
Messages
66
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?
 
Level 3
Joined
Apr 2, 2013
Messages
41
I very like your system , it is useful for my map .Can i ask a question ?
Can this system change damage type of units ?
for example , i have (unit A) with 100 Hero dmg attacking (unit B) with 99999 Hero armor which mean 0 dmg , i want change unit A now deal 100 magic dmg to B which mean 75 dmg (mgdmg vs Hero armor).
If it can ,pls give me example how to use , thank you.
 
I very like your system , it is useful for my map .Can i ask a question ?
Can this system change damage type of units ?
for example , i have (unit A) with 100 Hero dmg attacking (unit B) with 99999 Hero armor which mean 0 dmg , i want change unit A now deal 100 magic dmg to B which mean 75 dmg (mgdmg vs Hero armor).
If it can ,pls give me example how to use , thank you.

use the DamageModifier. Heal the unit with the receive amount from the physical damage(so it negates the damage), then deal damage with the same amount ,tho with different damage type and attack type.
 
Level 6
Joined
Jun 11, 2014
Messages
66
That happens when you crash the thread(do too many things at once or attempt something impossible). The most common cause seems to be dealing damage from a damage handler.

Dealing damage from a damage handler would mean doing a "cause unit to damage unit" action inside a "PDD_damageEventTrigger becomes equal to 1.00" event, right? If so I have none of that in my map. And I can't imagine that casting a standard, non triggered spell would be considered trying to do too many things at once or something impossible. I mean, the OnDamage trigger does check for a few things, like if they have item x or y, or ability z, but my other map checks for way more things and has never had this problem.
It also seems to never happen against creeps, and only occurs against computers when they are in "panic mode", when my army overwhelms them in their town, they are running into orc burrows, etc. Makes me think it has something to do with the AI. I also tested this against AMAI and had the same results. My other map I referred to before is not a melee map, so no melee AI, and it does not have this problem.


EDIT: I switched my DDS to Bribes GUI Damage Engine, using the cross compatibility with this DDS. I've only tested about 5 matches, but so far the bug hasn't reoccurred. I'll keep testing though.
 
Last edited:
Level 3
Joined
Mar 5, 2007
Messages
32
Hey there,
I'm having some issues with this system and Locust/Chaos.
I installed it in accordance with the instructions, and to start out, everything seemed to work flawlessly.

Now, I have created a unit called Rocks that's supposed to function as a new resource source, pretty much identical to trees. To simulate this feeling of a destructible, I'm using the Locust/Chaos trick to make the Rocks unselectable and yet targetable/attackable.

However, it seems that sooner or later PDD just stops working for a the Rocks. It won't detect any damage done to the rocks, even if it detects damage for all other units. The strange thing is that this happens at seemingly random times during testing, I have a hard time pin-pointing the exact culprit. I happens within a minute or so, though, or a few seconds.

Granted, I have a few other triggers as well (unrelated to PDD), but I only seem to have the issue when I have the Locust/Chaos trigger Enabled.

Here's the trigger, credits to rulerofiron99 from another thread:
  • UnselectableRocks
    • Events
      • Elapsed game time is 0.00 seconds.
    • Conditions
    • Actions
      • Unit Group - Pick every unit in Rocks_Group and do (Actions)
        • Custom script: local unit udg_Temp_Unit_1
        • Set Temp_Unit_1 = (Picked Unit)
        • Custom Script: call UnitAddAbility( udg_Temp_Unit_1, 'Aloc' )
        • Unit - Add Chaos (Rocks Dummy) to Temp_Unit_1
        • Custom script: call UnitRemoveAbility( udg_Temp_Unit_1, 'Aloc' )
        • Wait 0.00 seconds
        • Trigger - Turn off (This trigger)
        • Unit - Add Chaos (Rocks) to Temp_Unit_1
        • Trigger - Turn on (This trigger)
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
Hey there,
I'm having some issues with this system and Locust/Chaos.
I installed it in accordance with the instructions, and to start out, everything seemed to work flawlessly.

Now, I have created a unit called Rocks that's supposed to function as a new resource source, pretty much identical to trees. To simulate this feeling of a destructible, I'm using the Locust/Chaos trick to make the Rocks unselectable and yet targetable/attackable.

However, it seems that sooner or later PDD just stops working for a the Rocks. It won't detect any damage done to the rocks, even if it detects damage for all other units. The strange thing is that this happens at seemingly random times during testing, I have a hard time pin-pointing the exact culprit. I happens within a minute or so, though, or a few seconds.

Granted, I have a few other triggers as well (unrelated to PDD), but I only seem to have the issue when I have the Locust/Chaos trigger Enabled.

From this explanation I would guess the problem occurs exactly when the trigger is refreshed. Try changing udg_PDD_TRIGGER_CLEANUP_PERIOD = 60.0 to something very high, like 1000000.0. Then check if the problem still exists. If not, its because of the trigger cleanup.
 
Level 3
Joined
Mar 5, 2007
Messages
32
Thank you, looking_for_help, changing the trigger cleanup period to 1000000 fixed my problem.

What implications does this have for PDD? Does it start leaking if you don't cleanup every once in a while - and if so, would it be significant?
 
Level 24
Joined
Aug 1, 2013
Messages
4,658
The problem is that locust units are not re-added to the trigger because they are not grouped in GroupEnumUnitsInRect().

@lfh
To avoid that, loop through all units of Player(i) looping through all 16 players.

@BBt
It does leak one trigger event per unit that is created.
When a unit is removed from the map (either after being dead for a while or removed by triggers) the event remains and might even keep some of the unit's data.
That data remains there until you close the map.
It is going to matter if many units are created and/or if the map is played for a long time.
 
Level 30
Joined
Jul 23, 2009
Messages
1,033
I am trying to use this system to make certain units deal damage in a line with their autoattacks but I can't seem to make it work. I have tried setting the PDD_amount to 0 and then have the source unit deal damage to units in a unit group, but I guess you can't have an action for a unit damaging another unit within a trigger with the event PDD_damageEventTrigger becomes equal to 1.00.

I was thinking of just nulling the damage and spawn a dummy, every time a unit attacks, that travels forward and deals damage to nearby enemies but it does not feel optimal.
Is there any way to deal damage within the damageEventTrigger?
 
Top