• 🏆 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 17
Joined
Nov 13, 2006
Messages
1,814
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.

problem no native for check armor value, since want bypass in set dmg trigger and that make it more heavier than already it is (talking about create dummy unit who attack new unit type on map for check the armor)

since could have many creep type i can't use a simple formula vs all creep/neutral hero type
 
Level 17
Joined
Nov 13, 2006
Messages
1,814
I store the base armor of every relevant unit type in a variable and then change the value according to whatever buffs it gets from abilities. This can only be done when all armor gains are triggered though.

ouch, storeing every unit armor is a bit painfull :/
or do you got something trick? or all write it manually? or did with dummy unit at map init?
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
I don't mind having my systems map-specific. Besides, there usually aren't THAT many relevant unit types. If there would be, I would reconsider.
Also, testing the armor amount of a unit is simply not possible if you use a custom armor system, because the damage reduction multiplier in constants is simply 0.
Yes, I write them down manually. It's not much work though.
 

sentrywiz

S

sentrywiz

Awesome system, fairly easy to implement with the clear instructions.

I have a question though. Because I've implemented two different triggers I am interested how will they interact with each other. In one there is a chance to crit for double damage and in another there is a chance to dodge. Will there be some confusion, for example if a crit and a dodge occurs at the same time, will the crit pass the dodge or will the dodge take priority? Crit sets amount to amount * 2.00 and dodge puts amount to 0.00.
 
Level 17
Joined
Nov 13, 2006
Messages
1,814
Awesome system, fairly easy to implement with the clear instructions.

I have a question though. Because I've implemented two different triggers I am interested how will they interact with each other. In one there is a chance to crit for double damage and in another there is a chance to dodge. Will there be some confusion, for example if a crit and a dodge occurs at the same time, will the crit pass the dodge or will the dodge take priority? Crit sets amount to amount * 2.00 and dodge puts amount to 0.00.

i did this this way:


random=0,99

if random < dodge chance then
set dmg = 0
else
random=0,99
if random < crit_Rate then
set dmg = dmg *2
endif
endif

simple, can't crit if dodge, personally i think dodge have prior because criting then dodgeing for me no point
 

sentrywiz

S

sentrywiz

i did this this way:


random=0,99

if random < dodge chance then
set dmg = 0
else
random=0,99
if random < crit_Rate then
set dmg = dmg *2
endif
endif

simple, can't crit if dodge, personally i think dodge have prior because criting then dodgeing for me no point

Well, maybe a unit would crit, but you would still dodge the crit. It works either way. I was just interested how does the system count priority when the trigger occurs for multiple units.

But its better to check dodge first then crit, I agree.
 
Level 17
Joined
Nov 13, 2006
Messages
1,814
Well, maybe a unit would crit, but you would still dodge the crit. It works either way. I was just interested how does the system count priority when the trigger occurs for multiple units.

But its better to check dodge first then crit, I agree.

reason senseless calculate anything dmg related if it will dodged anyway, so that useless math if ur formula more complex (more dmg modifier factor than crit, like custom defence, amplifier etc)
 
Level 17
Joined
Nov 13, 2006
Messages
1,814
here the map with little modification (added floating text and hero revive)

system stop working sometimes (randomly, sometimes need ~2-3min or more for see it)

[EDIT]

tryed with this too but same bug, so not only reviveable hero

maybe related with magic? occured after phoenix was summoned and mountain king got 1 dmg but still for a awhile everything was normal

seems if hero is neutral hostile or player 2 unit is indifferent
 

Attachments

  • Physical Damage Detection v1.2.0.1 GUI_TEST.w3x
    40.9 KB · Views: 45
Level 14
Joined
Dec 12, 2012
Messages
1,007
here the map with little modification (added floating text and hero revive)

system stop working sometimes (randomly, sometimes need ~2-3min or more for see it)


The system doesn't stop working, it runs perfectly fine in the map you uploaded.

Its just that you spam text tags without recycling/cleaning them up. Wc3 has a maximum amount of text-tags, which you exceed. Therefore no more text-tags are created after some time.

Use "Display Message" to show the damage or clean up your text-tags properly and you will see that the system continous to work as expected.
 
Level 2
Joined
Apr 5, 2009
Messages
18
I have sent a pm to looking_for_help but I couldn't see it in my sent box, so i am going to post my question here in case.

