Voodoo Restoration doesn't heal units.

Level 16
Joined
Jul 19, 2007
Messages
939
I have imported the "Voodoo Restoration" from DOTA from a spellpack into my map but it doesn't work at all... No units are healed when I activate it and yes I have checked the RAW-Code of ability and buff and it's correct. Also I want it to ONLY heal organic units.

JASS:
//TESH.scrollpos=12
//TESH.alwaysfold=0
scope VoodooRestoration initializer Init

globals
    private constant integer AbilId = 'A0MT'
    private constant integer BuffId = 'B08E'
    
    private timer T = CreateTimer()
    private unit U
    private integer I
endglobals
private struct data
    unit u
    player p
    integer lvl
    trigger t
    
    static method Stop takes nothing returns nothing
        local integer lvl = GetUnitAbilityLevel(U,AbilId)
        
        call UnitRemoveAbility(U,AbilId)
        call UnitAddAbility(U,AbilId)
        call SetUnitAbilityLevel(U,AbilId,lvl)
    endmethod
    method onDestroy takes nothing returns nothing
        call DestroyTriggerEx(.t)
        set U = .u
        call TimerStart(T,0.,false,function data.Stop)
    endmethod
endstruct

private function Filt takes nothing returns boolean
    local data d = I
    local unit targ = GetFilterUnit()
    local real r
    
    if not IsUnitEnemy(targ,d.p) and GetWidgetLife(targ)>.405 and GetUnitAbilityLevel(targ,BuffId)>0 then
        set r = (8.+d.lvl*8.)/ 3.
        call SetUnitState(targ,UNIT_STATE_LIFE,GetUnitState(targ,UNIT_STATE_LIFE)+r)
    endif
    
    set targ = null
    return false
endfunction
private function Effects takes nothing returns boolean
    local data d = GetTriggerData(GetTriggeringTrigger())
    local group g
    local real r

    if GetTriggerEventId()==EVENT_UNIT_ISSUED_ORDER then
        if GetIssuedOrderId()==852178 then
            call d.destroy()
        endif
    else
        set g = NewGroup()
        set I = d
        call GroupEnumUnitsInRange(g,GetUnitX(d.u),GetUnitY(d.u),350.,Condition(function Filt))
        call ReleaseGroup(g)
        
        set r = (d.lvl*6+2.)/3.
        call SetUnitState(d.u,UNIT_STATE_MANA,GetUnitState(d.u,UNIT_STATE_MANA)-r)
        if GetUnitState(d.u,UNIT_STATE_MANA)<r then
            call IssueImmediateOrder(d.u,"unimmolation")
        endif
    endif
    
    return false
endfunction
private function Actions takes nothing returns nothing
    local data d = data.create()
    
    set d.u=GetTriggerUnit()
    set d.p = GetOwningPlayer(d.u)
    set d.lvl = GetUnitAbilityLevel(d.u,AbilId)
    set d.t=CreateTrigger()
    call TriggerRegisterTimerEvent(d.t,0.33,true)
    call TriggerRegisterUnitEvent(d.t,d.u,EVENT_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(d.t,Condition(function Effects))
    call SetTriggerData(d.t,d)
endfunction

private function Conds takes nothing returns boolean
    if GetSpellAbilityId()==AbilId then
        call Actions()
    endif
    return false
endfunction
private function Init takes nothing returns nothing
    local trigger t=CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t,Condition(function Conds))
endfunction

endscope
 
For starters, debug it step by step, because immolation style abilities are extremely hardcoded.

First thing verify whether the toggle order is even firing correctly.

Add this inside your issued-order event:


call BJDebugMsg("Order: " + OrderId2String(GetIssuedOrderId()) + " (" + I2S(GetIssuedOrderId()) + ")")


Then toggle the spell and see what actual orders your custom ability uses.


If that works, next thing to test is whether the periodic timer is running at all. Add:

call BJDebugMsg("tick") inside Effects




If you see ticks, then the problem is probably the filter.

This line is suspicious:

