• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Heal Engine 1.0.3

General

Import & Config

Code

Changelog


Purpose

This system is meant to be (or become) a complete event system for healing.
Based on Bribe's Heal Event for GUI, it is able to detect native heals and regeneration, but provides more tools for triggered healing and associated events.

This system will avoid recursive calls and will postpone any triggered heal created during events, avoiding variables conflicts.

Events List

Game - PreHealEvent Becomes Equal to 1.00 - Triggered heal only - This event fires when a heal is created, but not yet applied. Use this event to apply any modifiers you want. Depending of your config, PreHealEvent can be 2.00, 3.00 etc., several distinct steps before applying the heal to have a clear separation of modifiers.
Game - AfterHealEvent Becomes Equal to 1.00 - Native and Triggered heal - This event runs when a healed has been applied.
Game - AfterHealEvent Becomes Equal to 0.50 - Regen HP - This event runs when HP regen (excluding HP regen from heroes strength) is detected.
Game - ZeroHealEvent Becomes Equal to 1.00 - Triggered heal only - This event fires when healing amount is too small.
Game - OverHealEvent Becomes Equal to 1.00 - Triggered heal only - This event runs when applied healing was more than target's missing HP.
Game - NegativeHealEvent Becomes Equal to 1.00 - Triggered heal only, needs ALLOW_NEGATIVE_HEALING set to true in config - This event runs when heal become negative during PreHealEvent, thus dealing damage.

Available variables

HealSource - Who casted the heal. Native heal and regen might have this to null (no unit), depending on your config.
HealTarget - Who is being healed.

HealAmount - How much healing is being done at the current step
HealBaseAmount - Initial healing amount, before any modification. This variable should be treated as readonly.
HealPrvAmount - Healing amount at the start of this step. This variable should be treated as readonly.

EffectiveHealAmount - How much healing was done. Access this in AfterHealEvent and/or in OverHealEvent.
OverHealAmount - When OverHealEvent is fired, use this to know how much overhealing have been done. Access this in AfterHealEvent and/or in OverHealEvent.

IsSelfHeal - Is the caster healing himself ?

How to heal using this system ?

To heal a unit using this system, you can either :
1/ Directly use this engine function
Code:
call Heal.HealUnit(source, target, amount)
or
2/ you can set 3 variables and run Heal Unit Trigger. GUI users, I've got your back :grin:
  • Set NextHealSource = the one doing the healing
  • Set NextHealTarget = the unit being healed
  • Set NextHealAmount = the amount to be healed
  • Trigger - Run Heal Unit <gen> (ignoring conditions)

Requirements

No requirement is only using triggered healing without HealDetection system to catch native healing & regen.

For HealDetection system:
Mandatory - Bribe's GUI Unit Indexer
Optional & - Bribe's Damage Engine 5.A.0 and Bribe's GUI Unit Event

Import

Make sure to have option "File > Preference > Automatically create unknown variables while pasting trigger data" checked.
If you do not have the requirements in your map yet, copy and paste the "Requirements" folder.
Copy the folder Heal Engine and paste it in your map.

Configuration

The following constant can be configured in the globals section of the HealEngine library.

USE_HEAL_DETECTION - Default: true - Enable/disable HealDetection triggers. If set to true, make sure to have Unit Indexer requirement

HEAL_THRESHOLD - Default: 5.00 - Minimum value for a hp change to be considered a heal worth triggering an event.
HEAL_CHECK_INTERVAL - Default: 0.05 - How often is healing checked.

REGEN_STRENGTH_VALUE - Default: 0.05 - How much HP regen 1 point of strength give to a hero. Should reflect gameplay constant.
REGEN_THRESHOLD - Default: 5.00 - Minimum value to be considered a regen worth triggering an event.
REGEN_EVENT_INTERVAL - Default: 1.00 - How often HP regen is checked.

IS_NATIVE_HEALING_SELF - Default: false - Should native healing be considered as self healing ?
IS_NATIVE_REGEN_SELF - Default: true - Should native HP regen be considered as self healing ?

The following booleans are there to get more control on which units the HealDetection system should keep track of.
IGNORE_LOCUST - Default: true - Ignore units with locust ability (ex: dummies)
IGNORE_STRUCTURE - Default: false - Ignore structures (unit classification must be consistent)
IGNORE_MECHANICAL - Default: false - Ignore mechanical (unit classification must be consistent)
IGNORE_SUMMONED - Default: false - Ignore summoned units (unit classification must be consistent)
IGNORE_HERO - Default: false - Ignore heroes
IGNORE_NON_HERO - Default: false - Ignore every units except heroes