Whenever I create a custom item ability (without giving it to a unit, just purely creating it), it gets applied to every unit on the map. I'm 99% sure its due to the system since when i removed the PDD trigger, the ability was no longer applied to any unit. Do we have to work around this, or is it just a bug, or is it something else?

Thanks in advance
 
I have sent a pm to looking_for_help but I couldn't see it in my sent box, so i am going to post my question here in case.

Whenever I create a custom item ability (without giving it to a unit, just purely creating it), it gets applied to every unit on the map. I'm 99% sure its due to the system since when i removed the PDD trigger, the ability was no longer applied to any unit. Do we have to work around this, or is it just a bug, or is it something else?

Thanks in advance

It's your lack of knowledge that is the bug. To fix this user error bug go to "DamageEvent" and find "[jass=]//////////////////////////////////////////////////////////////////////////////////
// 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'[/code]"

In object editor --> go to View --> Display Values As Raw Data
Then go to the Abilities tab then find the two abilities you were required to copy and paste to your map.
"DAMAGE_TYPE_DETECTOR"
"SET_MAX_LIFE"
They'll display the code you have to type into those two values above this. Match the codes with the abilities into the right spots into the trigger.
Example: A000: Alsr(DAMAGE_TYPE_DETECTOR)
You want the A000 to go into "set udg_PDD_DAMAGE_TYPE_DETECTOR = "YourCode" While the other needs the same thing with the other ability.

User bug fixed. :thumbs_up:
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Question:

What if I use UnitDamageTarget and then used the options ATTACK_TYPE_HERO and DAMAGE_TYPE_NORMAL, would it still be detected as a Physical Damage?

Such things can be easily tested by trying, right ;)
But yes, its of course physical damage. Whether a damage is physical or spell is determined by the attack type, and not by the damage type. And ATTACK_TYPE_HERO is physical.

If so, is there a way to detect IF a physical damage comes from an attack?

What you mean with "attack"? A melee attack? If so, then no, there is not. The purpose of this system is just to differentiate physical and spell damage, but not ranged or melee.

User bug fixed.

Thanks :)
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
@lfh

The reason is that some would attempt to create Critical strikes and evasions,cleaves,etc

Well its no problem to create Critical strikes (and similar abilities like the ones you mentioned) with this system. I created a critical strike once with this system and it worked perfectly fine. Any unit by default has only one way to attack - so any physical damage is either a melee attack if the unit is a melee unit or a ranged attack if the unit is a range unit.

Exceptions are for example melee heros that carry an orb and attack a flying unit - however, you most likely want to trigger a Critical Strike on them too, just like in standard melee Wc3.

So basically I don't understand what kind of problem you mean. Could you elaborate?
 
What i meant is that if the damage came from a normal attack like this ideal scenario:
Unit 1 attacks Unit 2 -> executes Physical damage event
Unit 1 uses an ability that deals physical damage -> executes physical damage event
Unit 1 is used in a code to deal physical damage via UnitDamageTarget -> executes physical damage event.

From the scenario above,if someone creates a triggered Critical(damage mod) all 3 above will do a critical.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
What i meant is that if the damage came from a normal attack like this ideal scenario:
Unit 1 attacks Unit 2 -> executes Physical damage event
Unit 1 uses an ability that deals physical damage -> executes physical damage event
Unit 1 is used in a code to deal physical damage via UnitDamageTarget -> executes physical damage event.

From the scenario above,if someone creates a triggered Critical(damage mod) all 3 above will do a critical.

Thats what CODE damage type was invented for. Triggered damage can always be identified perfectly, so your scenario is not really a problem when creating a critical strike.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
So Im gonna check first if it is physical then filter it again if it comes from a code? if so, THANKS!

No, CODE damage is just for reflecting damage, so it depends on what you want exactly. But the idea is the same, you just have to encode it yourself:

You would use an integer value that encodes your own damage type. So before you deal damage with triggers, you set that value. In the OnDamage event you check that value, and if it is set you reset it and know that it was caused by triggered damage. If the value is not set (for example zero), you know it was a "real" attack.

I am creating a damage mod system which creates Misses, Critical Strikes, Splashes, Cleaves, etc.

That should work without problems with this system.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
I got 3 questions:

Why do you actually give the unit the health ability?

Why do you return when the amount of damage taken is 0?

Why are other stuff (like the spell reduction) hardcoded in the system?

EDIT: I actually downloaded this system to find out how you get the damage type.
Turns out that Phisical Damage is basic attacks and Spell Damage is abilities.