GetUnitAbilityLevel(targ,BuffId)>0
because BuffId is a buff rawcode, not an ability rawcode

Try temporarily removing the buff check entirely and just test:

if not IsUnitEnemy(targ,d.p) and GetWidgetLife(targ)>.405 and not IsUnitType(targ, UNIT_TYPE_MECHANICAL) and not IsUnitType(targ, UNIT_TYPE_STRUCTURE) then


...if healing suddenly works, then the issue was the buff detection rather than the heal logic itself.

I’d also register:

EVENT_UNIT_ISSUED_ORDER
EVENT_UNIT_ISSUED_POINT_ORDER
EVENT_UNIT_ISSUED_TARGET_ORDER

for cleanup safety because Immolation toggles can behave weirdly depending on engine state/copies.
 
For starters, debug it step by step, because immolation style abilities are extremely hardcoded.

First thing verify whether the toggle order is even firing correctly.

Add this inside your issued-order event:


call BJDebugMsg("Order: " + OrderId2String(GetIssuedOrderId()) + " (" + I2S(GetIssuedOrderId()) + ")")


Then toggle the spell and see what actual orders your custom ability uses.


If that works, next thing to test is whether the periodic timer is running at all. Add:

call BJDebugMsg("tick") inside Effects




If you see ticks, then the problem is probably the filter.

This line is suspicious:

GetUnitAbilityLevel(targ,BuffId)>0
because BuffId is a buff rawcode, not an ability rawcode

Try temporarily removing the buff check entirely and just test:

if not IsUnitEnemy(targ,d.p) and GetWidgetLife(targ)>.405 and not IsUnitType(targ, UNIT_TYPE_MECHANICAL) and not IsUnitType(targ, UNIT_TYPE_STRUCTURE) then


...if healing suddenly works, then the issue was the buff detection rather than the heal logic itself.

I’d also register:

EVENT_UNIT_ISSUED_ORDER
EVENT_UNIT_ISSUED_POINT_ORDER
EVENT_UNIT_ISSUED_TARGET_ORDER

for cleanup safety because Immolation toggles can behave weirdly depending on engine state/copies.
Can't you please make the full JASS-script pls? I'm not sure where to put these lines... Also I noticed that this ability works only the FIRST time it is used, next time it stops working :-/
 
Can't you please make the full JASS-script pls? I'm not sure where to put these lines... Also I noticed that this ability works only the FIRST time it is used, next time it stops working :-/
I overlooked this part:
call UnitRemoveAbility(U,AbilId)
call UnitAddAbility(U,AbilId)

Code:
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope VoodooRestoration initializer Init

globals
    private constant integer AbilId = 'A0MT' // Rawcode of your custom Immolation
    private constant integer BuffId = 'B08E' // Rawcode of your custom Buff
    
    // Global variable used to bridge data over to the Group Filter
    private integer TempStructBridge = 0
endglobals

private struct data
    unit u
    player p
    integer lvl
    trigger t
    
    method onDestroy takes nothing returns nothing
        call DestroyTrigger(.t)
        set .t = null
        set .u = null
        set .p = null
    endmethod
endstruct

private function Filt takes nothing returns boolean
    local data d = TempStructBridge
    local unit targ = GetFilterUnit()
    local real healAmount
    
    // Filter: Is an ally, is alive, is NOT mechanical, and is NOT a structure
    if not IsUnitEnemy(targ, d.p) and GetWidgetLife(targ) > 0.405 then
        if not IsUnitType(targ, UNIT_TYPE_MECHANICAL) and not IsUnitType(targ, UNIT_TYPE_STRUCTURE) then
            
            // Optional: If you want to strictly require the buff to be on them, uncomment the line below:
            // if GetUnitAbilityLevel(targ, BuffId) > 0 then
            
            set healAmount = (8.0 + d.lvl * 8.0) / 3.0
            call SetUnitState(targ, UNIT_STATE_LIFE, GetUnitState(targ, UNIT_STATE_LIFE) + healAmount)
            
            // if GetUnitAbilityLevel(targ, BuffId) > 0 then
            //     endif
                
        endif
    endif
    
    set targ = null
    return false