Parameters for the events:
ZEROHEAL_THRESHOLD - Default: 1.00 - Healing with an amount inferior to this value will trigger a ZeroHealEvent.
OVERHEAL_THRESHOLD - Default: 1.00 - Healing with an overheal superior to this value will trigger an OverHealEvent
MAX_PREHEAL_EVENTS - Default: 2.00 - How many PreHealEvent will fire before applying the heal, to make sure modifiers are applied in a correct order. By default, 2 steps (1.00 for percentage multipliers & 2.00 for flat modifiers).

ALLOW_NEGATIVE_HEALING - Default: false - If true, negative healing after PreHealEvents will do damage. Else, heal won't go under 0.
NEGATIVE_HEAL_THRESHOLD - Default: -1.00 - Used only if ALLOW_NEGATIVE_HEALING = true. Threshold for firing NegativeHealEvent.

Complete engine code:
vJASS:
/*
    vJass HealEngine 1.0.3
    by Marchombre
    Requirement if using HealDetection: GUI Unit Indexer 1.4.0.0 (by Bribe)
*/
/*
    ==================
    ==== Jass API ====
    ==================
    // This struct is used for native healing and regen detection
    struct HealDetection
        // Add unit (by its index) to the detection system
        static method AddUnit takes integer unitId returns nothing
        // Remove unit (by its index) from the detection system
        static method SystemRemoveUnit takes integer unitId returns nothing
        // Used for Optionnal trigger
        // Force a check on target unit. Used before applying damage.
        static method Adjust takes unit target returns nothing
        // Used for Optionnal trigger
        // Adjust values after taking damage, or some events might not trigger due to HP diff
        static method AdjustAfterDamage takes unit target, real dmg returns nothing
        // Adjust values after receiving a triggered heal, to avoid triggering the healing event a second time by detection
        static method AdjustAfterHeal takes unit u, real heal returns nothing
    // This struct is used for triggered healing and complete Heal Events
    struct Heal
        // User Entry Point. Takes heal source, target and amount
        // If an other heal is currently running, postpon new one to avoid conflicts
        static method HealUnit takes unit source, unit target, real amount
*/
library HealEngine
    globals
        /*
            Variables used for detecting war3 native heals and regeneration
        */
        private constant boolean USE_HEAL_DETECTION     = true      // Enable/disable HealDetection triggers. If set to true, make sure to have Unit Indexer requirement
        private constant real HEAL_THRESHOLD            = 5.00
        private constant real HEAL_CHECK_INTERVAL       = 0.05
        private constant real REGEN_STRENGTH_VALUE      = 0.05
        private constant real REGEN_THRESHOLD           = 5.00
        private constant real REGEN_EVENT_INTERVAL      = 1.00
        private constant boolean IS_NATIVE_HEALING_SELF = false     // True: native healing will be considered selfhealing. False: considered from an unknown source
        private constant boolean IS_NATIVE_REGEN_SELF   = true      // True: regen will be considered selfhealing. False: considered from an unknown source
        // Booleans to control control what type of units should be ignored by the HealDetection system.
        private constant boolean IGNORE_LOCUST          = true      // Units with locust ability (ex: dummies)
        private constant boolean IGNORE_STRUCTURE       = false     // Structures (unit classification must be consistent)
        private constant boolean IGNORE_MECHANICAL      = false     // Mechanical (unit classification must be consistent)
        private constant boolean IGNORE_SUMMONED        = false     // Summoned units (unit classification must be consistent)
        private constant boolean IGNORE_HERO            = false     // Heroes
        private constant boolean IGNORE_NON_HERO        = false     // Every units except heroes
        /*
            Config for Events
        */
        private constant real ZEROHEAL_THRESHOLD        = 1.00      // Heal under this value will fire ZeroHealEvent
        private constant real OVERHEAL_THRESHOLD        = 1.00      // How much overheal is needed to fire OverHealEvent
        private constant real MAX_PREHEAL_EVENTS        = 4.00      // PreHealEvent will fire this many time. Useful if you want to make sure modifiers are applierd if the right order (% then flat for example).
       
        private constant boolean ALLOW_NEGATIVE_HEALING = false     // If true, negative healing after PreHealEvents will do damage. Else, heal won't go under 0.
        private constant real NEGATIVE_HEAL_THRESHOLD   = -1.00     // Used if ALLOW_NEGATIVE_HEALING = true. Threshold for firing NegativeHealEvent.
    endglobals
    // GUI vars for those who really don't want to call custom script
    // These are set before running a trigger calling this engine
    /*
        unit                udg_NextHealSource
        unit                udg_NextHealTarget
        real                udg_NextHealAmount
    */
    // GUI Vars to catch events
    /*
        // Variables you can access to get infos on current heal
        unit                udg_HealSource
        unit                udg_HealTarget
        real                udg_HealAmount              // Current heal amount
        real                udg_HealBaseAmount          // Heal amount before any event was fired.
        real                udg_HealPrvAmount           // Heal amount at the start of this event
        real                udg_EffectiveHealAmount     // How much HPs was actually healed
        real                udg_OverHealAmount          // How much heal is left after getting the unit to full HP.
        boolean             udg_IsSelfHeal
        // Events variables
        real                udg_PreHealEvent            // Can be 0.00 or X.00, with 1 <= X <= MAX_PREHEAL_EVENTS 
        real                udg_AfterHealEvent          // Can be 0.00 or 1.00 or 0.50
        real                udg_ZeroHealEvent           // Can be 0.00 or 1.00
        real                udg_OverHealEvent           // Can be 0.00 or 1.00
        real                udg_NegativeHealEvent       // Can be 0.00 or 1.00
    */
   
    struct HealDetection
        private static trigger checkLoopTrigger
        private static trigger addUnitTrigger
        private static trigger removeUnitTrigger
        private static boolean array isInSystem
        private static integer array indices
        private static integer array indexRef
       
        private static real array lastLife
        private static real array regen
        private static real array regenBuildUp
        private static real array regenTimeLeft
       
        private static integer count        = 0
        private static integer unitIndex    = 0
        private static timer healTimer
       
       
        static method AddUnit takes integer unitId returns nothing
            // if Unit is already in system or unit match one ignore config, then do nothing
            if isInSystem[unitId] /*
                */ or (GetUnitAbilityLevel(udg_UDexUnits[unitId], 'Aloc') > 0 and IGNORE_LOCUST)/*
                */ or (IsUnitType(udg_UDexUnits[unitId], UNIT_TYPE_MECHANICAL) and IGNORE_MECHANICAL) /*
                */ or (IsUnitType(udg_UDexUnits[unitId], UNIT_TYPE_STRUCTURE) and IGNORE_STRUCTURE) /*
                */ or (IsUnitType(udg_UDexUnits[unitId], UNIT_TYPE_SUMMONED) and IGNORE_SUMMONED) /*
                */ or (IsUnitType(udg_UDexUnits[unitId], UNIT_TYPE_HERO) and IGNORE_HERO) /*
                */ or (not IsUnitType(udg_UDexUnits[unitId], UNIT_TYPE_HERO) and IGNORE_NON_HERO) /*
                */ then
                return
            endif
            set isInSystem[unitId]      = true
            set indices[count]          = unitId
            set indexRef[unitId]        = count
            set lastLife[unitId]        = GetWidgetLife(udg_UDexUnits[unitId])
            set regenTimeLeft[unitId]   = REGEN_EVENT_INTERVAL
           
            set count = count +1
            // If first unit added, turn on checkloop
            if count == 1 then
                call EnableTrigger(checkLoopTrigger)
            endif
        endmethod
        static method SystemRemoveUnit takes integer unitId returns nothing
            // If unit not in system, can't remove them, so just returns
            if not isInSystem[unitId] then
                return
            endif
            set isInSystem[unitId]          = false
            set count = count -1
            set indices[indexRef[unitId]]   = indices[count]
            set indexRef[indices[count]]    = indexRef[unitId]
            // If last unit in system, turn off checkloop
            if count == 0 then
                call DisableTrigger(checkLoopTrigger)
            endif
        endmethod
        private static method CheckLoop takes nothing returns nothing
            local integer max = count -1
            local integer unitIndex = 0
            local unit target
            local real unitHP
            local real diff
            local real heal
           
            loop
                exitwhen unitIndex > max
               
                set unitIndex = indices[unitIndex]
                set target              = udg_UDexUnits[unitIndex]
                set unitHP              = GetWidgetLife(target)
                set diff                = unitHP - lastLife[unitIndex]
                set lastLife[unitIndex] = unitHP
                set heal                = diff - RMaxBJ(0.00, regen[unitIndex])
                if heal > HEAL_THRESHOLD then
                    // Set Up variables
                    set udg_HealAmount = heal
                    set udg_HealTarget = target
                    if IS_NATIVE_HEALING_SELF then
                        set udg_HealSource = target
                    else
                        set udg_HealSource = null   // Can't know the source of a native healing sadly
                    endif
                   
                    set udg_IsSelfHeal = IS_NATIVE_HEALING_SELF
                    // Fire AfterHealEvent
                    set udg_AfterHealEvent  = 0.00
                    set udg_AfterHealEvent  = 1.00
                    set udg_AfterHealEvent  = 0.00
                else
                    // Check Regen
                    set regen[unitIndex] = (regen[unitIndex] + diff) * 0.5
                    set regenBuildUp[unitIndex]     = regenBuildUp[unitIndex] + diff
                    set regenTimeLeft[unitIndex]    = regenTimeLeft[unitIndex] - HEAL_CHECK_INTERVAL
                    if regenTimeLeft[unitIndex] <= 0.00 then
                        // reset clock
                        set regenTimeLeft[unitIndex] = REGEN_EVENT_INTERVAL
                        set heal = regenBuildUp[unitIndex]
                        set regenBuildUp[unitIndex] = 0.00
                       
                        set diff = heal
                        // Ignore regen from hero stats
                        if IsUnitType(target, UNIT_TYPE_HERO) then
                            set diff = diff - REGEN_STRENGTH_VALUE * I2R(GetHeroStr(target, true))
                        endif
                        // Fire Regen Event (AfterHealEvent = 0.5)
                        if diff > REGEN_THRESHOLD then
                             // Set Up variables
                            set udg_HealAmount = heal
                            set udg_HealTarget = target
                            if IS_NATIVE_REGEN_SELF then
                                set udg_HealSource = target
                            else
                                set udg_HealSource = null   // Can't know the source of a native healing sadly
                            endif
                           
                            set udg_IsSelfHeal = IS_NATIVE_REGEN_SELF
                            // Fire AfterHealEvent
                            set udg_AfterHealEvent  = 0.00
                            set udg_AfterHealEvent  = 0.50
                            set udg_AfterHealEvent  = 0.00
                        endif
                    endif
                endif
                set unitIndex = indexRef[unitIndex]
                set unitIndex = unitIndex +1
            endloop
        endmethod
        // Check unit new life and fire AfterHealEvent if needed
        static method Adjust takes unit target returns nothing
            local integer index     = GetUnitUserData(target)
            local real u_hp         = GetWidgetLife(target)
           
            local real heal = u_hp - lastLife[index] - regen[index] * TimerGetElapsed(healTimer) / HEAL_CHECK_INTERVAL
            if heal > HEAL_THRESHOLD then
                set lastLife[index] = lastLife[index] + heal
               
                // Set Up variables
                set udg_HealAmount = heal
                set udg_HealTarget = target
                if IS_NATIVE_HEALING_SELF then
                    set udg_HealSource = target
                else
                    set udg_HealSource = null   // Can't know the source of a native healing sadly
                endif
               
                set udg_IsSelfHeal = IS_NATIVE_HEALING_SELF
                // Fire AfterHealEvent
                set udg_AfterHealEvent  = 0.00
                set udg_AfterHealEvent  = 1.00
                set udg_AfterHealEvent  = 0.00
            endif
        endmethod
        // Adjust values when unit takes damage, or heal might not be detected
        static method AdjustAfterDamage takes unit target, real dmg returns nothing
            local integer index     = GetUnitUserData(target)
            local real u_hp         = GetWidgetLife(target)
            if dmg > 0 then
                set lastLife[index] = u_hp - regen[index] * TimerGetElapsed(healTimer) / HEAL_CHECK_INTERVAL
            else
                call Adjust(target)
            endif
        endmethod
        // Adjust after heal, avoiding a double event for one heal
        static method AdjustAfterHeal takes unit u, real heal returns nothing
            local integer index = GetUnitUserData(u)
            set lastLife[index] = lastLife[index] + heal
        endmethod
        private static method ActionAddUnit takes nothing returns nothing
            call AddUnit(udg_UDex)
        endmethod
        private static method ActionRemoveUnit takes nothing returns nothing
            call SystemRemoveUnit(udg_UDex)
        endmethod
        private static method onInit takes nothing returns nothing
            if USE_HEAL_DETECTION then
                set healTimer           = CreateTimer()
                set checkLoopTrigger    = CreateTrigger()
                set addUnitTrigger      = CreateTrigger()
                set removeUnitTrigger   = CreateTrigger()
                call StartTimerBJ(healTimer, true, HEAL_CHECK_INTERVAL)
               
                call TriggerRegisterTimerExpireEvent(checkLoopTrigger, healTimer)
                call TriggerAddAction(checkLoopTrigger, function HealDetection.CheckLoop)
                call TriggerRegisterVariableEvent(addUnitTrigger, "udg_UnitIndexEvent", EQUAL, 1.00)
                call TriggerRegisterVariableEvent(addUnitTrigger, "udg_DeathEvent", EQUAL, 2.00)
                call TriggerAddAction(addUnitTrigger, function HealDetection.ActionAddUnit)
                call TriggerRegisterVariableEvent(removeUnitTrigger, "udg_UnitIndexEvent", EQUAL, 2.00)
                call TriggerRegisterVariableEvent(removeUnitTrigger, "udg_DeathEvent", EQUAL, 0.50)
                call TriggerRegisterVariableEvent(removeUnitTrigger, "udg_DeathEvent", EQUAL, 1.00)
                call TriggerRegisterVariableEvent(removeUnitTrigger, "udg_DeathEvent", EQUAL, 3.00)
                call TriggerAddAction(removeUnitTrigger, function HealDetection.ActionRemoveUnit)
            endif
        endmethod
    endstruct
    struct Heal
        /*
            Variables used in healing
        */
        private unit healSource
        private unit healTarget
        private real healBaseAmt
        private real healPrevAmt
        private real healAmount
       
        private real effectiveHealAmt
        private real overHealAmount
        private boolean isSelfHeal
       
        /*
            Variables used for listing
        */
        private Heal next
        private static Heal first
        private static Heal last

        private static method create takes unit source, unit target, real amount returns Heal
            local Heal this = Heal.allocate()
            set this.healSource         = source
            set this.healTarget         = target
            set this.healAmount         = amount
            set this.healBaseAmt        = amount
            set this.healPrevAmt        = amount
            set this.isSelfHeal         = (source == target)
            set this.overHealAmount     = 0
            set this.effectiveHealAmt   = 0
            set this.next = 0
           
            return this
        endmethod
        private method destroy takes nothing returns nothing
            call this.deallocate()
        endmethod
        private static method onInit takes nothing returns nothing
            set first = 0
            set last = 0
        endmethod
        /*
            Entry point for user.
        */
        static method HealUnit takes unit source, unit target, real amount returns nothing
            local Heal h = Heal.create(source, target, amount)
            if first == 0 then
                set first = h
                set last = h
                call Heal.Run()
            else
                set last.next = h
                set last = last.next
            endif
        endmethod
        // Put this instance members into GUI variables
        private method setEventVariables takes nothing returns nothing
            set udg_HealSource              = this.healSource
            set udg_HealTarget              = this.healTarget
           
            set udg_HealAmount              = this.healAmount
            set udg_HealBaseAmount          = this.healBaseAmt
            set udg_HealPrvAmount           = this.healPrevAmt
           
            set udg_EffectiveHealAmount     = this.effectiveHealAmt
            set udg_OverHealAmount          = this.overHealAmount
            set udg_IsSelfHeal              = this.isSelfHeal
        endmethod
        // Updates this instance members with GUI vars that might have been updated
        private method getEventVariables takes nothing returns nothing
            set this.healSource     = udg_HealSource
            set this.healTarget     = udg_HealTarget
            set this.healAmount     = udg_HealAmount
            set this.healPrevAmt    = udg_HealAmount    // Stores this step result for reference in next step if needed.
            set this.isSelfHeal     = (this.healSource == this.healTarget)
        endmethod
        // Reset Variables to a neutral state
        private static method resetEventVariables takes nothing returns nothing
            set udg_HealSource              = null
            set udg_HealTarget              = null
            set udg_HealBaseAmount          = 0.00
            set udg_HealPrvAmount           = 0.00
            set udg_HealAmount              = 0.00
            set udg_EffectiveHealAmount     = 0.00
            set udg_OverHealAmount          = 0.00
            set udg_IsSelfHeal              = false
        endmethod
        /*
            Function where the healing and Events are done.
            Healing are dealt one at the time. Healing created during Events will be postponed.
        */
        private static method Run takes nothing returns nothing
            local Heal h
            local integer u_custom
            local real u_current_hp
            local real u_max_hp
            local real u_missing_hp
            local real new_missing_hp
            local real i = 1.00
            loop
                exitwhen first == null
               
                set h = first
               
                // Using not == instead of !=; the idea is to eliminate floating point bugs when two numbers are very close to 0,
                // because JASS uses a less-strict comparison for checking if a number is equal than when it is unequal.
                if not (h.healAmount == 0.00) then
                    loop
                        exitwhen i > MAX_PREHEAL_EVENTS
                        // Set GUI vars
                        call h.setEventVariables()
                        // Fire PreHealEvent
                        set udg_PreHealEvent    = 0.00
                        set udg_PreHealEvent    = i
                        set udg_PreHealEvent    = 0.00
                        // Update fields in case event was caught and variables changed.
                        call h.getEventVariables()
                        set i = i +1.00
                    endloop
                    if not ALLOW_NEGATIVE_HEALING and h.healAmount < 0.00 then
                        set h.healAmount = 0.00
                    endif                       
                   
                    // Get target infos
                    set u_custom            = GetUnitUserData(h.healTarget)
                    set u_max_hp            = GetUnitState(h.healTarget, UNIT_STATE_MAX_LIFE)
                    set u_current_hp        = GetWidgetLife(h.healTarget)
                    set u_missing_hp        = u_max_hp - u_current_hp
                   
                    if h.healAmount > 0 then
                        // Healing itself.
                        call SetWidgetLife(h.healTarget, u_current_hp + h.healAmount)
                       
                        // Getting new missing hp
                        set new_missing_hp = GetUnitState(h.healTarget, UNIT_STATE_MAX_LIFE) - GetWidgetLife(h.healTarget)
                        // Computing effective healing and possible overhealing
                        set h.effectiveHealAmt = new_missing_hp - u_missing_hp
                        set h.overHealAmount = h.healAmount - h.effectiveHealAmt
                    else
                        // Use Damage function to be compatible with damage engines
                        call UnitDamageTargetBJ(h.healSource, h.healTarget, h.healAmount, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC)
                        set h.effectiveHealAmt = h.healAmount
                    endif
                   
                    // Fire OverHealEvent
                    if h.overHealAmount > OVERHEAL_THRESHOLD then
                        // Set GUI vars
                        call h.setEventVariables()
                        set udg_OverHealEvent   = 0.00
                        set udg_OverHealEvent   = 1.00
                        set udg_OverHealEvent   = 0.00
                    endif
                    // Fire AfterHealEvent if enough healing was done, else ZeroHealEvent
                    if h.effectiveHealAmt > ZEROHEAL_THRESHOLD or (h.overHealAmount > 0.00) then
                        // Update HealDetection system, avoiding the event triggering twice for this heal
                        call HealDetection.AdjustAfterHeal(h.healTarget, h.healAmount)
                        // Set GUI vars
                        call h.setEventVariables()
                        set udg_AfterHealEvent  = 0.00
                        set udg_AfterHealEvent  = 1.00
                        set udg_AfterHealEvent  = 0.00
                    elseif h.effectiveHealAmt < NEGATIVE_HEAL_THRESHOLD then
                        // Set GUI vars
                        call h.setEventVariables()
                        set udg_NegativeHealEvent   = 0.00
                        set udg_NegativeHealEvent   = 1.00
                        set udg_NegativeHealEvent   = 0.00
                    else
                        // Set GUI vars
                        call h.setEventVariables()
                        set udg_ZeroHealEvent   = 0.00
                        set udg_ZeroHealEvent   = 1.00
                        set udg_ZeroHealEvent   = 0.00
                    endif
                endif
                // Resets variables
                call resetEventVariables()
                // Set the next heal that will be run
                set first = h.next
               
                // Free resources
                call h.destroy()
            endloop
        endmethod
    endstruct
