• 🏆 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 3
Joined
Jan 19, 2009
Messages
27
System works fine for me except for 1 thing. Since I implemented it, every unit in the game got lightning shield spell, even though they have 0 spells in unit editor... Could this be the problem with 2 custom spells I added... I also tried removing those 2 spells, but lightning shield spell is still there, and system works...
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
System works fine for me except for 1 thing. Since I implemented it, every unit in the game got lightning shield spell, even though they have 0 spells in unit editor...

Then you have setup a wrong ability ID for the spells used by the system. Please make sure to follow the installation instructions step by step. You have to import the two custom abilities in your object editor and then add the correct IDs of those abilities to the system.

Search for this function in the systems code:

JASS:
function InitGlobalVariables takes nothing returns nothing
    // Put here the correct ability IDs
    set udg_DAMAGE_TYPE_DETECTOR = 'A000'
    set udg_SET_MAX_LIFE = 'A001'
   
    // Here you can configure some stuff, read the documentation for further info
    set udg_SPELL_DAMAGE_REDUCTION_ITEM = 'brac'
    set udg_SPELL_RESISTANCE_AUTO_DETECT = false
    set udg_ETHEREAL_DAMAGE_FACTOR = 1.66
    set udg_BRACERS_SPELL_DAMAGE_REDUCTION = 0.33
    set udg_TRIGGER_CLEANUP_PERIOD = 60.0
endfunction

Here were it says 'A000' and 'A001' you have to put the correct IDs of the abilities used by the system.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Is there a way to not display blocked damage?
EX:
Damage dealt is 50
blocked amount is 25
Displayed damage amount is still 50?

Hi, well it is already showing the correct amount of damage. However, make sure that you display the damage after you modified it ;)

Compare the Archmage in the testmap: he deals zero damage which is also displayed correct.
 
Level 3
Joined
Dec 22, 2009
Messages
39
Hi. Uhm, can I use this system along with Vexorian's Caster System?
Also can I change the what the type of damage dealt? For example I will change the phyiscal damage of a unit into magic?
Does damage reduction from armor comes first before this system?
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
Hi,

Hi. Uhm, can I use this system along with Vexorian's Caster System?

I don't know Vexorians Caster system, but you can use this system in combination with every other system. The only exception are systems that use another DDS than this one, obviously.

Also can I change the what the type of damage dealt? For example I will change the phyiscal damage of a unit into magic?

Yes, you can do this quite easy: On Physical damage set the damage amount of the desired unit to zero and use the systems function UnitDamageTargetEx to deal the desired damage (example how to use that function is in the testmap).

You can pass any Attack- and Damagetype you wish to that function.

Does damage reduction from armor comes first before this system?

Yes.
 
Level 3
Joined
Dec 22, 2009
Messages
39
How can implement the 1.1.0.0 update? I tried downloading the updated map and copying the new DamageEvent trigger to my map to replace the old one but when i use the old one i can't compile, it says that UnitDamageTargetEx function is undeclared. But it works on the old one.

Yes, you can do this quite easy: On Physical damage set the damage amount of the desired unit to zero and use the systems function UnitDamageTargetEx to deal the desired damage (example how to use that function is in the testmap).

You can pass any Attack- and Damagetype you wish to that function.

What if i want it to deal 'amount' damage? I've tried doing what you said and other stuff including saving amount into a local to save its amount then setting amount to zero. Ive been trying to convert the damage from Carrion Swarm into a phyiscal Pierce type damage. But it deals no damage at all. Can you give me a much more concrete example if you dont mind?

Everything works fine on the test map but map my ruins everything. Is it possible for a map to be bricked?
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
How can implement the 1.1.0.0 update? I tried downloading the updated map and copying the new DamageEvent trigger to my map to replace the old one but when i use the old one i can't compile, it says that UnitDamageTargetEx function is undeclared. But it works on the old one.

This usually happens if the library is placed in the wrong order in the trigger treeview. Please perform the following steps:

  • Deactivate all triggers that use that function and save the map
  • Make sure that the DamageEvent trigger is above all triggers that use the DDS in your map
  • Now save your map again, exit the World Editor and start it again
  • Re-activate your triggers, they should work now