But if I would copy and paste that specific thing into my system, I got exacly the same.
Except that I never use a Hashtable, I never use timers, I have 0 code compared to this.
No offense... (I never used this system but there are many people who are very happy with it.) But I cannot find the reason why everything is added to it.

EDIT 2: Ok one more: Why do you restrict people not to use wait timers?
 
I got 3 questions:

Why do you actually give the unit the health ability?

Why do you return when the amount of damage taken is 0?

Why are other stuff (like the spell reduction) hardcoded in the system?

EDIT: I actually downloaded this system to find out how you get the damage type.
Turns out that Phisical Damage is basic attacks and Spell Damage is abilities.

But if I would copy and paste that specific thing into my system, I got exacly the same.
Except that I never use a Hashtable, I never use timers, I have 0 code compared to this.
No offense... (I never used this system but there are many people who are very happy with it.) But I cannot find the reason why everything is added to it.

EDIT 2: Ok one more: Why do you restrict people not to use wait timers?

So the unit doesn't die before you interact with it as damage is dealt.

To catch any time when a unit hits another.

Because it has to be hardcoded to detect proper spell damage and to tell the difference between spell/magic and physical. Easy enough to make your own spell reduction with this, and you can even stack stuff now like add 5% of strength as spell reduction... etc.

Physical damage isn't just basic attacks, its some of the damage types from triggers.

I don't understand
Wietlol said:
But if I would copy and paste that specific thing into my system, I got exacly the same.
Except that I never use a Hashtable, I never use timers, I have 0 code compared to this.
No offense... (I never used this system but there are many people who are very happy with it.) But I cannot find the reason why everything is added to it.

Well he isn't restricting people, it just won't work when the variables are globals because they are not MUI/MPI and will be overwritten with new data when another unit hits another. You have to work around that and make your own system to counter this problem.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
If i give a unit 99999999 basic damage and put a trigger in my system that will reduce the damage to 0 it works.

I got spells that deal 0 damage and then will be changed via triggers.
0 damage is still a hit event.

There are some ways how you could avoid it.
The thing is that (maybe) some people dont want a specific thing in it what is not required to make the DDS work.
I should put that spell reduction in a different trigger and if you turn that trigger on it works and if not then it doesnt.

I just tested it and I saw that you could see the difference between the damage from spells and the damage from basic attacks.
I dont know about trigger damage if that will also be caught as physical damage but I think that the AA / Spell is the biggest difference.

He is restricting people.
Try to put this in your system:
(Yes there is a new unit variable TempUnit but you could figure.)
  • Test
    • Events
      • Game - PDD_damageEventTrigger becomes Equal to 1.00
    • Conditions
    • Actions
      • Custom script: local unit udg_TempUnit
      • Set TempUnit = PDD_target
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Unit-type of PDD_source) Equal to Archmage
        • Then - Actions
          • Wait 2.00 game-time seconds
          • Unit - Kill TempUnit
        • Else - Actions
This is completely MUI.
So???

We strictly want accurate results.
I know about timers being inaccurate, but you give this to GUI users. Otherwise change the name.
Back in the days, I was in love with the wait time.
After that I found timers.
Even now I use some wait timers now and then.
(I made them leakless and less data consuming... more like advanced wat timers.)

There are people who still use it because they dont know about timers or they dont care about the small difference.
Now people cannot use them because you want to have 0.00001 better performance from conditions?
 
I know about timers being inaccurate, but you give this to GUI users. Otherwise change the name.
Back in the days, I was in love with the wait time.
After that I found timers.
Even now I use some wait timers now and then.
(I made them leakless and less data consuming... more like advanced wat timers.)

There are people who still use it because they dont know about timers or they dont care about the small difference.
Now people cannot use them because you want to have 0.00001 better performance from conditions?

You should really contact Nestharus
 
Yes I could do that... but I dont know why I should.

Limiting map makers by making normal actions have errors and stop the trigger is not good system programming IMO.
No matter why you do it and why not.

Nestharus is the other one who knows a lot in Warcraft. the other is DSG.
Nestharus also the one who created most of the Timer libs we use today.
 
Nestharus is the other one who knows a lot in Warcraft. the other is DSG.
Nestharus also the one who created most of the Timer libs we use today.

They're not the only ones you know. I know stuff DSG didn't as well hasn't he moved onto SC2? I do like how he still tries to help though in the WC3 section, I really respect that.