endlibrary


Version 1.0.3 (2025-02-10)
New/Changed config options:
USE_HEAL_DETECTION - Default: true - Enable/disable HealDetection triggers. If set to true, make sure to have Unit Indexer requirement

IS_NATIVE_HEALING_SELF - Default: false - Should native healing be considered as self healing ?
IS_NATIVE_REGEN_SELF - Default: true - Should native HP regen be considered as self healing ?

ALLOW_NEGATIVE_HEALING - Default: false - If true, negative healing after PreHealEvents will do damage. Else, heal won't go under 0.
NEGATIVE_HEAL_THRESHOLD - Default: -1.00 - Used only if ALLOW_NEGATIVE_HEALING = true. Threshold for firing NegativeHealEvent.

New Event:
Game - NegativeHealEvent Becomes Equal to 1.00 - Triggered heal only, needs ALLOW_NEGATIVE_HEALING set to true in config - This event runs when heal become negative during PreHealEvent, thus dealing damage.

New/Changed variables:
HealBaseAmount - Initial healing amount, before any modification. This variable should be treated as readonly.
HealPrvAmount - Healing amount at the start of this step. This variable should be treated as readonly.

EffectiveHealAmount - How much healing was done. Access this in AfterHealEvent and/or in OverHealEvent.

Version 1.0.2 (2025-01-23)
I thought it would be nice to get a bit more control on the HealDetection system.
Added a bunch of constant that will allow to ignore some types of units:
IGNORE_LOCUST - Default: true - Ignore units with locust ability (ex: dummies)
IGNORE_STRUCTURE - Default: false - Ignore structures (unit classification must be consistent)
IGNORE_MECHANICAL - Default: false - Ignore mechanical (unit classification must be consistent)
IGNORE_SUMMONED - Default: false - Ignore summoned units (unit classification must be consistent)
IGNORE_HERO - Default: false - Ignore heroes
IGNORE_NON_HERO - Default: false - Ignore every units except heroes

