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

HealEvent

Status
Not open for further replies.

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Detect when a unit is healed by using a periodic timer which rules out the unit's regeneration. The problems with this resource are:

  • There's no way to accurately get the healer unless you trigger the heal using UnitHealUnit.
  • If there is more than one heal within the interval the events will merge.
  • If the unit has very high regeneration (over 100 per second at 0.10 interval and 10.00 HEAL_THRESHOLD), its regen could be mistaken for a heal. To circumvent this, you can decrease the constant INTERVAL or increase the constant HEAL_THRESHOLD.

JASS:
library HealEvent initializer Init requires DamageEngineGUI

private function UnitFilter takes unit u returns boolean
    return GetUnitAbilityLevel(u, 'Aloc') == 0
endfunction

globals
    //Minimum heal to pass as an actual event. Should be greater than a unit's
    //life regeneration per INTERVAL
    private constant boolean HEAL_THRESHOLD = 10.00

    //Less decreases the chance of two overlapping heals, but decreases performance.
    private constant real INTERVAL = 0.067
endglobals

globals
    private constant string HEAL_EVENT = "healEventVariable"
    real healEventVariable = 0.00

    private timer interval = CreateTimer()
    private integer count = 0
    private boolean array inSys
    private integer array indices
    private integer array indexRef
    private real array lastLife
    private real array regen
    private boolean array regenerating
    private real heal = 0.00
    private unit target = null
    private unit source = null
    private unit swap
endglobals

//Returns true if the unit is in the process of regenerating health
function IsUnitRegenerating takes unit u returns boolean
    return regenerating[GetUnitUserData(u)]
endfunction

//This function is an approximation and can't tell the difference between healing
//salves, healing wards, fountain of health and natural regeneration. If the
//unit is at full health or dead, this will return 0.00 (even if they otherwise
//have regeneration).
function GetUnitRegeneration takes unit u returns real
    return regen[GetUnitUserData(u)] / INTERVAL
endfunction

function GetHealEventAmount takes nothing returns real
    return heal
endfunction

function GetHealEventTarget takes nothing returns unit
    return target
endfunction

function GetHealEventSource takes nothing returns unit
    return source
endfunction

function CreateHealEventTrigger takes code toRun returns trigger
    return CreateRealEventTrigger(HEAL_EVENT, 1.00, toRun)
endfunction

private function UnitHealUnitEx takes unit src, unit tgt, real amount, boolean heals returns boolean
    local integer id = GetUnitUserData(tgt)
    local real life = GetWidgetLife(tgt)
    local boolean b
    if heals then
        call SetWidgetLife(tgt, life + amount)
        set amount = GetWidgetLife(tgt) - life
        set b = amount != 0.00
    else
        set amount = life - regen[id]*TimerGetRemaining(interval)/INTERVAL - lastLife[id]
        set b = amount >= HEAL_THRESHOLD
    endif
    if b then
        //Don't let the automatic event run as we're running the event manually
        set lastLife[id] = lastLife[id] + amount

        //caching with a swap variable because handling recursion is ugly
        set swap = src
        set src = source
        set source = swap

        set swap = tgt
        set tgt = target
        set target = swap

        set life = heal
        set heal = amount

        //finally, fire the event
        set healEventVariable = 0.00
        set healEventVariable = 1.00
        set healEventVariable = 0.00

        //set event globals back to where they were
        set heal = life
        set target = tgt
        set source = src

        //These parameters need to be nulled since I used "set" on them
        set tgt = null
        set src = null
    endif
    return b
endfunction

function CheckPendingHeal takes unit u returns boolean
    return UnitHealUnitEx(null, u, 0, false)
endfunction

function UnitHealUnit takes unit s, unit t, real r returns boolean
    call CheckPendingHeal(t)
    return UnitHealUnitEx(s, t, r, true)
endfunction

function UpdateHealEvent takes unit u returns nothing
    local integer id = GetUnitUserData(u)
    set lastLife[id] = GetWidgetLife(u) - regen[id]*TimerGetRemaining(interval)/INTERVAL
endfunction

//Function runs 1.00/INTERVAL times per second
private function OnExpire takes nothing returns nothing
    local integer i = count
    local real life
    local real diff
    loop
        exitwhen i == 0
        set i = i - 1
        set i = indices[i]

        set target = udg_UDexUnits[i]
        set life = GetWidgetLife(target)
        set diff = life - lastLife[i]
        set lastLife[i] = life

        set life = regen[i]
        set heal = diff - life
        if heal >= HEAL_THRESHOLD then
            set healEventVariable = 1.00
            set healEventVariable = 0.00

        elseif diff != 0.00 then
            if not regenerating[i] then
                set regenerating[i] = true
                set regen[i] = diff
            else
                set regen[i] = (life + diff) * 0.50
            endif
        elseif regenerating[i] then
            set regenerating[i] = false
        endif

        set i = indexRef[i]
    endloop
endfunction

private function OnDamage takes nothing returns nothing
    call CheckPendingHeal(udg_DamageEventSource)
endfunction

private function OnAfterDamage takes nothing returns nothing
    call UpdateHealEvent(udg_DamageEventSource)