What if i want it to deal 'amount' damage? [...] Can you give me a much more concrete example if you dont mind?

Sure:

  • OnDamage
    • Events
      • Game - damageEventTrigger becomes Equal to 1.00
    • Conditions
    • Actions
      • Custom script: local real stored_amount = udg_amount
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • 'IF'-Conditions
          • damageType Equal to PHYSICAL
        • 'THEN'-Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • 'IF'-Conditions
              • source Equal to Archmage 0001 <gen>
            • 'THEN'-Actions
              • Set amount = 0.00
              • Custom script: call UnitDamageTargetEx(udg_source, udg_target, stored_amount, false, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_UNIVERSAL, null)
              • Game - Display to (All players) for 10.00 seconds the text: Magic Damage!
            • 'ELSE'-Actions
        • 'ELSE'-Actions
Here, the archmage (or whatever unit you want) deals magic damage, because you pass ATTACK_TYPE_MAGIC to the function. The damage isn't affected by the targets armor because of the passed damagetype, which is DAMAGE_TYPE_UNIVERSAL. If you want different behavior, choose the corresponding damagetypes and attacktypes. For a good explanation about those types, please refer to this overview.
 
Level 3
Joined
Dec 22, 2009
Messages
39
This usually happens if the library is placed in the wrong order in the trigger treeview. Please perform the following steps:

  • Deactivate all triggers that use that function and save the map
  • Make sure that the DamageEvent trigger is above all triggers that use the DDS in your map
  • Now save your map again, exit the World Editor and start it again
  • Re-activate your triggers, they should work now

Thank you very much, I love you.
This usually happens if the library is placed in the wrong order in the trigger treeview. Please perform the following steps:

  • Deactivate all triggers that use that function and save the map
  • Make sure that the DamageEvent trigger is above all triggers that use the DDS in your map
  • Now save your map again, exit the World Editor and start it again
  • Re-activate your triggers, they should work now

Thank you very much, I love you.

So everything that comes from the UnitDamageTargetEx function will be classified neither SPELL nor PHYSICAL right?
Well, kinda made some magic resistance triggers that checks if the damage was SPELL, does that mean even if I trigger the damage as ATTACK_TYPE_MAGIC, and DAMAGE_TYPE_MAGIC, it won't be considered as SPELL therefore, won't be reduced by the magic resistance?
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Thank you very much, I love you.

Thx :D


So everything that comes from the UnitDamageTargetEx function will be classified neither SPELL nor PHYSICAL right?

Correct, but ...

Well, kinda made some magic resistance triggers that checks if the damage was SPELL, does that mean even if I trigger the damage as ATTACK_TYPE_MAGIC, and DAMAGE_TYPE_MAGIC, it won't be considered as SPELL therefore, won't be reduced by the magic resistance?

... every damage will be reduced correctly, depending on it attacktype/damagetype. If you really want to detect the damagetype of UnitDamageTargetEx (for whatever reason), you can do this very easily by defining a custom global integer variable, something like udg_myDamageType.

Then, right before you call UnitDamageTargetEx, set this variable to the corresponding damagetype. In the onDamage trigger, just read the variable (on Code damage), and then reset it to some initial value.

With this simple "trick" you have total control. If you have problems implementing this, feel free to ask again.
 
Level 3
Joined
Dec 22, 2009
Messages
39
Every damage will be reduced correctly, depending on it attacktype/damagetype. If you really want to detect the damagetype of UnitDamageTargetEx (for whatever reason), you can do this very easily by defining a custom global integer variable, something like udg_myDamageType.

Then, right before you call UnitDamageTargetEx, set this variable to the corresponding damagetype. In the onDamage trigger, just read the variable (on Code damage), and then reset it to some initial value.

With this simple "trick" you have total control. If you have problems implementing this, feel free to ask again.

"Does that mean that "udg_myDamageType" will be a new kind of damage along with SPELL and PHYSICAL? So, if I am going to check magic, I am going to check if it's "SPELL" or "udg_myDamageType", did I get it right?" <- Well I guess this statement is wrong, sorry I don't really get what you mean :(