endfunction

private function Effects takes nothing returns boolean
    local trigger trg = GetTriggeringTrigger()
    local data d = GetTriggerData(trg)
    local group g
    local real manaCost

    // If the unit is given a new order while the spell is active
    if GetTriggerEventId() == EVENT_UNIT_ISSUED_ORDER then
        // 852178 is the order ID for "unimmolation" (turning it off)
        if GetIssuedOrderId() == 852178 then
            call d.destroy()
        endif
    else
        // Periodic execution (every 0.33 seconds)
        set g = CreateGroup()
        set TempStructBridge = d
        call GroupEnumUnitsInRange(g, GetUnitX(d.u), GetUnitY(d.u), 350.0, Condition(function Filt))
        call DestroyGroup(g)
        set g = null
        
        // Mana drain calculation
        set manaCost = (d.lvl * 6.0 + 2.0) / 3.0
        call SetUnitState(d.u, UNIT_STATE_MANA, GetUnitState(d.u, UNIT_STATE_MANA) - manaCost)
        
        // If the caster runs out of mana, force the ability to turn off
        if GetUnitState(d.u, UNIT_STATE_MANA) < manaCost then
            call IssueImmediateOrder(d.u, "unimmolation")
            call d.destroy()
        endif
    endif
    
    set trg = null
    return false
endfunction

