• Check out the results of the Techtree Contest #19!
  • 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.
  • Create a void inspired texture for Warcraft 3 and enter Hive's 34th Texturing Contest: Void! Click here to enter!
  • The Hive's 22nd Icon Contest: Creep Abilities is now concluded, time to vote for your favourite set of icons! Click here to vote!

Heal Engine 1.0.4

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.4 (2025-06-19)
  • Fix a bug Effective heal computing. Effective heal was giving negative value due to switching variable in formula.
  • Overheal event is now properly working. Due to previous bug and overheal amount being calculated from effective healing, overheal was firing way to often.

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)

HealEngine 1.0.4 (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...
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
 
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:
 
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
 
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.
 
Would you add LUA version as well? (might come in handy for people that make the map on LUA - even for GUI users)
I haven't looked into lua yet, so not planned right now.
I might try in the future, but will not be my priority. And I'll probably wait to have a version stable enough so I don't have to duplicate every single change :xxd:
 
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.

 
Hello @Marchombre , excellent system!

I ran into some things, and noticed that, for example: if a Hero has the Attribute Bonus ability, when learned, it will show the HP gained from strength as a heal.

Is there a way to exclude that?
 
Hello @Marchombre , excellent system!

I ran into some things, and noticed that, for example: if a Hero has the Attribute Bonus ability, when learned, it will show the HP gained from strength as a heal.

Is there a way to exclude that?

Gaining Max HPs is indeed detected as heal. This is because when gaining Max HPs, you are also gaining current HPs, which is what is tracked by the detection part of the system.


I'm not sure how to fix that, and I'm currently working on other things, so it might take a while for me to update and fix this issue.

Best way to use this system is still to go full triggered healing and thus not needing the detection that's causing this issue (by setting USE_HEAL_DETECTION = false) :grin:
 
Gaining Max HPs is indeed detected as heal. This is because when gaining Max HPs, you are also gaining current HPs, which is what is tracked by the detection part of the system.


I'm not sure how to fix that, and I'm currently working on other things, so it might take a while for me to update and fix this issue.

Best way to use this system is still to go full triggered healing and thus not needing the detection that's causing this issue (by setting USE_HEAL_DETECTION = false) :grin:
Alright :D

Thank you for the feedback and for the tip.
 
What about Heal and Mana Restoration Engine 2.0.0 ? Just duplicate the Heal Engine codes and change variables "Heal" with "Mana", Life of Unit with Mana of Unit and Strength with Intelligence. With this way, we can detect native Mana Restoration and Mana Regeneration addition of Heal and Health Restoration. @Marchombre @Bribe.
 
What about Heal and Mana Restoration Engine 2.0.0 ? Just duplicate the Heal Engine codes and change variables "Heal" with "Mana", Life of Unit with Mana of Unit and Strength with Intelligence. With this way, we can detect native Mana Restoration and Mana Regeneration addition of Heal and Health Restoration. @Marchombre @Bribe.
To be honest, I thought about doing mana engine, but didn't need it as much and things are never as easy as duplicating and changing 1 variable :grin:
I might do it later this summer though.
 
I tried to find tranquility. I succeeded only with the help of "AfterHealingEvent".

Further installation of "HealAmount" works, judging by the text report, but the heal does not increase. Alas, this is all my powers.
If you're using the native tranquility, that is the expected behavior.
Native heals can only be detected after their effects have been applied, and thus can only trigger the AfterHealingEvent.

If you wish to have a tranquility working with healing boost on PreHealEvent, you'll have to trigger it.
 
If you're using the native tranquility, that is the expected behavior.
Native heals can only be detected after their effects have been applied, and thus can only trigger the AfterHealingEvent.

If you wish to have a tranquility working with healing boost on PreHealEvent, you'll have to trigger it.
I called it by setting it to one, but the text report did not appear, from which I concluded that tracking was not happening =(
 
It just doesn't work...
I simplified the trigger to the maximum to make sure it would work, but no.
So, like I was saying here:
If you're using the native tranquility, that is the expected behavior.
Native heals can only be detected after their effects have been applied, and thus can only trigger the AfterHealingEvent.

If you wish to have a tranquility working with healing boost on PreHealEvent, you'll have to trigger it.
Native healing (ie pre existing abilities) can only use the AfterHealingEvent.


If you want to use other events, you need to trigger the healing.
You should look the "Healing ability" folder that gives 2 examples of triggered ability that can heal and use PreHealingEvent and use that to implement a tranquility spell.
 
Do I need the GUI Unit Indexer if I only want the vJass engine? I don't use GUI

You need the GUI Unit Indexer if and only if you need to detect native healing / hp regen (config: USE_HEAL_DETECTION = true)

If you want to use this engine only to make triggered healing and event from triggered healing only, then you don't need it.
 
To be honest, I thought about doing mana engine, but didn't need it as much and things are never as easy as duplicating and changing 1 variable :grin:
I might do it later this summer though.

I've finished making the Mana Engine which I mentioned earlier. I can share it with you if you'd like. @Marchombre

JASS:
/*
    vJass ManaEngine 1.0.0
    by Kotol

    Requirement if using ManaRestorationDetection: GUI Unit Indexer 1.4.0.0 (by Bribe)
*/

/*
    ==================
    ==== Jass API ====
    ==================

    // This struct is used for native mana restoration and mana regeneration detection
    struct ManaDetection
        // 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 Optional trigger
        // Force a check on target unit. Used before applying damage.
        static method Adjust takes unit target returns nothing

        // Used for Optional trigger
        // Adjust values after taking damage, or some events might not trigger due to MP diff
        static method AdjustAfterDamage takes unit target, real dmg returns nothing

        // Adjust values after receiving a triggered mana, to avoid triggering the mana restoration event a second time by detection
        static method AdjustAfterMana takes unit u, real mana returns nothing

    // This struct is used for triggered mana restoration and complete Mana Events
    struct Mana
        // User Entry Point. Takes mana source, target and amount
        // If an other mana is currently running, postpon new one to avoid conflicts
        static method ManaUnit takes unit source, unit target, real amount

*/
library ManaEngine
    globals
        /*
            Variables used for detecting war3 native mana restoration and mana regeneration
        */
        private constant boolean USE_MANA_DETECTION         = true      // Enable/disable ManaDetection triggers. If set to true, make sure to have Unit Indexer requirement

        private constant real MANA_THRESHOLD                = 5.00
        private constant real MANA_CHECK_INTERVAL           = 0.05

        private constant real MANA_REGEN_INTELLIGENCE_VALUE = 0.05
        private constant real MANA_REGEN_THRESHOLD          = 5.00
        private constant real MANA_REGEN_EVENT_INTERVAL     = 1.00

        private constant boolean IS_NATIVE_MANA_SELF        = false     // True: native mana restoration will be considered self mana restoration. False: considered from an unknown source
        private constant boolean IS_NATIVE_MANA_REGEN_SELF  = true      // True: mana regen will be considered self mana restoration. False: considered from an unknown source

        // Booleans to control control what type of units should be ignored by the ManaDetection 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 ZEROMANA_THRESHOLD            = 1.00      // Mana restore under this value will fire ZeroManaEvent
        private constant real OVERMANA_THRESHOLD            = 1.00      // How much overmana is needed to fire OverManaEvent
        private constant real MAX_PREMANA_EVENTS            = 4.00      // PreManaEvent will fire this many time. Useful if you want to make sure modifiers are applied if the right order (% then flat for example).
      
        private constant boolean ALLOW_NEGATIVE_MANA        = false     // If true, negative mana restoration after PreManaEvents will do damage. Else, mana restore won't go under 0.
        private constant real NEGATIVE_MANA_THRESHOLD       = -1.00     // Used if ALLOW_NEGATIVE_MANA = true. Threshold for firing NegativeManaEvent.
    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_NextManaSource
        unit                udg_NextManaTarget
        real                udg_NextManaAmount
    */

    // GUI Vars to catch events
    /*

        // Variables you can access to get infos on current mana
        unit                udg_ManaSource
        unit                udg_ManaTarget

        real                udg_ManaAmount              // Current mana amount.
        real                udg_ManaBaseAmount          // Mana amount before any event was fired.
        real                udg_ManaPrvAmount           // Mana amount at the start of this event.

        real                udg_EffectiveManaAmount     // How much MPs was actually mana restored.
        real                udg_OverManaAmount          // How much mana is left after getting the unit to full MP.
        boolean             udg_IsSelfMana

        // Events variables
        real                udg_PreManaEvent            // Can be 0.00 or X.00, with 1 <= X <= MAX_PREMANA_EVENTS
        real                udg_AfterManaEvent          // Can be 0.00 or 1.00 or 0.50
        real                udg_ZeroManaEvent           // Can be 0.00 or 1.00
        real                udg_OverManaEvent           // Can be 0.00 or 1.00
        real                udg_NegativeManaEvent       // Can be 0.00 or 1.00

    */
  
    struct ManaDetection
        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 lastMana
        private static real array manaregen

        private static real array manaregenBuildUp
        private static real array manaregenTimeLeft
      
        private static integer count        = 0
        private static integer unitIndex    = 0

        private static timer manaTimer
      
      
        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 lastMana[unitId]        = GetUnitState(udg_UDexUnits[unitId], UNIT_STATE_MANA)
            set manaregenTimeLeft[unitId]   = MANA_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 unitMP
            local real diff
            local real mana
          
            loop
                exitwhen unitIndex > max
              
                set unitIndex = indices[unitIndex]

                set target              = udg_UDexUnits[unitIndex]
                set unitMP              = GetUnitState(target, UNIT_STATE_MANA)
                set diff                = unitMP - lastMana[unitIndex]
                set lastMana[unitIndex] = unitMP

                set mana                = diff - RMaxBJ(0.00, manaregen[unitIndex])

                if mana > MANA_THRESHOLD then
                    // Set Up variables
                    set udg_ManaAmount = mana
                    set udg_ManaTarget = target

                    if IS_NATIVE_MANA_SELF then
                        set udg_ManaSource = target
                    else
                        set udg_ManaSource = null   // Can't know the source of a native mana restoration sadly
                    endif
                  
                    set udg_IsSelfMana = IS_NATIVE_MANA_SELF

                    // Fire AfterManaEvent
                    set udg_AfterManaEvent  = 0.00
                    set udg_AfterManaEvent  = 1.00
                    set udg_AfterManaEvent  = 0.00
                else
                    // Check ManaRegen
                    set manaregen[unitIndex] = (manaregen[unitIndex] + diff) * 0.5

                    set manaregenBuildUp[unitIndex]     = manaregenBuildUp[unitIndex] + diff
                    set manaregenTimeLeft[unitIndex]    = manaregenTimeLeft[unitIndex] - MANA_CHECK_INTERVAL

                    if manaregenTimeLeft[unitIndex] <= 0.00 then
                        // reset clock
                        set manaregenTimeLeft[unitIndex] = MANA_REGEN_EVENT_INTERVAL

                        set mana = manaregenBuildUp[unitIndex]
                        set manaregenBuildUp[unitIndex] = 0.00
                      
                        set diff = mana

                        // Ignore manaregen from hero stats
                        if IsUnitType(target, UNIT_TYPE_HERO) then
                            set diff = diff - MANA_REGEN_INTELLIGENCE_VALUE * I2R(GetHeroInt(target, true))
                        endif

                        // Fire ManaRegen Event (AfterManaEvent = 0.5)
                        if diff > MANA_REGEN_THRESHOLD then
                             // Set Up variables
                            set udg_ManaAmount = mana
                            set udg_ManaTarget = target

                            if IS_NATIVE_MANA_REGEN_SELF then
                                set udg_ManaSource = target
                            else
                                set udg_ManaSource = null   // Can't know the source of a native mana restoration sadly
                            endif
                          
                            set udg_IsSelfMana = IS_NATIVE_MANA_REGEN_SELF

                            // Fire AfterManaEvent
                            set udg_AfterManaEvent  = 0.00
                            set udg_AfterManaEvent  = 0.50
                            set udg_AfterManaEvent  = 0.00
                        endif
                    endif
                endif

                set unitIndex = indexRef[unitIndex]
                set unitIndex = unitIndex +1
            endloop
        endmethod

        // Check unit new mana and fire AfterManaEvent if needed
        static method Adjust takes unit target returns nothing
            local integer index     = GetUnitUserData(target)
            local real u_mp         = GetUnitState(target, UNIT_STATE_MANA)
          
            local real mana = u_mp - lastMana[index] - manaregen[index] * TimerGetElapsed(manaTimer) / MANA_CHECK_INTERVAL

            if mana > MANA_THRESHOLD then
                set lastMana[index] = lastMana[index] + mana
              
                // Set Up variables
                set udg_ManaAmount = mana
                set udg_ManaTarget = target

                if IS_NATIVE_MANA_SELF then
                    set udg_ManaSource = target
                else
                    set udg_ManaSource = null   // Can't know the source of a native mana restoration sadly
                endif
              
                set udg_IsSelfMana = IS_NATIVE_MANA_SELF

                // Fire AfterManaEvent
                set udg_AfterManaEvent  = 0.00
                set udg_AfterManaEvent  = 1.00
                set udg_AfterManaEvent  = 0.00
            endif
        endmethod

        // Adjust values when unit takes damage, or mana might not be detected
        static method AdjustAfterDamage takes unit target, real dmg returns nothing
            local integer index     = GetUnitUserData(target)
            local real u_mp         = GetUnitState(target, UNIT_STATE_MANA)

            if dmg > 0 then
                set lastMana[index] = u_mp - manaregen[index] * TimerGetElapsed(manaTimer) / MANA_CHECK_INTERVAL
            else
                call Adjust(target)
            endif
        endmethod

        // Adjust after mana, avoiding a double event for one mana
        static method AdjustAfterMana takes unit u, real mana returns nothing
            local integer index = GetUnitUserData(u)
            set lastMana[index] = lastMana[index] + mana
        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_MANA_DETECTION then
                set manaTimer           = CreateTimer()
                set checkLoopTrigger    = CreateTrigger()
                set addUnitTrigger      = CreateTrigger()
                set removeUnitTrigger   = CreateTrigger()

                call StartTimerBJ(manaTimer, true, MANA_CHECK_INTERVAL)
              
                call TriggerRegisterTimerExpireEvent(checkLoopTrigger, manaTimer)
                call TriggerAddAction(checkLoopTrigger, function ManaDetection.CheckLoop)

                call TriggerRegisterVariableEvent(addUnitTrigger, "udg_UnitIndexEvent", EQUAL, 1.00)
                call TriggerRegisterVariableEvent(addUnitTrigger, "udg_DeathEvent", EQUAL, 2.00)
                call TriggerAddAction(addUnitTrigger, function ManaDetection.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 ManaDetection.ActionRemoveUnit)
            endif
        endmethod
    endstruct

    struct Mana

        /*
            Variables used in mana restoration
        */
        private unit manaSource
        private unit manaTarget

        private real manaBaseAmt
        private real manaPrevAmt
        private real manaAmount
      
        private real effectiveManaAmt
        private real overManaAmount

        private boolean isSelfMana
      
        /*
            Variables used for listing
        */
        private Mana next

        private static Mana first
        private static Mana last


        private static method create takes unit source, unit target, real amount returns Mana
            local Mana this = Mana.allocate()

            set this.manaSource         = source
            set this.manaTarget         = target
            set this.manaAmount         = amount
            set this.manaBaseAmt        = amount
            set this.manaPrevAmt        = amount

            set this.isSelfMana         = (source == target)
            set this.overManaAmount     = 0
            set this.effectiveManaAmt   = 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 ManaUnit takes unit source, unit target, real amount returns nothing
            local Mana h = Mana.create(source, target, amount)

            if first == 0 then
                set first = h
                set last = h
                call Mana.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_ManaSource              = this.manaSource
            set udg_ManaTarget              = this.manaTarget
          
            set udg_ManaAmount              = this.manaAmount
            set udg_ManaBaseAmount          = this.manaBaseAmt
            set udg_ManaPrvAmount           = this.manaPrevAmt
          
            set udg_EffectiveManaAmount     = this.effectiveManaAmt
            set udg_OverManaAmount          = this.overManaAmount

            set udg_IsSelfMana              = this.isSelfMana
        endmethod

        // Updates this instance members with GUI vars that might have been updated
        private method getEventVariables takes nothing returns nothing
            set this.manaSource     = udg_ManaSource
            set this.manaTarget     = udg_ManaTarget
            set this.manaAmount     = udg_ManaAmount
            set this.manaPrevAmt    = udg_ManaAmount    // Stores this step result for reference in next step if needed.

            set this.isSelfMana     = (this.manaSource == this.manaTarget)
        endmethod

        // Reset Variables to a neutral state
        private static method resetEventVariables takes nothing returns nothing
            set udg_ManaSource              = null
            set udg_ManaTarget              = null

            set udg_ManaBaseAmount          = 0.00
            set udg_ManaPrvAmount           = 0.00
            set udg_ManaAmount              = 0.00

            set udg_EffectiveManaAmount     = 0.00
            set udg_OverManaAmount          = 0.00

            set udg_IsSelfMana              = false
        endmethod

        /*
            Function where the hmana restoration and Events are done.
            Mana restoration are dealt one at the time. Mana restoration created during Events will be postponed.
        */
        private static method Run takes nothing returns nothing
            local Mana h
            local integer u_custom
            local real u_current_mp
            local real u_max_mp
            local real u_missing_mp
            local real new_missing_mp
            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.manaAmount == 0.00) then

                    loop
                        exitwhen i > MAX_PREMANA_EVENTS
                        // Set GUI vars
                        call h.setEventVariables()

                        // Fire PreManaEvent
                        set udg_PreManaEvent    = 0.00
                        set udg_PreManaEvent    = i
                        set udg_PreManaEvent    = 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_MANA and h.manaAmount < 0.00 then
                        set h.manaAmount = 0.00
                    endif                      
                  
                    // Get target infos
                    set u_custom            = GetUnitUserData(h.manaTarget)
                    set u_max_mp            = GetUnitState(h.manaTarget, UNIT_STATE_MAX_MANA)
                    set u_current_mp         = GetUnitState(h.manaTarget, UNIT_STATE_MANA)
                    set u_missing_mp        = u_max_mp - u_current_mp
                  
                    if h.manaAmount > 0 then
                        // Mana Restoration itself.
                        call SetUnitState(h.manaTarget, UNIT_STATE_MANA, u_current_mp + h.manaAmount)
                      
                        // Getting new missing mp
                        set new_missing_mp = GetUnitState(h.manaTarget, UNIT_STATE_MAX_MANA) - GetUnitState(h.manaTarget, UNIT_STATE_MANA)

                        // Computing effective mana restoration and possible overmana restoration
                        set h.effectiveManaAmt = u_missing_mp - new_missing_mp
                        set h.overManaAmount = h.manaAmount - h.effectiveManaAmt
                    else
                        // Use Damage function to be compatible with damage engines
                        call UnitDamageTargetBJ(h.manaSource, h.manaTarget, h.manaAmount, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC)
                        set h.effectiveManaAmt = h.manaAmount
                    endif
                  
                    // Fire OverManaEvent
                    if h.overManaAmount > OVERMANA_THRESHOLD then
                        // Set GUI vars
                        call h.setEventVariables()

                        set udg_OverManaEvent   = 0.00
                        set udg_OverManaEvent   = 1.00
                        set udg_OverManaEvent   = 0.00
                    endif

                    // Fire AfterManaEvent if enough mana restoration was done, else ZeroManaEvent
                    if h.effectiveManaAmt > ZEROMANA_THRESHOLD or (h.overManaAmount > 0.00) then
                        // Update ManaDetection system, avoiding the event triggering twice for this mana restoration
                        call ManaDetection.AdjustAfterMana(h.manaTarget, h.manaAmount)

                        // Set GUI vars
                        call h.setEventVariables()

                        set udg_AfterManaEvent  = 0.00
                        set udg_AfterManaEvent  = 1.00
                        set udg_AfterManaEvent  = 0.00
                    elseif h.effectiveManaAmt < NEGATIVE_MANA_THRESHOLD then
                        // Set GUI vars
                        call h.setEventVariables()

                        set udg_NegativeManaEvent   = 0.00
                        set udg_NegativeManaEvent   = 1.00
                        set udg_NegativeManaEvent   = 0.00
                    else
                        // Set GUI vars
                        call h.setEventVariables()

                        set udg_ZeroManaEvent   = 0.00
                        set udg_ZeroManaEvent   = 1.00
                        set udg_ZeroManaEvent   = 0.00
                    endif

                endif

                // Resets variables
                call resetEventVariables()

                // Set the next mana that will be run
                set first = h.next
              
                // Free resources
                call h.destroy()
            endloop

        endmethod
    endstruct

endlibrary
 
Last edited:
I've finished making the Mana Engine which I mentioned earlier. I can share it with you if you'd like. @Marchombre

JASS:
/*
    vJass ManaEngine 1.0.0
    by Kotol

    Requirement if using ManaRestorationDetection: GUI Unit Indexer 1.4.0.0 (by Bribe)
*/

/*
    ==================
    ==== Jass API ====
    ==================

    // This struct is used for native mana restoration and mana regeneration detection
    struct ManaDetection
        // 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 Optional trigger
        // Force a check on target unit. Used before applying damage.
        static method Adjust takes unit target returns nothing

        // Used for Optional trigger
        // Adjust values after taking damage, or some events might not trigger due to MP diff
        static method AdjustAfterDamage takes unit target, real dmg returns nothing

        // Adjust values after receiving a triggered mana, to avoid triggering the mana restoration event a second time by detection
        static method AdjustAfterMana takes unit u, real mana returns nothing

    // This struct is used for triggered mana restoration and complete Mana Events
    struct Mana
        // User Entry Point. Takes mana source, target and amount
        // If an other mana is currently running, postpon new one to avoid conflicts
        static method ManaUnit takes unit source, unit target, real amount

*/
library ManaEngine
    globals
        /*
            Variables used for detecting war3 native mana restoration and mana regeneration
        */
        private constant boolean USE_MANA_DETECTION         = true      // Enable/disable ManaDetection triggers. If set to true, make sure to have Unit Indexer requirement

        private constant real MANA_THRESHOLD                = 5.00
        private constant real MANA_CHECK_INTERVAL           = 0.05

        private constant real MANA_REGEN_INTELLIGENCE_VALUE = 0.05
        private constant real MANA_REGEN_THRESHOLD          = 5.00
        private constant real MANA_REGEN_EVENT_INTERVAL     = 1.00

        private constant boolean IS_NATIVE_MANA_SELF        = false     // True: native mana restoration will be considered self mana restoration. False: considered from an unknown source
        private constant boolean IS_NATIVE_MANA_REGEN_SELF  = true      // True: mana regen will be considered self mana restoration. False: considered from an unknown source

        // Booleans to control control what type of units should be ignored by the ManaDetection 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 ZEROMANA_THRESHOLD            = 1.00      // Mana restore under this value will fire ZeroManaEvent
        private constant real OVERMANA_THRESHOLD            = 1.00      // How much overmana is needed to fire OverManaEvent
        private constant real MAX_PREMANA_EVENTS            = 4.00      // PreManaEvent will fire this many time. Useful if you want to make sure modifiers are applied if the right order (% then flat for example).
     
        private constant boolean ALLOW_NEGATIVE_MANA        = false     // If true, negative mana restoration after PreManaEvents will do damage. Else, mana restore won't go under 0.
        private constant real NEGATIVE_MANA_THRESHOLD       = -1.00     // Used if ALLOW_NEGATIVE_MANA = true. Threshold for firing NegativeManaEvent.
    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_NextManaSource
        unit                udg_NextManaTarget
        real                udg_NextManaAmount
    */

    // GUI Vars to catch events
    /*

        // Variables you can access to get infos on current mana
        unit                udg_ManaSource
        unit                udg_ManaTarget

        real                udg_ManaAmount              // Current mana amount.
        real                udg_ManaBaseAmount          // Mana amount before any event was fired.
        real                udg_ManaPrvAmount           // Mana amount at the start of this event.

        real                udg_EffectiveManaAmount     // How much MPs was actually mana restored.
        real                udg_OverManaAmount          // How much mana is left after getting the unit to full MP.
        boolean             udg_IsSelfMana

        // Events variables
        real                udg_PreManaEvent            // Can be 0.00 or X.00, with 1 <= X <= MAX_PREMANA_EVENTS
        real                udg_AfterManaEvent          // Can be 0.00 or 1.00 or 0.50
        real                udg_ZeroManaEvent           // Can be 0.00 or 1.00
        real                udg_OverManaEvent           // Can be 0.00 or 1.00
        real                udg_NegativeManaEvent       // Can be 0.00 or 1.00

    */
 
    struct ManaDetection
        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 lastMana
        private static real array manaregen

        private static real array manaregenBuildUp
        private static real array manaregenTimeLeft
     
        private static integer count        = 0
        private static integer unitIndex    = 0

        private static timer manaTimer
     
     
        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 lastMana[unitId]        = GetUnitState(udg_UDexUnits[unitId], UNIT_STATE_MANA)
            set manaregenTimeLeft[unitId]   = MANA_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 unitMP
            local real diff
            local real mana
         
            loop
                exitwhen unitIndex > max
             
                set unitIndex = indices[unitIndex]

                set target              = udg_UDexUnits[unitIndex]
                set unitMP              = GetUnitState(target, UNIT_STATE_MANA)
                set diff                = unitMP - lastMana[unitIndex]
                set lastMana[unitIndex] = unitMP

                set mana                = diff - RMaxBJ(0.00, manaregen[unitIndex])

                if mana > MANA_THRESHOLD then
                    // Set Up variables
                    set udg_ManaAmount = mana
                    set udg_ManaTarget = target

                    if IS_NATIVE_MANA_SELF then
                        set udg_ManaSource = target
                    else
                        set udg_ManaSource = null   // Can't know the source of a native mana restoration sadly
                    endif
                 
                    set udg_IsSelfMana = IS_NATIVE_MANA_SELF

                    // Fire AfterManaEvent
                    set udg_AfterManaEvent  = 0.00
                    set udg_AfterManaEvent  = 1.00
                    set udg_AfterManaEvent  = 0.00
                else
                    // Check ManaRegen
                    set manaregen[unitIndex] = (manaregen[unitIndex] + diff) * 0.5

                    set manaregenBuildUp[unitIndex]     = manaregenBuildUp[unitIndex] + diff
                    set manaregenTimeLeft[unitIndex]    = manaregenTimeLeft[unitIndex] - MANA_CHECK_INTERVAL

                    if manaregenTimeLeft[unitIndex] <= 0.00 then
                        // reset clock
                        set manaregenTimeLeft[unitIndex] = MANA_REGEN_EVENT_INTERVAL

                        set mana = manaregenBuildUp[unitIndex]
                        set manaregenBuildUp[unitIndex] = 0.00
                     
                        set diff = mana

                        // Ignore manaregen from hero stats
                        if IsUnitType(target, UNIT_TYPE_HERO) then
                            set diff = diff - MANA_REGEN_INTELLIGENCE_VALUE * I2R(GetHeroInt(target, true))
                        endif

                        // Fire ManaRegen Event (AfterManaEvent = 0.5)
                        if diff > MANA_REGEN_THRESHOLD then
                             // Set Up variables
                            set udg_ManaAmount = mana
                            set udg_ManaTarget = target

                            if IS_NATIVE_MANA_REGEN_SELF then
                                set udg_ManaSource = target
                            else
                                set udg_ManaSource = null   // Can't know the source of a native mana restoration sadly
                            endif
                         
                            set udg_IsSelfMana = IS_NATIVE_MANA_REGEN_SELF

                            // Fire AfterManaEvent
                            set udg_AfterManaEvent  = 0.00
                            set udg_AfterManaEvent  = 0.50
                            set udg_AfterManaEvent  = 0.00
                        endif
                    endif
                endif

                set unitIndex = indexRef[unitIndex]
                set unitIndex = unitIndex +1
            endloop
        endmethod

        // Check unit new mana and fire AfterManaEvent if needed
        static method Adjust takes unit target returns nothing
            local integer index     = GetUnitUserData(target)
            local real u_mp         = GetUnitState(target, UNIT_STATE_MANA)
         
            local real mana = u_mp - lastMana[index] - manaregen[index] * TimerGetElapsed(manaTimer) / MANA_CHECK_INTERVAL

            if mana > MANA_THRESHOLD then
                set lastMana[index] = lastMana[index] + mana
             
                // Set Up variables
                set udg_ManaAmount = mana
                set udg_ManaTarget = target

                if IS_NATIVE_MANA_SELF then
                    set udg_ManaSource = target
                else
                    set udg_ManaSource = null   // Can't know the source of a native mana restoration sadly
                endif
             
                set udg_IsSelfMana = IS_NATIVE_MANA_SELF

                // Fire AfterManaEvent
                set udg_AfterManaEvent  = 0.00
                set udg_AfterManaEvent  = 1.00
                set udg_AfterManaEvent  = 0.00
            endif
        endmethod

        // Adjust values when unit takes damage, or mana might not be detected
        static method AdjustAfterDamage takes unit target, real dmg returns nothing
            local integer index     = GetUnitUserData(target)
            local real u_mp         = GetUnitState(target, UNIT_STATE_MANA)

            if dmg > 0 then
                set lastMana[index] = u_mp - manaregen[index] * TimerGetElapsed(manaTimer) / MANA_CHECK_INTERVAL
            else
                call Adjust(target)
            endif
        endmethod

        // Adjust after mana, avoiding a double event for one mana
        static method AdjustAfterMana takes unit u, real mana returns nothing
            local integer index = GetUnitUserData(u)
            set lastMana[index] = lastMana[index] + mana
        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_MANA_DETECTION then
                set manaTimer           = CreateTimer()
                set checkLoopTrigger    = CreateTrigger()
                set addUnitTrigger      = CreateTrigger()
                set removeUnitTrigger   = CreateTrigger()

                call StartTimerBJ(manaTimer, true, MANA_CHECK_INTERVAL)
             
                call TriggerRegisterTimerExpireEvent(checkLoopTrigger, manaTimer)
                call TriggerAddAction(checkLoopTrigger, function ManaDetection.CheckLoop)

                call TriggerRegisterVariableEvent(addUnitTrigger, "udg_UnitIndexEvent", EQUAL, 1.00)
                call TriggerRegisterVariableEvent(addUnitTrigger, "udg_DeathEvent", EQUAL, 2.00)
                call TriggerAddAction(addUnitTrigger, function ManaDetection.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 ManaDetection.ActionRemoveUnit)
            endif
        endmethod
    endstruct

    struct Mana

        /*
            Variables used in mana restoration
        */
        private unit manaSource
        private unit manaTarget

        private real manaBaseAmt
        private real manaPrevAmt
        private real manaAmount
     
        private real effectiveManaAmt
        private real overManaAmount

        private boolean isSelfMana
     
        /*
            Variables used for listing
        */
        private Mana next

        private static Mana first
        private static Mana last


        private static method create takes unit source, unit target, real amount returns Mana
            local Mana this = Mana.allocate()

            set this.manaSource         = source
            set this.manaTarget         = target
            set this.manaAmount         = amount
            set this.manaBaseAmt        = amount
            set this.manaPrevAmt        = amount

            set this.isSelfMana         = (source == target)
            set this.overManaAmount     = 0
            set this.effectiveManaAmt   = 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 ManaUnit takes unit source, unit target, real amount returns nothing
            local Mana h = Mana.create(source, target, amount)

            if first == 0 then
                set first = h
                set last = h
                call Mana.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_ManaSource              = this.manaSource
            set udg_ManaTarget              = this.manaTarget
         
            set udg_ManaAmount              = this.manaAmount
            set udg_ManaBaseAmount          = this.manaBaseAmt
            set udg_ManaPrvAmount           = this.manaPrevAmt
         
            set udg_EffectiveManaAmount     = this.effectiveManaAmt
            set udg_OverManaAmount          = this.overManaAmount

            set udg_IsSelfMana              = this.isSelfMana
        endmethod

        // Updates this instance members with GUI vars that might have been updated
        private method getEventVariables takes nothing returns nothing
            set this.manaSource     = udg_ManaSource
            set this.manaTarget     = udg_ManaTarget
            set this.manaAmount     = udg_ManaAmount
            set this.manaPrevAmt    = udg_ManaAmount    // Stores this step result for reference in next step if needed.

            set this.isSelfMana     = (this.manaSource == this.manaTarget)
        endmethod

        // Reset Variables to a neutral state
        private static method resetEventVariables takes nothing returns nothing
            set udg_ManaSource              = null
            set udg_ManaTarget              = null

            set udg_ManaBaseAmount          = 0.00
            set udg_ManaPrvAmount           = 0.00
            set udg_ManaAmount              = 0.00

            set udg_EffectiveManaAmount     = 0.00
            set udg_OverManaAmount          = 0.00

            set udg_IsSelfMana              = false
        endmethod

        /*
            Function where the hmana restoration and Events are done.
            Mana restoration are dealt one at the time. Mana restoration created during Events will be postponed.
        */
        private static method Run takes nothing returns nothing
            local Mana h
            local integer u_custom
            local real u_current_mp
            local real u_max_mp
            local real u_missing_mp
            local real new_missing_mp
            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.manaAmount == 0.00) then

                    loop
                        exitwhen i > MAX_PREMANA_EVENTS
                        // Set GUI vars
                        call h.setEventVariables()

                        // Fire PreManaEvent
                        set udg_PreManaEvent    = 0.00
                        set udg_PreManaEvent    = i
                        set udg_PreManaEvent    = 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_MANA and h.manaAmount < 0.00 then
                        set h.manaAmount = 0.00
                    endif                     
                 
                    // Get target infos
                    set u_custom            = GetUnitUserData(h.manaTarget)
                    set u_max_mp            = GetUnitState(h.manaTarget, UNIT_STATE_MAX_MANA)
                    set u_current_mp         = GetUnitState(h.manaTarget, UNIT_STATE_MANA)
                    set u_missing_mp        = u_max_mp - u_current_mp
                 
                    if h.manaAmount > 0 then
                        // Mana Restoration itself.
                        call SetUnitState(h.manaTarget, UNIT_STATE_MANA, u_current_mp + h.manaAmount)
                     
                        // Getting new missing mp
                        set new_missing_mp = GetUnitState(h.manaTarget, UNIT_STATE_MAX_MANA) - GetUnitState(h.manaTarget, UNIT_STATE_MANA)

                        // Computing effective mana restoration and possible overmana restoration
                        set h.effectiveManaAmt = u_missing_mp - new_missing_mp
                        set h.overManaAmount = h.manaAmount - h.effectiveManaAmt
                    else
                        // Use Damage function to be compatible with damage engines
                        call UnitDamageTargetBJ(h.manaSource, h.manaTarget, h.manaAmount, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC)
                        set h.effectiveManaAmt = h.manaAmount
                    endif
                 
                    // Fire OverManaEvent
                    if h.overManaAmount > OVERMANA_THRESHOLD then
                        // Set GUI vars
                        call h.setEventVariables()

                        set udg_OverManaEvent   = 0.00
                        set udg_OverManaEvent   = 1.00
                        set udg_OverManaEvent   = 0.00
                    endif

                    // Fire AfterManaEvent if enough mana restoration was done, else ZeroManaEvent
                    if h.effectiveManaAmt > ZEROMANA_THRESHOLD or (h.overManaAmount > 0.00) then
                        // Update ManaDetection system, avoiding the event triggering twice for this mana restoration
                        call ManaDetection.AdjustAfterMana(h.manaTarget, h.manaAmount)

                        // Set GUI vars
                        call h.setEventVariables()

                        set udg_AfterManaEvent  = 0.00
                        set udg_AfterManaEvent  = 1.00
                        set udg_AfterManaEvent  = 0.00
                    elseif h.effectiveManaAmt < NEGATIVE_MANA_THRESHOLD then
                        // Set GUI vars
                        call h.setEventVariables()

                        set udg_NegativeManaEvent   = 0.00
                        set udg_NegativeManaEvent   = 1.00
                        set udg_NegativeManaEvent   = 0.00
                    else
                        // Set GUI vars
                        call h.setEventVariables()

                        set udg_ZeroManaEvent   = 0.00
                        set udg_ZeroManaEvent   = 1.00
                        set udg_ZeroManaEvent   = 0.00
                    endif

                endif

                // Resets variables
                call resetEventVariables()

                // Set the next mana that will be run
                set first = h.next
             
                // Free resources
                call h.destroy()
            endloop

        endmethod
    endstruct