Now I think I get it (though i am not sure), but would it be safe to say that it would not conflict with other damage instances?

PS: I am really sorry for being amateur
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
Now I think I get it (though i am not sure), but would it be safe to say that it would not conflict with other damage instances?

Yes, it would be 100% safe. Just don't forget to reset the custom damage type everytime you read it out.

PS: I am really sorry for being amateur

You don't have to be sorry, everbody starts as an amateur ;)
 
Level 14
Joined
Nov 17, 2010
Messages
1,265
I've got a strange problem and I can't seem to figure out why.

I have all of my abilities triggered in my map and use your DDS for Critical Strikes and a custom mana shield.

The strange thing is that when I use my abilities, instead of dealing damage they do the opposite... The really strange thing is that I don't directly use the DDS in any of the abilities, but when I disable it and test the map the abilities all work correctly.

To make things even more confusing, the abilities heal enemies for a while and then begin to work properly again, but every once in a while they will heal them instead of deal damage once again.

The damage done by my abilities is a calculation that involves a real number a custom real number called spell power and the level of the ability for the most part.

So hopefully this is a problem you have come across before and maybe I just need to tweak something to make it work correctly. I'd post triggers, but I don't even know where to begin...
 
Level 14
Joined
Nov 17, 2010
Messages
1,265
Good Lord I'm an idiot.

Please disregard the above post, it turns out I set the Ability IDs incorrectly. It works perfectly now. I must admit it's a big relief haha.
 
Level 16
Joined
May 2, 2011
Messages
1,345
Hello looking for help
Can I ask is there a way to detect peirce/normal/siege damage types to put them in conditions?
Edit: I saw there is variable called attack type or something....

also, just tested armor and certain amount reduction
so is there a way to make this damage detect system llike hardened skin ability? i.e. damage reduction come before armor reduction


e.g. one have armor make 50%=0.5 reduction,
hardneed skin do 10 damage reduce
damage is 50

Warcraft ability resut:
0.5 X (50-10) = 0.5 X40 = 20

damage detect system result:
(0.5 X 50) - 10 = 25 -10 = 15

quite significantly different isnt it?

------------------------------
~~just tested stuff~~
ok I was thinking to replace the elune grace ability, but it seems the ability work for peirce reduction but only need to be fixed with spell reduction (which is easy
)
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
Hello looking for help
Can I ask is there a way to detect peirce/normal/siege damage types to put them in conditions?
Edit: I saw there is variable called attack type or something....

No there is no way to do this. If you want to detect this you have to index all your units and then check in your onDamage handler for the unit-type of the damage source. Then you can infer the concrete attacktype. This approach however is map dependent.

also, just tested armor and certain amount reduction
so is there a way to make this damage detect system llike hardened skin ability? i.e. damage reduction come before armor reduction

quite significantly different isnt it?

The system (like all other DDS) detects damage after damage reduction. Damage reduction from Abilities like hardened skin get detected correctly, it will always be the reduced damage.


ok I was thinking to replace the elune grace ability, but it seems the ability work for peirce reduction but only need to be fixed with spell reduction (which is easy
)

I don't understand what you are talking about. First the system doesn't use Elunes Grace at all, but Runed Braces. Second you can't replace it and then "fix it with spell reduction". You either use a spell reduction ability like Runed Braces with this system (as it is intended) or the system won't work.

It would be better if you tell what you are trying to achieve instead of making such asumptions.
 
Level 12
Joined
May 20, 2009
Messages
822
Hello. Is there any compatibility with Weep's system? If not, oh well. I'll just have to transfer everything over. ;)

I don't need basic damage detection anymore, I need exactly what this system does. (Tell the difference between Physical, Spell, and Trigger damage)