Nestharus is a VJASS'er only so he isn't much help here really. He's only a genius there, doesn't mean much really. :grin: If you want an answer and you don't mind that it'll be VJASS then Nestharus can solve any question there, that's true however he wouldn't know the other sections of WC3 just as good unless he posts here to prove me wrong. :xxd:

They're knowledgeable and intelligent, but don't forget about everyone else too. We're all very smart, well most of us. :thumbs_up: Just remember it is very unlikely they will help you with GUI questions, DSG will likely provide you with a giant wall of text that redefines your question and confuses you which might or might not resolve your question while Nes provides you with the solution you want however in VJASS.
 
They're not the only ones you know. I know stuff DSG didn't as well hasn't he moved onto SC2? I do like how he still tries to help though in the WC3 section, I really respect that.

Nestharus is a VJASS'er only so he isn't much help here really. He's only a genius there, doesn't mean much really. :grin: If you want an answer and you don't mind that it'll be VJASS then Nestharus can solve any question there, that's true however he wouldn't know the other sections of WC3 just as good unless he posts here to prove me wrong. :xxd:

They're knowledgeable and intelligent, but don't forget about everyone else too. We're all very smart, well most of us. :thumbs_up: Just remember it is very unlikely they will help you with GUI questions, DSG will likely provide you with a giant wall of text that redefines your question and confuses you which might or might not resolve your question while Nes provides you with the solution you want however in VJASS.

And you thought Nestharus only knows better in vJASS? He even know how Warcraft 3 environment works,that's why he created or can create things which we thought is impossible(such as Codeless Save Load)
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
vJASS is actually the same as GUI... in concept.

GUI is something where people can click on commands and it will be translated into JASS.
vJASS is something where people can write commands and it will be translated into JASS.

If you can use vJASS, you can also use JASS.

BTW: I do see now why 0 damage events have to be excluded.
And I see why you hardcoded the spell reduction in the system.
Didn't see that it was required before.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
If i give a unit 99999999 basic damage and put a trigger in my system that will reduce the damage to 0 it works.

I got spells that deal 0 damage and then will be changed via triggers.
0 damage is still a hit event.

There are some ways how you could avoid it.
The thing is that (maybe) some people dont want a specific thing in it what is not required to make the DDS work.
I should put that spell reduction in a different trigger and if you turn that trigger on it works and if not then it doesnt.

I just tested it and I saw that you could see the difference between the damage from spells and the damage from basic attacks.
I dont know about trigger damage if that will also be caught as physical damage but I think that the AA / Spell is the biggest difference.

He is restricting people.
Try to put this in your system:
(Yes there is a new unit variable TempUnit but you could figure.)
  • Test
    • Events
      • Game - PDD_damageEventTrigger becomes Equal to 1.00
    • Conditions
    • Actions
      • Custom script: local unit udg_TempUnit
      • Set TempUnit = PDD_target
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Unit-type of PDD_source) Equal to Archmage
        • Then - Actions
          • Wait 2.00 game-time seconds
          • Unit - Kill TempUnit
        • Else - Actions
This is completely MUI.
So???


I know about timers being inaccurate, but you give this to GUI users. Otherwise change the name.
Back in the days, I was in love with the wait time.
After that I found timers.
Even now I use some wait timers now and then.
(I made them leakless and less data consuming... more like advanced wat timers.)

There are people who still use it because they dont know about timers or they dont care about the small difference.
Now people cannot use them because you want to have 0.00001 better performance from conditions?

There are a lot of events that fire 0 damage events, which are not wanted(When you cast stormbolt, when the buff is applied, a 0 damage event is fired). This is why you cant have.

You dont use global variable, you use technique called variable name shadowing.

Waits are hilariously inaccurate. In multiplayer, the delay for wait is always at least the ping to host. Waits do not give two shits if your wc3 is even actively running, and they will happily keep running when your wc3 is minimalized for instance.

Waits themselves are neither leaking nor data consuming, so I dont know how you can improve that.

Nevertheless, you can not run waits or timers from native damage event either without variable name shadowing, so I dont see the problem.

Also Nestharus wrote Codeless save/load, but it is useless because anything above roughly 10 bytes takes infinity to sync.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Well I should have said Game-Waits (PolledWait())
They leak by creating a timer without setting it to null again.
Also they do not pause the timer... I forgot why that was necessary.

I create PolledWaits without timers... almost.
I use gametime instead of timers. When the polledwait starts I simply ask for what gametime it is and then I add the duration of the timer to it.
Whenever the gametime reaches the value that the polledwait wants, it stops looping the trigger sleeps and is therefor finished.