private function Actions takes nothing returns nothing
    local data d = data.create()
    
    set d.u = GetTriggerUnit()
    set d.p = GetOwningPlayer(d.u)
    set d.lvl = GetUnitAbilityLevel(d.u, AbilId)
    set d.t = CreateTrigger()
    
    // Register the periodic interval loop and the turn-off order event
    call TriggerRegisterTimerEvent(d.t, 0.33, true)
    call TriggerRegisterUnitEvent(d.t, d.u, EVENT_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(d.t, Condition(function Effects))
    call SetTriggerData(d.t, d)
endfunction

private function Conds takes nothing returns boolean
    // 852177 is the engine order ID for "immolation" (turning it on)
    if GetIssuedOrderId() == 852177 and GetUnitAbilityLevel(GetTriggerUnit(), AbilId) > 0 then
        call Actions()
    endif
    return false
endfunction

private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    // Listens for players turning on toggle abilities
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(t, Condition(function Conds))
    set t = null
endfunction

endscope

I’m also not sure about this line

if GetIssuedOrderId() == 852178 then
call d.destroy()
endif
Cuz immolation toggle spam can issue orders internally; try with the updated code, we’ll see after that.
 
I overlooked this part:
call UnitRemoveAbility(U,AbilId)
call UnitAddAbility(U,AbilId)

Code:
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope VoodooRestoration initializer Init

globals
    private constant integer AbilId = 'A0MT' // Rawcode of your custom Immolation
    private constant integer BuffId = 'B08E' // Rawcode of your custom Buff
  
    // Global variable used to bridge data over to the Group Filter
    private integer TempStructBridge = 0
endglobals

private struct data
    unit u
    player p
    integer lvl
    trigger t
  
    method onDestroy takes nothing returns nothing
        call DestroyTrigger(.t)
        set .t = null
        set .u = null
        set .p = null
    endmethod
endstruct

private function Filt takes nothing returns boolean
    local data d = TempStructBridge
    local unit targ = GetFilterUnit()
    local real healAmount
  
    // Filter: Is an ally, is alive, is NOT mechanical, and is NOT a structure
    if not IsUnitEnemy(targ, d.p) and GetWidgetLife(targ) > 0.405 then
        if not IsUnitType(targ, UNIT_TYPE_MECHANICAL) and not IsUnitType(targ, UNIT_TYPE_STRUCTURE) then
          
            // Optional: If you want to strictly require the buff to be on them, uncomment the line below:
            // if GetUnitAbilityLevel(targ, BuffId) > 0 then
          
            set healAmount = (8.0 + d.lvl * 8.0) / 3.0
            call SetUnitState(targ, UNIT_STATE_LIFE, GetUnitState(targ, UNIT_STATE_LIFE) + healAmount)
          
            // if GetUnitAbilityLevel(targ, BuffId) > 0 then
            //     endif
              
        endif
    endif
  
    set targ = null
    return false
endfunction

private function Effects takes nothing returns boolean
    local trigger trg = GetTriggeringTrigger()
    local data d = GetTriggerData(trg)
    local group g
    local real manaCost

    // If the unit is given a new order while the spell is active
    if GetTriggerEventId() == EVENT_UNIT_ISSUED_ORDER then
        // 852178 is the order ID for "unimmolation" (turning it off)
        if GetIssuedOrderId() == 852178 then
            call d.destroy()
        endif
    else
        // Periodic execution (every 0.33 seconds)
        set g = CreateGroup()
        set TempStructBridge = d
        call GroupEnumUnitsInRange(g, GetUnitX(d.u), GetUnitY(d.u), 350.0, Condition(function Filt))
        call DestroyGroup(g)
        set g = null
      
        // Mana drain calculation
        set manaCost = (d.lvl * 6.0 + 2.0) / 3.0
        call SetUnitState(d.u, UNIT_STATE_MANA, GetUnitState(d.u, UNIT_STATE_MANA) - manaCost)
      
        // If the caster runs out of mana, force the ability to turn off
        if GetUnitState(d.u, UNIT_STATE_MANA) < manaCost then
            call IssueImmediateOrder(d.u, "unimmolation")
            call d.destroy()
        endif
    endif
  
    set trg = null
    return false
endfunction

private function Actions takes nothing returns nothing
    local data d = data.create()
  
    set d.u = GetTriggerUnit()
    set d.p = GetOwningPlayer(d.u)
    set d.lvl = GetUnitAbilityLevel(d.u, AbilId)
    set d.t = CreateTrigger()
  
    // Register the periodic interval loop and the turn-off order event
    call TriggerRegisterTimerEvent(d.t, 0.33, true)
    call TriggerRegisterUnitEvent(d.t, d.u, EVENT_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(d.t, Condition(function Effects))
    call SetTriggerData(d.t, d)
endfunction

private function Conds takes nothing returns boolean
    // 852177 is the engine order ID for "immolation" (turning it on)
    if GetIssuedOrderId() == 852177 and GetUnitAbilityLevel(GetTriggerUnit(), AbilId) > 0 then
        call Actions()
    endif
    return false
endfunction

private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    // Listens for players turning on toggle abilities
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(t, Condition(function Conds))
    set t = null
endfunction

endscope

I’m also not sure about this line

if GetIssuedOrderId() == 852178 then
call d.destroy()
endif
Cuz immolation toggle spam can issue orders internally; try with the updated code, we’ll see after that.
So far it seems to be working. Thank you!

Grrrr... I still doesn't work! After using it about 2-4 times, it stops working again... I just don't understand what causes it :(
 
Last edited:

So far it seems to be working. Thank you!

Grrrr... I still doesn't work! After using it about 2-4 times, it stops working again... I just don't understand what causes it :(
Probably memory corruption if the hero dies, leaving the timer running forever... also I'm guessing that the lack of mana is forcing it off. I added a boolean check, try with the updated code:

Code:
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope VoodooRestoration initializer Init

globals
    private constant integer AbilId = 'A0MT' // Rawcode of your custom Immolation
    private constant integer BuffId = 'B08E' // Rawcode of your custom Buff
 
    // Global variable used to bridge data over to the Group Filter
    private integer TempStructBridge = 0
endglobals

private struct data
    unit u
    player p
    integer lvl
    trigger t
    boolean active // Safety valve to prevent double-free corruption
 
    method onDestroy takes nothing returns nothing
        call DestroyTrigger(.t)
        set .t = null
        set .u = null
        set .p = null
    endmethod
endstruct

private function Filt takes nothing returns boolean
    local data d = TempStructBridge
    local unit targ = GetFilterUnit()
    local real healAmount
 
    // Filter: Is an ally, is alive, is NOT mechanical, and is NOT a structure
    if not IsUnitEnemy(targ, d.p) and GetWidgetLife(targ) > 0.405 then
        if not IsUnitType(targ, UNIT_TYPE_MECHANICAL) and not IsUnitType(targ, UNIT_TYPE_STRUCTURE) then
            set healAmount = (8.0 + d.lvl * 8.0) / 3.0
            call SetUnitState(targ, UNIT_STATE_LIFE, GetUnitState(targ, UNIT_STATE_LIFE) + healAmount)
        endif
    endif
 
    set targ = null
    return false
endfunction

private function Effects takes nothing returns boolean
    local trigger trg = GetTriggeringTrigger()
    local data d = GetTriggerData(trg)
    local group g
    local real manaCost

    // If the struct is already processing destruction, stop executing.
    if not d.active then
        set trg = null
        return false
    endif

    // If the unit is given a new order while the spell is active
    if GetTriggerEventId() == EVENT_UNIT_ISSUED_ORDER then
        // 852178 is the order ID for "unimmolation" (turning it off)
        if GetIssuedOrderId() == 852178 then
            set d.active = false
            call d.destroy()
        endif
    else
        // Check if the caster died. If so, shut down the timer immediately!
        if GetWidgetLife(d.u) < 0.405 then
            set d.active = false
            call d.destroy()
            set trg = null
            return false
        endif

        // Periodic execution (every 0.33 seconds)
        set g = CreateGroup()
        set TempStructBridge = d
        call GroupEnumUnitsInRange(g, GetUnitX(d.u), GetUnitY(d.u), 350.0, Condition(function Filt))
        call DestroyGroup(g)
        set g = null
     
        // Mana drain calculation
        set manaCost = (d.lvl * 6.0 + 2.0) / 3.0
        call SetUnitState(d.u, UNIT_STATE_MANA, GetUnitState(d.u, UNIT_STATE_MANA) - manaCost)
     
        // If the caster runs out of mana, force the ability to turn off
        if GetUnitState(d.u, UNIT_STATE_MANA) < manaCost then
            set d.active = false
            // The order below fires synchronously, but d.active is now false, protecting the struct!
            call IssueImmediateOrder(d.u, "unimmolation")
            call d.destroy()
        endif
    endif
 
    set trg = null
    return false
endfunction

private function Actions takes nothing returns nothing
    local data d = data.create()
 
    set d.u = GetTriggerUnit()
    set d.p = GetOwningPlayer(d.u)
    set d.lvl = GetUnitAbilityLevel(d.u, AbilId)
    set d.t = CreateTrigger()
    set d.active = true // Initialize safety valve
 
    // Register the periodic interval loop and the turn-off order event
    call TriggerRegisterTimerEvent(d.t, 0.33, true)
    call TriggerRegisterUnitEvent(d.t, d.u, EVENT_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(d.t, Condition(function Effects))
    call SetTriggerData(d.t, d)
endfunction

private function Conds takes nothing returns boolean
    // 852177 is the engine order ID for "immolation" (turning it on)
    if GetIssuedOrderId() == 852177 and GetUnitAbilityLevel(GetTriggerUnit(), AbilId) > 0 then
        call Actions()
    endif
    return false
endfunction

private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    // Listens for players turning on toggle abilities
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(t, Condition(function Conds))
    set t = null
endfunction

endscope
 
Probably memory corruption if the hero dies, leaving the timer running forever... also I'm guessing that the lack of mana is forcing it off. I added a boolean check, try with the updated code:

Code:
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope VoodooRestoration initializer Init

globals
    private constant integer AbilId = 'A0MT' // Rawcode of your custom Immolation
    private constant integer BuffId = 'B08E' // Rawcode of your custom Buff
 
    // Global variable used to bridge data over to the Group Filter
    private integer TempStructBridge = 0
endglobals

private struct data
    unit u
    player p
    integer lvl
    trigger t
    boolean active // Safety valve to prevent double-free corruption
 
    method onDestroy takes nothing returns nothing
        call DestroyTrigger(.t)
        set .t = null
        set .u = null
        set .p = null
    endmethod
endstruct

private function Filt takes nothing returns boolean
    local data d = TempStructBridge
    local unit targ = GetFilterUnit()
    local real healAmount
 
    // Filter: Is an ally, is alive, is NOT mechanical, and is NOT a structure
    if not IsUnitEnemy(targ, d.p) and GetWidgetLife(targ) > 0.405 then
        if not IsUnitType(targ, UNIT_TYPE_MECHANICAL) and not IsUnitType(targ, UNIT_TYPE_STRUCTURE) then
            set healAmount = (8.0 + d.lvl * 8.0) / 3.0
            call SetUnitState(targ, UNIT_STATE_LIFE, GetUnitState(targ, UNIT_STATE_LIFE) + healAmount)
        endif
    endif
 
    set targ = null
    return false
endfunction

private function Effects takes nothing returns boolean
    local trigger trg = GetTriggeringTrigger()
    local data d = GetTriggerData(trg)
    local group g
    local real manaCost

    // If the struct is already processing destruction, stop executing.
    if not d.active then
        set trg = null
        return false
    endif

    // If the unit is given a new order while the spell is active
    if GetTriggerEventId() == EVENT_UNIT_ISSUED_ORDER then
        // 852178 is the order ID for "unimmolation" (turning it off)
        if GetIssuedOrderId() == 852178 then
            set d.active = false
            call d.destroy()
        endif
    else
        // Check if the caster died. If so, shut down the timer immediately!
        if GetWidgetLife(d.u) < 0.405 then
            set d.active = false
            call d.destroy()
            set trg = null
            return false
        endif

        // Periodic execution (every 0.33 seconds)
        set g = CreateGroup()
        set TempStructBridge = d
        call GroupEnumUnitsInRange(g, GetUnitX(d.u), GetUnitY(d.u), 350.0, Condition(function Filt))
        call DestroyGroup(g)
        set g = null
    
        // Mana drain calculation
        set manaCost = (d.lvl * 6.0 + 2.0) / 3.0
        call SetUnitState(d.u, UNIT_STATE_MANA, GetUnitState(d.u, UNIT_STATE_MANA) - manaCost)
    
        // If the caster runs out of mana, force the ability to turn off
        if GetUnitState(d.u, UNIT_STATE_MANA) < manaCost then
            set d.active = false
            // The order below fires synchronously, but d.active is now false, protecting the struct!
            call IssueImmediateOrder(d.u, "unimmolation")
            call d.destroy()
        endif
    endif
 
    set trg = null
    return false
endfunction

private function Actions takes nothing returns nothing
    local data d = data.create()
 
    set d.u = GetTriggerUnit()
    set d.p = GetOwningPlayer(d.u)
    set d.lvl = GetUnitAbilityLevel(d.u, AbilId)
    set d.t = CreateTrigger()
    set d.active = true // Initialize safety valve
 
    // Register the periodic interval loop and the turn-off order event
    call TriggerRegisterTimerEvent(d.t, 0.33, true)
    call TriggerRegisterUnitEvent(d.t, d.u, EVENT_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(d.t, Condition(function Effects))
    call SetTriggerData(d.t, d)
endfunction

private function Conds takes nothing returns boolean
    // 852177 is the engine order ID for "immolation" (turning it on)
    if GetIssuedOrderId() == 852177 and GetUnitAbilityLevel(GetTriggerUnit(), AbilId) > 0 then
        call Actions()
    endif
    return false
endfunction

private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    // Listens for players turning on toggle abilities
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(t, Condition(function Conds))
    set t = null
endfunction

endscope
Still the same problem... :( Also it seems like the hero with the ability is getting slightly increased hp regeneration after using this ability the first time :-/
 
Still the same problem... :( Also it seems like the hero with the ability is getting slightly increased hp regeneration after using this ability the first time :-/
I stripped the system down completely..... the trigger-per-cast architecture combined with repeating timer events and toggle orders. Every activation could leave orphaned periodic loops running, try this version:


Code:
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope VoodooRestoration initializer Init

globals
    private constant integer ABIL_ID = 'A0MT'

    // Orders
    private constant integer ORDER_IMMOLATION   = 852177
    private constant integer ORDER_UNIMMOLATION = 852178

    // Config
    private constant real PERIOD = 0.33
    private constant real AOE = 350.0

    // Active casters
    private group ActiveGroup = CreateGroup()

    // Temp globals for enum
    private unit TempCaster = null
    private player TempPlayer = null
    private integer TempLevel = 0
endglobals

//===========================================================================
// Heal filter
//===========================================================================
private function HealFilter takes nothing returns boolean
    local unit u = GetFilterUnit()
    local real healAmount

    if not IsUnitEnemy(u, TempPlayer) and GetWidgetLife(u) > 0.405 then

        // Organic only
        if not IsUnitType(u, UNIT_TYPE_MECHANICAL) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then

            set healAmount = (8.0 + TempLevel * 8.0) * PERIOD

            call SetUnitState(
                u,
                UNIT_STATE_LIFE,
                GetUnitState(u, UNIT_STATE_LIFE) + healAmount
            )
        endif
    endif

    set u = null
    return false
endfunction

//===========================================================================
// Main periodic loop
//===========================================================================
private function Periodic takes nothing returns nothing
    local group g = CreateGroup()
    local group enumGroup
    local unit u
    local real manaCost

    // Copy active casters
    call GroupAddGroup(ActiveGroup, g)

    loop
        set u = FirstOfGroup(g)
        exitwhen u == null

        call GroupRemoveUnit(g, u)

        // Dead or ability removed
        if GetWidgetLife(u) <= 0.405 or GetUnitAbilityLevel(u, ABIL_ID) == 0 then
            call GroupRemoveUnit(ActiveGroup, u)

        else
            set TempCaster = u
            set TempPlayer = GetOwningPlayer(u)
            set TempLevel = GetUnitAbilityLevel(u, ABIL_ID)

            // Mana drain
            set manaCost = (TempLevel * 6.0 + 2.0) * PERIOD

            // Not enough mana -> shut off
            if GetUnitState(u, UNIT_STATE_MANA) < manaCost then
                call IssueImmediateOrderById(u, ORDER_UNIMMOLATION)
                call GroupRemoveUnit(ActiveGroup, u)

            else
                call SetUnitState(
                    u,
                    UNIT_STATE_MANA,
                    GetUnitState(u, UNIT_STATE_MANA) - manaCost
                )

                // Heal nearby allies
                set enumGroup = CreateGroup()

                call GroupEnumUnitsInRange(
                    enumGroup,
                    GetUnitX(u),
                    GetUnitY(u),
                    AOE,
                    Condition(function HealFilter)
                )

                call DestroyGroup(enumGroup)
                set enumGroup = null
            endif
        endif
    endloop

    call DestroyGroup(g)

    set g = null
    set u = null
    set TempCaster = null
    set TempPlayer = null
endfunction

//===========================================================================
// Detect toggle ON/OFF
//===========================================================================
private function Orders takes nothing returns boolean
    local unit u = GetTriggerUnit()
    local integer orderId = GetIssuedOrderId()

    if GetUnitAbilityLevel(u, ABIL_ID) > 0 then

        // Turn ON
        if orderId == ORDER_IMMOLATION then
            call GroupAddUnit(ActiveGroup, u)

        // Turn OFF
        elseif orderId == ORDER_UNIMMOLATION then
            call GroupRemoveUnit(ActiveGroup, u)
        endif
    endif

    set u = null
    return false
endfunction

//===========================================================================
// Init
//===========================================================================
private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()

    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(t, Condition(function Orders))

    call TimerStart(CreateTimer(), PERIOD, true, function Periodic)

    set t = null
endfunction

endscope
 
I stripped the system down completely..... the trigger-per-cast architecture combined with repeating timer events and toggle orders. Every activation could leave orphaned periodic loops running, try this version:


Code:
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope VoodooRestoration initializer Init

globals
    private constant integer ABIL_ID = 'A0MT'

    // Orders
    private constant integer ORDER_IMMOLATION   = 852177
    private constant integer ORDER_UNIMMOLATION = 852178

    // Config
    private constant real PERIOD = 0.33
    private constant real AOE = 350.0

    // Active casters
    private group ActiveGroup = CreateGroup()

    // Temp globals for enum
    private unit TempCaster = null
    private player TempPlayer = null
    private integer TempLevel = 0
endglobals

//===========================================================================
// Heal filter
//===========================================================================
private function HealFilter takes nothing returns boolean
    local unit u = GetFilterUnit()
    local real healAmount

    if not IsUnitEnemy(u, TempPlayer) and GetWidgetLife(u) > 0.405 then

        // Organic only
        if not IsUnitType(u, UNIT_TYPE_MECHANICAL) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then

            set healAmount = (8.0 + TempLevel * 8.0) * PERIOD

            call SetUnitState(
                u,
                UNIT_STATE_LIFE,
                GetUnitState(u, UNIT_STATE_LIFE) + healAmount
            )
        endif
    endif

    set u = null
    return false
endfunction

//===========================================================================
// Main periodic loop
//===========================================================================
private function Periodic takes nothing returns nothing
    local group g = CreateGroup()
    local group enumGroup
    local unit u
    local real manaCost

    // Copy active casters
    call GroupAddGroup(ActiveGroup, g)

    loop
        set u = FirstOfGroup(g)
        exitwhen u == null

        call GroupRemoveUnit(g, u)

        // Dead or ability removed
        if GetWidgetLife(u) <= 0.405 or GetUnitAbilityLevel(u, ABIL_ID) == 0 then
            call GroupRemoveUnit(ActiveGroup, u)

        else
            set TempCaster = u
            set TempPlayer = GetOwningPlayer(u)
            set TempLevel = GetUnitAbilityLevel(u, ABIL_ID)

            // Mana drain
            set manaCost = (TempLevel * 6.0 + 2.0) * PERIOD

            // Not enough mana -> shut off
            if GetUnitState(u, UNIT_STATE_MANA) < manaCost then
                call IssueImmediateOrderById(u, ORDER_UNIMMOLATION)
                call GroupRemoveUnit(ActiveGroup, u)

            else
                call SetUnitState(
                    u,
                    UNIT_STATE_MANA,
                    GetUnitState(u, UNIT_STATE_MANA) - manaCost
                )

                // Heal nearby allies
                set enumGroup = CreateGroup()

                call GroupEnumUnitsInRange(
                    enumGroup,
                    GetUnitX(u),
                    GetUnitY(u),
                    AOE,
                    Condition(function HealFilter)
                )

                call DestroyGroup(enumGroup)
                set enumGroup = null
            endif
        endif
    endloop

    call DestroyGroup(g)

    set g = null
    set u = null
    set TempCaster = null
    set TempPlayer = null
endfunction

//===========================================================================
// Detect toggle ON/OFF
//===========================================================================
private function Orders takes nothing returns boolean
    local unit u = GetTriggerUnit()
    local integer orderId = GetIssuedOrderId()

    if GetUnitAbilityLevel(u, ABIL_ID) > 0 then

        // Turn ON
        if orderId == ORDER_IMMOLATION then
            call GroupAddUnit(ActiveGroup, u)

        // Turn OFF
        elseif orderId == ORDER_UNIMMOLATION then
            call GroupRemoveUnit(ActiveGroup, u)
        endif
    endif

    set u = null
    return false
endfunction

//===========================================================================
// Init
//===========================================================================
private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()

    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(t, Condition(function Orders))

    call TimerStart(CreateTimer(), PERIOD, true, function Periodic)

    set t = null
endfunction

endscope
Hmm I unfortantly got this error... Maybe it's just a little mistake?
error.webp
 
Back
Top