Version 1.0.1 (2025-01-19)
  • Changed condition for ZeroHealEvent & AfterHealEvent to use struct members and not globals variables.
  • Added the possibility to fire more than one PreHealEvent with new parameter MAX_PREHEAL_EVENTS if you need more steps to separate different modifiers.

Version 1.0.0 (2025-01-15)
First upload.


Keywords:
heal, healing, heal event, heal engine, EVENT_PLAYER_UNIT_HEALED
Contents

HealEngine 1.0.3 (Map)

Reviews
Antares
Healing through code is so much better than these dirty hacks required to detect native heals, so that is a welcome addition to the Heal Detection system. Since it is possible to use the system without using any native heals, you should make an option...
Level 9
Joined
Nov 18, 2014
Messages
81
I thought it would be nice to get a bit more control on the HealDetection system :grin:


Version 1.0.2

Added a bunch of constant that will allow to ignore some types of units:
IGNORE_LOCUST - Default: true - Ignore units with locust ability (ex: dummies)
IGNORE_STRUCTURE - Default: false - Ignore structures (unit classification must be consistent)
IGNORE_MECHANICAL - Default: false - Ignore mechanical (unit classification must be consistent)
IGNORE_SUMMONED - Default: false - Ignore summoned units (unit classification must be consistent)
IGNORE_HERO - Default: false - Ignore heroes
IGNORE_NON_HERO - Default: false - Ignore every units except heroes
 