endfunction

private function OnCreate takes nothing returns nothing
    if not inSys[udg_UDex] and UnitFilter(GetIndexedUnit()) then
        set inSys[udg_UDex] = true
        set indices[count] = udg_UDex
        set indexRef[udg_UDex] = count

        set lastLife[count] = GetWidgetLife(GetIndexedUnit())

        set count = count + 1
    endif
endfunction

private function OnRemove takes nothing returns nothing
    local integer index
    local integer pop
    if inSys[udg_UDex] then
        set inSys[udg_UDex] = false
        set count = count - 1
        set indices[indexRef[udg_UDex]] = indices[count]
        set indexRef[indices[count]] = indexRef[udg_UDex]
    endif
endfunction

private function Init takes nothing returns nothing
    call TimerStart(CreateTimer(), INTERVAL, true, function OnExpire)

    call CreateRealEventTrigger("udg_DamageEvent", 1.00, function OnDamage)
    call CreateRealEventTrigger("udg_AfterDamageEvent", 1.00, function OnAfterDamage)

    //The unit can be added to the system when it's first created
    call CreateRealEventTrigger("udg_UnitIndexEvent", 1.00, function OnCreate)

    //when it's unloaded from a transport
    call CreateRealEventTrigger("udg_CargoEvent", 2.00, function OnCreate)

    //or when it's resurrected.
    call CreateRealEventTrigger("udg_DeathEvent", 2.00, function OnCreate)

    //A unit should be removed from the system if it was removed from Unit Indexer,
    call CreateRealEventTrigger("udg_UnitIndexEvent", 2.00, function OnRemove)

    //when it's loaded into a transport,
    call CreateRealEventTrigger("udg_CargoEvent", 1.00, function OnRemove)

    //when it begins reincarnating,
    call CreateRealEventTrigger("udg_DeathEvent", 0.50, function OnRemove)

    //when it dies,
    call CreateRealEventTrigger("udg_DeathEvent", 1.00, function OnRemove)

    //or is removed from the game. Whichever comes first.
    call CreateRealEventTrigger("udg_DeathEvent", 3.00, function OnRemove)
endfunction

endlibrary
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Once I am confident I've sorted out the logic with this system, I will release it in GUI. The problem is that GUI will have a tedious syntax conpared to JASS, so it will be harder for me to administer for bugs. I've been working on many side projects lately so I haven't even gotten around to seeing if this thing even compiles nor have made a test map.

My biggest gripe is that the system doesn't support getting the healing unit without triggering all heals. I could do a check when an ability is cast on an allied unit and if that unit was healed within a certain time by an anonymous healer it might be that unit, but multiple consecutive casts could throw that off (for example one priest heals while the other casts inner fire). It also doesn't take into consideration area-effect heals like healing wave/spray, doesn't deal with slow healing by salves/fountain of health.

Also, if a priest heals and a Paladin holy lights the same unit at the same time, the event will show for 200/400/600+25 instead of two separate heals. It's far from perfect.

How would a GUI manual-heal look like? I could do it in Knockback2D/DamageEngine style...

  • Setup
    • Events
    • Conditions
    • Actions
      • Set NextHealEventSource = hooman
      • Set NextHealEventTarget = poop
      • Set NextHealEventAmount = 13.37
      • Trigger - Run HealEvent <gen> (Checking conditions)
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
My biggest gripe is that the system doesn't support getting the healing unit without triggering all heals. I could do a check when an ability is cast on an allied unit and if that unit was healed within a certain time by an anonymous healer it might be that unit, but multiple consecutive casts could throw that off (for example one priest heals while the other casts inner fire). It also doesn't take into consideration area-effect heals like healing wave/spray, doesn't deal with slow healing by salves/fountain of health.

Also, if a priest heals and a Paladin holy lights the same unit at the same time, the event will show for 200/400/600+25 instead of two separate heals. It's far from perfect.

Yup, I think that it's far from being ready : ). I've yet to come up with a solution to those things either, which is why I've never done something like this : P.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Even with the merging of events and the lack if being able to find a source without triggers, this can still be useful for triggering grievous wounds (-50% healing) or spirit visage (%boost to heals) from League of Legends. It's better than nothing. Plus, if the user does trigger their heals, they don't even need the periodic timer for anything but detecting regen which is less important.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
The only way to detect heal event perfectly is make it ourself and remove the original heal and replace all with trigger like your DDS.

A small list to give a sense of what kind of project that would be:

abilities:

heal
holy light
healing wave
healing spray
touch of life
moon well
locust swarm
life drain
death coil
eat tree
cannibalize
building repair

items:
many kinds of potions
healing salve
rune of healing and its various levels
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Level 13
Joined
Jul 15, 2007
Messages
763
Out of curiosity, if you have to trigger your heals to use this system properly, why do you need the system at all? Since if you're triggering your heals, you already know what the source and targets are.

I guess this system is for if you want to modify healing as it's done (e.g. you make a spell that reduces healing received by the target by 50%), but you can already consider this in a triggered spell's healing.

What would be neat is if this system interacted with all native healing spells in the game (though i'm gathering that's unlikely).
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Status
Not open for further replies.
Top