I know I dont use global variables but this system does not support waits.
LFH said (and I can understand) that it is not because of the performance but the problem if the DDS would re-initialize (removing events of dead units), while one of those wait timers is still running.
If so the action is destroyed anyway.

This is new for me too :) but I just uploaded the concept of doubled triggers.
However the system needs a different event for the re-initialization to make it work.
 
None of the damage detection systems support waits, because there is no way to do so.

There's actually a few ways, just they aren't meant to be part of DDS's so they're considered clutter or extra systems.
Wietlol said:
Well I should have said Game-Waits (PolledWait())
They leak by creating a timer without setting it to null again.
Also they do not pause the timer... I forgot why that was necessary.

I create PolledWaits without timers... almost.
I use gametime instead of timers. When the polledwait starts I simply ask for what gametime it is and then I add the duration of the timer to it.
Whenever the gametime reaches the value that the polledwait wants, it stops looping the trigger sleeps and is therefor finished.

I know I dont use global variables but this system does not support waits.
LFH said (and I can understand) that it is not because of the performance but the problem if the DDS would re-initialize (removing events of dead units), while one of those wait timers is still running.
If so the action is destroyed anyway.

This is new for me too :) but I just uploaded the concept of doubled triggers.
However the system needs a different event for the re-initialization to make it work.
Easiest way is just to shadow the names with locals If you really want to use waits. Otherwise learn to index or hashtable. http://www.hiveworkshop.com/forums/...u-should-know-when-using-triggers-gui-233242/
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Easiest way is just to shadow the names with locals If you really want to use waits. Otherwise learn to index or hashtable. http://www.hiveworkshop.com/forums/...u-should-know-when-using-triggers-gui-233242/

Dont worry Dat-C3, I know how to write triggers... I suppose.
I only feel that people (not me) are happy enough with wait timers.
Even I use them sometimes. When I dont care about perfect accuracy or if it would be too much to use a hashtable and timer and save the stuff etc. I use wait timers.

Here is an example where I use 2 wait timers when perfect accuracy is not required.

I prefer to use JASS only but in stuff that I send to other people I make the system in JASS (mostly) and make it GUI configurable.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
that means the spell/system(your submission) is broken.

If I alt-tab, the game wont run, but the wait will keep going, and when it ends it will start Jass Interpreter again, resulting in broken spells and systems
trigger sleep action = TriggerSleepAction()
wait timer = PolledWait() because it is a wait inside a timer...
timers = CreateTimer()

It is NOT running when the game is not running.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
JASS:
//  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.
Can you explain why should we do these steps? You said that they are required for all DDS. I don't get how they could affect how DDS works.


JASS:
//
//  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.
What are those functions doin' here? This is DDS not BonusMod or something.
JASS:
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
I don't want to use your fatty function, I want to use GetWidgetLife normally.

This is worse event handling I have seen so far.
JASS:
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

You should add somekind of prefix to your function names:
function AfterDamage takes nothing returns nothing
I think it (and some others) can collide with user defined functions easily.
function MapInit takes nothing returns nothing
function UnitEntersMap takes nothing returns nothing

Further review will be made based on your answer upon this:
JASS:
//  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.
Can you explain why should we do these steps? You said that they are required for all DDS. I don't get how they could affect how DDS works.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Can you explain why should we do these steps? You said that they are required for all DDS. I don't get how they could affect how DDS works.

The fact that you have to ask this questions means you didn't even try locust swarm with your own DDS a single time.

However, before criticizing, I expect you to do some research beforehand. If you just would have tried out on your own what happens (in any DDS) if those steps are not performed you would know. And then you would not have to ask such questions or state the installation process as "complicated". Changing two values in the object editor is everything, but not complicated.

So: do the research first, then come again and ask your questions.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
The fact that you have to ask this questions means you didn't even try locust swarm with your own DDS a single time.

However, before criticizing, I expect you to do some research beforehand. If you just would have tried out on your own what happens (in any DDS) if those steps are not performed you would know. And then you would not have to ask such questions or state the installation process as "complicated". Changing two values in the object editor is everything, but not complicated.

So: do the research first, then come again and ask your questions.

Of course I have tested locust swarm, it deals damage to caster on return instead. But still don't get the prob with bracer item.