EDIT: This also does not detect 0 damage. <=[
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
EDIT: This also does not detect 0 damage. <=[

It is useless to detect 0 damage because many invalid damage "events" cause zero damage, thats why no DDS detects it.

Update to Version 1.1.2.0
- Replaced bj_mapInitialPlayableArea with GetWorldBounds() which fixes a bug with the former one that disabled the DDS under certain circumstances
 
Level 4
Joined
Feb 4, 2013
Messages
43
One of the best pdds ever created i have used it extensively in my map . 89 % my spell use this system. There is a bug in xode damage like when damage assingned is too high it doesn.t cause damage. I don.t use it ... i have seen many pdds they are good but one problem is they either require jngp or they give lesser functionalty... thnx alot for this system... by the way can u develop a system which can detect hero spell chaos pierce magic damage... or detect armour type i would be gratefull... any way i am using your 1.00.2 version i can.t replace it cause i changed it is there any diff bw 1.002 and 1.11 ::thumbs_up:

5 star

You can detect armour type if you no damage const of a map. Also u have noted use ur setunitlife func which sometimes which results creepy effects when abused extensivelly whats diff bw urs and native function of blizard.. one of the major flaw i have noticed is when no of units that attacks each other increase the damage amount changes inside triger.. we get wrong source and target unit... thnx ur implementations are simple to understand and edit .. plz tell me if you have created other systems....
 
Last edited:
Level 4
Joined
Feb 4, 2013
Messages
43
I have read your jass forum ... read every link nestharus and cokemonkey gave.....
I have one recomendation plz add PDSS_ before all your variables cause i used multiple dds so when i started using urs i removed thiers but i had hard time removing thier global vars from my campaign ( pain ) .... ;
Also i found this bug when i use pick group it is also picking corpse even when i specially check for isunitalive ... cause problems like corpse knock back...
One funny thing i have noticed is when a unit with envenomed dies pdds detects damage done by that corpse if poision buff is active... ( not a bad thing ) ...
Also it cant diff bw magic and spells... ^-^

Any system that doesn require jngp is gui for me ...
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
One of the best pdds ever created i have used it extensively in my map . 89 % my spell use this system.

Thanks, thats nice to hear.

by the way can u develop a system which can detect hero spell chaos pierce magic damage... or detect armour type i would be gratefull...

No, thats in general not possible because we have until now no way to determine the attacktype of the damage. However, this is also not really neccessary, because it can be easily solved by indexing your unit types.

any way i am using your 1.00.2 version i can.t replace it cause i changed it is there any diff bw 1.002 and 1.11 ::thumbs_up:

Yes, of course there are differences between 1.0.0.2 and 1.1.2.0, otherwise I wouldn't have made these updates. So you should really update the system, that would also solve your other problem:


Also i found this bug when i use pick group it is also picking corpse even when i specially check for isunitalive ... cause problems like corpse knock back...

This bug was fixed a long time ago, thats why you should update your system. I also don't really see why you modified the system, that shouldn't be neccessary at all. Maybe you got some concept of the system wrong, so it would help if you tell what modifications you made and why.

Also u have noted use ur setunitlife func which sometimes which results creepy effects when abused extensivelly whats diff bw urs and native function of blizard..

The native functions won't work reliable if you use damage modification.


One funny thing i have noticed is when a unit with envenomed dies pdds detects damage done by that corpse if poision buff is active... ( not a bad thing ) ...

Yes, of course, everything else would be wrong.

Also it cant diff bw magic and spells... ^-^

I don't understand. The system is there to differentiate between physical and spell damage which does work. Magic damage in Wc3 is considered physical, so it does differentiate those two.
 
Level 4
Joined
Feb 4, 2013
Messages
43
However, this is also not really neccessary, because it can be easily solved by indexing your unit types

Or
You can asign them dummy ability and check there level to determine thier attack type defense type.... etc....
Either way both of them are map specific....

The best thing about wc3 is its unertainity... no matter how muchever time pass there will be some bugs which we can exploit to create new system.... we just have to look harder..
 
Level 11
Joined
Oct 11, 2012
Messages
711
How does this DDS compare to Nes's? I am looking for a DDS which can differentiate Spell and Physical damage, but my map has a considerable amount of spell reduction abilities. So does Nes's system also need to use triggered spell reduction abilities like this one?

Edit: I used Nes's system before and as far as I can recall, I did not use any triggered spell reduction abilities, but I'm not sure if it is required. However, Nes's DDS has some weird bug that couldn't be fixed and caused my map to crash, so I'm looking for an alternative.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
How does this DDS compare to Nes's? I am looking for a DDS which can differentiate Spell and Physical damage, but my map has a considerable amount of spell reduction abilities. So does Nes's system also need to use triggered spell reduction abilities like this one?

Yes, every DDS that can differentiate Spell and Physical damage requires the user to code all his abilities based on runed bracers or elunes grace. However, as his is just a bunch of ifs and set variable blocks, even if you have 50 abilities like that it should be a work of about 5-10 minutes.
 
Level 11
Joined
Oct 11, 2012
Messages
711
Yes, every DDS that can differentiate Spell and Physical damage requires the user to code all his abilities based on runed bracers or elunes grace. However, as his is just a bunch of ifs and set variable blocks, even if you have 50 abilities like that it should be a work of about 5-10 minutes.

Well, if that's the case, I will use your DDS coz it's the best on HIve now. :D
Thanks!
 
Level 17
Joined
Nov 13, 2006
Messages
1,814
i like this system, even i dont changed my current one because anyway to both need reconfigure the stuffs (like spells with trigger damage if want make INT/Hero lv depend damage) but i must agree for more place where peoples use non custom/non trigger spells this got advantage for gui users

(maybe faster too than bribe one, or no?)
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Update to Version 1.2.0.0
- Fixed a bug that sometimes wrong target and source units were asigned
- All global variables used by this system now have the prefix PDD_, so that naming conflicts are avoided. To use variables like target or amount just refer to PDD_target or PDD_amount and so on.
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
addDamageHandler still doesn't work

Ups, you are right. I forgot the "udg_" prefix in the string for the TriggerRegisterVariableEvent. This is fixed now with the new mini-update:

Update to Version 1.2.0.1
- Fixed a bug that AddDamageHandler didn't work
- Added the system function UnitDamageTargetEx to the documentation in the comment section of the system

Before I go to bed, just wanted to share my thoughts on LFH projects, again.
uber duper super.

And here is the heart: <3

Thank you very much, thats nice to hear :)

I can help you test this out, if you have some problems reproducing bugs ofc, tomorrow afternoon.

Thanks, with the new update everything should work fine (at least I hope so^^)
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
as you asked for evidence.

Not evidence, its just that without the compile error I can't know whats going wrong, you know?

Did you follow the installation instructions from the documentation?

looking_for_help said:
  • 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.

Especially the first point, did you check "Automatically create unknown variables" and copied and safed the VariableCreator trigger to your map before the DDS?
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
yes. Actually, even in your map it has compile error(when i click syntax check), but there i can turn on the trigger and save the map without popping up errors.

It has no errors. You should just not use the "Syntax Check" button, because it doesn't work properly. The only valid compiler output is what you get when saving the map. And if the map safes (whats of course the case in the test map) then everything is fine.

The "Syntax Check" button is not reliable as it cannot check for dependencies from outside the current trigger. Therefore it is worthless and was removed in the new TESH.

would you liek me to send you the map?

No, I can see from the screenshots you uploaded that you are using an old version of the DDS (this can be seen on the variable names which are missing the PDD prefix). Just update the systems code with the newest version and everything will work fine.
 
No, I can see from the screenshots you uploaded that you are using an old version of the DDS (this can be seen on the variable names which are missing the PDD prefix). Just update the systems code with the newest version and everything will work fine.

but i downloaded this dds, what else dds there is, ok i'l ltry.

edit: yeah, i updated it, no errors, thanks.
 
Level 17
Joined
Nov 13, 2006
Messages
1,814
don't got a version where u use variable array and not hashtable?
i already use more hashtable (~7-8) and i guess it would slower than with variable array, no?

if i don't use runed bracer and other stuff then still need this only my own custom defence system what is based on variables?

JASS:
    // Register spell reduction above 100%
//here
    if udg_PDD_SPELL_RESIST_AUTO_DETECT then
        set udg_PDD_amount = GetUnitSpellResistance(udg_PDD_target)*udg_PDD_amount
//till here included with Getunitspell ressistance
    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

another question is possible ignore the wc3 damage reduction what amor make? lets say i want creep's doing 100% dmg iniferrent how much armor i get...
if possible then how.


How to calculate petcentage decreasr like
set amount =amount - 20%?

JASS:
set amount = amount / 100 * percentage

so if u want only 20% then
JASS:
set amount = amount / 100 * 20 
//or
set amount = amount / 5
//if u want 120% then 
set amount = amount + amount/100*percentage
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
don't got a version where u use variable array and not hashtable?
i already use more hashtable (~7-8) and i guess it would slower than with variable array, no?

Not really. In theory it is a very tiny bit slower, but the difference is neglectable. There were benchmarks made to test this and yoi can measure the difference only in constructed, very unrealistic situations.

And the number of hashtables doesn't affect their speed, a hashtable just gets slower if you store really a lot in it (so having more seperated hashtables is even "faster" than having just one "big" one. However, same applies here, for any realistic mapping scenario you won't come even close to that point. And 7-8 hashtables is nothing, so you shouldn't worry about this.

If you do have performance problems, it is most likely due to some inefficient triggering (fast periodic timers)/memory leaks and so on.[/QUOTE]

if i don't use runed bracer and other stuff then still need this only my own custom defence system what is based on variables?

Well no, you don't really need it if you don't care that ethereal units aren't damaged more by spell damage anymore and the damage table for spells above 100% is ignored. But I wouldn't change the code itself, just set udg_PDD_SPELL_RESIST_AUTO_DETECT to false and udg_PDD_ETHEREAL_DAMAGE_FACTOR to 1.0.


another question is possible ignore the wc3 damage reduction what amor make? lets say i want creep's doing 100% dmg iniferrent how much armor i get...
if possible then how.

Not directly, no. This system was only made for damage detection and modification, but not for bonus detection or modification. However, you have several options to achieve what you want:

  • Set the damage table to 100% for all damage types and trigger all damage reductions in your map, then you have total control.
  • If you only want certain spells/actions to ignore a units armor, you can use the systems function.DealFixDamage to deal damage regardless of the armor.
  • Use an Armor Detection System to recalculate and modify the current damage based on the units armor.

None of these is really easy, unfortunatly.
 
[trigger=]
Events
Conditions
Actions
call UnitDamageTargetEx(udg_PDD_target, udg_PDD_source, 50.0, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)[/trigger]

How come that throws a error? It expects a function name, latest version and also previous versions have this problem. Why is that? It works but when you disable then enable trigger it refuses to work.

You could also fix other problems such as life drain by giving the unit a certain variable or hashtable value to be registered to detect if finger of death or life drain was casted on it.
 
Level 17
Joined
Nov 13, 2006
Messages
1,814
[trigger=]
Events
Conditions
Actions
call UnitDamageTargetEx(udg_PDD_target, udg_PDD_source, 50.0, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)[/trigger]

How come that throws a error? It expects a function name, latest version and also previous versions have this problem. Why is that? It works but when you disable then enable trigger it refuses to work.

You could also fix other problems such as life drain by giving the unit a certain variable or hashtable value to be registered to detect if finger of death or life drain was casted on it.
u would try put every function to header
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
How come that throws a error? It expects a function name, latest version and also previous versions have this problem. Why is that? It works but when you disable then enable trigger it refuses to work.

Well thats a general problem with the vanilla Editor and one of the reasons why vJass was developed. So this problem is general and not really related to the DDS.

You could also fix other problems such as life drain by giving the unit a certain variable or hashtable value to be registered to detect if finger of death or life drain was casted on it.

Well an extra spellpack as a seperate resource including handmade versions of those spells would be the preferable solution IMO. At the moment I don't have the time to make such a pack...
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
Someone asked how to remove the usual blizzard damage formula.
Firstly, you have to know the armor amount. There's simply no way over it that I know of(if you want to use the armor display for something, that is).

Secondly, set the armor damage reduction to 0.
This only works for positive armor, so for negative armor use this part in the damage handler:
JASS:
if armor < 0 then//The negative armor formula by blizzard can not be entirely disabled, so we have to undo it. 
    set amount = amount / (2-Pow(0.94,I2R(-armor)))
endif

When you've triggered the armor damage reduction yourself, then you can also easily bypass it to deal true damage.
 
Top