Level 9
Joined
Nov 18, 2014
Messages
81
I'm curious to see how you develop it, it will have many great uses if perfected.
Feel free to make suggestions :cute:

I have currently added everything I said "Damn, I wish I could do that" for.
I'm pretty sure there are others uses I didn't even think of yet, and I'll do my best to add them if someone point them out :grin:
Otherwise, I'll wait until a new need rise in the map I'm currently maintaining :xxd:
 
Level 9
Joined
Nov 18, 2014
Messages
81
How does the MaxPreHeal event works in detail?
MAX_PREHEAL_EVENTS is a config constant, not an event in itself.

It dictates how many PreHealEvents are fired before applying the heal.

For example, with MAX_PREHEAL_EVENTS = 1, you will have only PreHealEvent = 1.00
With MAX_PREHEAL_EVENTS = 5, you'll get PreHealEvent =1.00, PreHealEvent = 2.00, PreHealEvent = 3.00, PreHealEvent = 4.00 & PreHealEvent =5.00

I think there can be room for spells that 'interrupt' any healing and cancel their effect completely,

It is possible to completely negate a heal.
For example, I'm currently using this system with 4 PreHealEvents:
  • PreHealEvent = 1.00 -- flat heal increase
  • PreHealEvent = 2.00 -- % heal increase
  • PreHealEvent = 3.00 -- % heal reduction
  • PreHealEvent = 4.00 -- flat heal reduction