Remember, our DDS (due to current spell damage type detection method) will not come in harmony with wc3 native abilities, tons of them have bizarre behavior that causes some strange bugs. Locust Swarm is just one example, who knows the healing amount is actually a negative spell "damage"? Blizz shouldn't have done that. Who knows why unit with high damage such as 2000 could causes bug to Spirit Link ability? Why? Can you explain? Why do bracer item convert spell damage to physical damage? Are those our fault? No. And the point is, it's not our DDS' responsibility to get rid of those bugs. If you want to remove them all one by one, I'm afraid you could encounter another tons of awaiting bugs right there.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
Of course I have tested locust swarm, it deals damage to caster on return instead. But still don't get the prob with bracer item.

Remember, our DDS (due to current spell damage type detection method) will not come in harmony with wc3 native abilities, tons of them have bizarre behavior that causes some strange bugs. Locust Swarm is just one example, who knows the healing amount is actually a negative spell "damage"? Blizz shouldn't have done that. Who knows why unit with high damage such as 2000 could causes bug to Spirit Link ability? Why? Can you explain? Why do bracer item convert spell damage to physical damage? Are those our fault? No. And the point is, it's not our DDS' responsibility to get rid of those bugs. If you want to remove them all one by one, I'm afraid you could encounter another tons of awaiting bugs right there.

I've used the one by lfh and the only bug it has it that it breaks the GUI condition for "is unit dead".
Locust swarm does not deal negative damage to yourself as far as I know, but rather it's because it considers spell resistance. Thus, since the actual damage is negative, then a factor of it is also. This is why the returned amount is negative and no, it doesn't even have to be returned as damage(if it would be, the system would actually fix it automatically).
Haven't investigated spirit link.
Bracers don't truly change spell damage into physical damage. They only reduce it and it just so happens that they can reduce it beyond 100%. Thus, this is used for detecting whether damage was magical or not, because this same ability doesn't apply to physical damage and thus, the only way to deal negative damage is by spells.
Those things are not really that hard to fix if you know about them and yes they are the responsibility of a DDS, because breaking a map just because you're lazy is unjustifiable. Undefined and/or unexpected behaviour should be avoided.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
I've used the one by lfh and the only bug it has it that it breaks the GUI condition for "is unit dead".
Locust swarm does not deal negative damage to yourself as far as I know, but rather it's because it considers spell resistance. Thus, since the actual damage is negative, then a factor of it is also. This is why the returned amount is negative and no, it doesn't even have to be returned as damage(if it would be, the system would actually fix it automatically).
That's why I said
It's undetectable.

Haven't investigated spirit link.
Bracers don't truly change spell damage into physical damage. They only reduce it and it just so happens that they can reduce it beyond 100%. Thus, this is used for detecting whether damage was magical or not, because this same ability doesn't apply to physical damage and thus, the only way to deal negative damage is by spells.
Those things are not really that hard to fix if you know about them and yes they are the responsibility of a DDS, because breaking a map just because you're lazy is unjustifiable. Undefined and/or unexpected behaviour should be avoided.

How about Thorn Aura and Spiked Carapace or any damage reflecting abilities bugs? If user modifies the damage amount at damage event, damage reflection's amount wont be based on that new modified damage. That would be nasty to be fixed. That's why I will just say, "my DDS is not made for wc3 native abilities" :p
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Of course I have tested locust swarm, it deals damage to caster on return instead. But still don't get the prob with bracer item.

Remember, our DDS (due to current spell damage type detection method) will not come in harmony with wc3 native abilities, tons of them have bizarre behavior that causes some strange bugs. Locust Swarm is just one example, who knows the healing amount is actually a negative spell "damage"?

There are not tons of strange bugs, but only very few which can be handled manually.

A DDS shouldn't change gameplay mechanics by default, right? So if there is the posiblity to fix a bug then why not do it? Especially if it is so extremly easy to fix? Changing one value in the object editor takes maybe 10 seconds and has to be done only once per map. So I don't get your problem with this?

If you don't use locust swarm at all in your map you can of course just omit this step. But if you use it, you might want it to work correct, right?

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.

Why? Can you explain?

No, why should I?



That's why I will just say, "my DDS is not made for wc3 native abilities" :p

Then there is no point in using this system at all. Because the damage type detection technique is only useful for native abilities. If you have triggered spells only, you don't need damage type detection because you can just asign the damage type yourself. Triggered damage can always be detected perfectly without the use of the bracers trick.

If only triggered abilities are used, its better to use a DDS without damage type detection support. Because triggered damage type identification is trivial.
 
Top