endlibrary
Heal & Mana Engine v2.0.0
 

Attachments

Hello,

Sorry, didn't have time to look at what you send yet.
Will check all of this during this week.

Besides the new mana engine, did you change anything to the heal engine itself that I should also see ?
No changes on Heal Engine.
I just add-on Mana Engine which is based on Heal Engine.
 
Didn't have much time to look into it, as I was working on other projects, but it seems that some mana modifications aren't applying correctly, or not at the correct step.
I want to make sure everything is fine before adding to official release.

Plus, I think I'll add a thing to make mana restoration and mana burn 2 different things with distinct events, with options to make negative restoration either 0 or count as mana burn, and negative mana burn either 0 or count as restoration.
 
Didn't have much time to look into it, as I was working on other projects, but it seems that some mana modifications aren't applying correctly, or not at the correct step.
I want to make sure everything is fine before adding to official release.

Plus, I think I'll add a thing to make mana restoration and mana burn 2 different things with distinct events, with options to make negative restoration either 0 or count as mana burn, and negative mana burn either 0 or count as restoration.
The problem might be originating from the GetWidgetLife(h.healTarget) function. Since the GetWidgetMana() function is missing, I used GetUnitState(h.manaTarget, UNIT_STATE_MANA) instead.

Adding mana restoration and mana burn as two separate mechanics would make more sense, in my opinion.
 
Back
Top