Having healing reduction after any heal increase will allow you to give a 100% heal reduction and negate the heal entirely.

or spells that only selectively negate certain heals
This part is quite limited for now. You can only do a threshold (like every heal < 100 will be negated), or check if the unit is self healing with IsSelfHeal.



Feel free to correct me I misunderstood you, and suggest improvements :cute:
 
Last edited:
Healing through code is so much better than these dirty hacks required to detect native heals, so that is a welcome addition to the Heal Detection system. Since it is possible to use the system without using any native heals, you should make an option to disable that feature (and remove the unit indexer requirement if possible).

I think overheal is poorly handled. Instead of having a separate event, I think there should be just the heal event which sets the variables Raw Heal, Effective Heal, and Overheal. In any case, even if you keep the separate overheal event, an effective heal variable should exist.

I didn't find any problems with the code and all of the features seem to be working without problems.

Approved
 
Level 9
Joined
Nov 18, 2014
Messages
81
Thanks for the approval :grin:

Healing through code is so much better than these dirty hacks required to detect native heals, so that is a welcome addition to the Heal Detection system. Since it is possible to use the system without using any native heals, you should make an option to disable that feature (and remove the unit indexer requirement if possible).
That's a good idea. I guess I could just put a condition in the onInit of HealDetection struct and avoid creating the triggers/timers for native heals.
This should be enough, as long I clearly state that unit indexer is required if config allows native heals detection.

I think overheal is poorly handled. Instead of having a separate event, I think there should be just the heal event which sets the variables Raw Heal, Effective Heal, and Overheal. In any case, even if you keep the separate overheal event, an effective heal variable should exist.
OverHealEvent was, in my opinion, like ZeroHealEvent. An event for convenience and trigger clarity, more that mandatory thing to have. Way easier - in my mind at least - to code and read "Check OverHealEvent = 1.00" than "Check if AfterHealEvent = 1.00 and OverHealAmount > 0.00", especially when using GUI.

However, I agree that EffectiveHeal is missing and will add that in next version :thumbs_up:

I didn't find any problems with the code and all of the features seem to be working without problems.
Well, as a matter of fact, I did find one problem myself:xxd:
If user catch PreHealEvent and apply heal reduction superior to the heal amount, the system would damage the unit and would still trigger events.

I'll probably add a config option to decide if negative healing should damage the target or act as a 0 heal. And will use a proper damaging function so damage engines can catch it.
Might add a NegativeHealEvent too ? :confused:2 Not sure of that yet.
 
Level 9
Joined
Nov 18, 2014
Messages
81
Version 1.0.3


New/Changed config options:
USE_HEAL_DETECTION - Default: true - Enable/disable HealDetection triggers. If set to true, make sure to have Unit Indexer requirement

IS_NATIVE_HEALING_SELF - Default: false - Should native healing be considered as self healing ?
IS_NATIVE_REGEN_SELF - Default: true - Should native HP regen be considered as self healing ?

ALLOW_NEGATIVE_HEALING - Default: false - If true, negative healing after PreHealEvents will do damage. Else, heal won't go under 0.
NEGATIVE_HEAL_THRESHOLD - Default: -1.00 - Used only if ALLOW_NEGATIVE_HEALING = true. Threshold for firing NegativeHealEvent.

New Event:
Game - NegativeHealEvent Becomes Equal to 1.00 - Triggered heal only, needs ALLOW_NEGATIVE_HEALING set to true in config - This event runs when heal become negative during PreHealEvent, thus dealing damage.

New/Changed variables:
HealBaseAmount - Initial healing amount, before any modification. This variable should be treated as readonly.
HealPrvAmount - Healing amount at the start of this step. This variable should be treated as readonly.

EffectiveHealAmount - How much healing was done. Access this in AfterHealEvent and/or in OverHealEvent.

 
Top