• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Lua] Spells can't cast after a while

Status
Not open for further replies.
Level 24
Joined
Jun 26, 2020
Messages
1,852
I'm trying to create a boss fight and first I have to create its spells, but the problem is for some reason after various casts the spells are not casted, I don't even get an error message so I don't know what's going on, this are the spells:
Scorching heat
Lua:
do
    local SPELL = FourCC('A001')
    local DURATION = 10. -- seconds
    local DAMAGE = 25. -- per second
    local AREA = 250.
    local INTERVAL = 0.03125
    local DMG_PER_TICK = DAMAGE * INTERVAL

    RegisterSpellEffectEvent(SPELL, function ()
        local caster = GetSpellAbilityUnit()
        local eff = AddSpecialEffectTarget("Abilities\\Spells\\Other\\Doom\\DoomTarget.mdl", caster, "origin")
        BlzSetSpecialEffectScale(eff, 2.)
        Timed.echo(function (node)
            if node.elapsed < DURATION then
                ForUnitsInRange(GetUnitX(caster), GetUnitY(caster), AREA, function (u)
                    if IsUnitEnemy(caster, GetOwningPlayer(u)) then
                        UnitDamageTarget(caster, u, DMG_PER_TICK, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS)
                        DestroyEffect(AddSpecialEffectTarget("Abilities\\Weapons\\LordofFlameMissile\\LordofFlameMissile.mdl", u, "chest"))
                    end
                end)
            else
                BlzSetSpecialEffectScale(eff, 0.01)
                DestroyEffect(eff)
                return true
            end
        end, INTERVAL)
    end)
end
Lava explosions
Lua:
do
    local SPELL = FourCC('A002')
    local DURATION = 10. -- seconds
    local DAMAGE = 100. -- per explosion
    local MIN_DIST = 128.
    local AREA = 500.
    local AREA_EXP = 150.
    local INTERVAL = 0.5

    RegisterSpellEffectEvent(SPELL, function ()
        print("LE")
        local caster = GetSpellAbilityUnit()
        Timed.echo(function (node)
            for _ = 1, GetRandomInt(1, 4) do
                local angle = GetRandomReal(0, 2*bj_PI)
                local dist = GetRandomReal(MIN_DIST, AREA)
                local x = GetUnitX(caster) + dist * Cos(angle)
                local y = GetUnitY(caster) + dist * Sin(angle)
    
                DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\Volcano\\VolcanoMissile.mdl", x, y))
                DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\Volcano\\VolcanoDeath.mdl", x, y))
    
                ForUnitsInRange(x, y, AREA_EXP, function (u)
                    if IsUnitEnemy(caster, GetOwningPlayer(u)) then
                        UnitDamageTarget(caster, u, DAMAGE, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS)
                    end
                end)
            end

            return node.elapsed > DURATION
        end, INTERVAL)
    end)
end
Fire ball attack
Lua:
do
    local SPELL = FourCC('A003')
    local RANGE = 900. -- The same as in the object editor
    local DAMAGE = 10. -- per tick
    local AREA = 128.

    RegisterSpellEffectEvent(SPELL, function ()
        local caster = GetSpellAbilityUnit()
        local x = GetUnitX(caster)
        local y = GetUnitY(caster)
        local tx = GetSpellTargetX()
        local ty = GetSpellTargetY()
        local angle = Atan2(ty - y, tx - x)
        local missile = Missiles:create(x, y, 100., x + RANGE * Cos(angle), y + RANGE * Sin(angle), 100.)
        missile:model("Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl")
        missile:speed(500.)
        missile:scale(2.)
        missile.source = caster
        missile.owner = GetOwningPlayer(caster)
        missile.collision = AREA
        missile.onHit = function (u)
            missile:flush(u)
            if IsUnitEnemy(caster, GetOwningPlayer(u)) then
                UnitDamageTarget(caster, u, DAMAGE, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS)
            end
        end
        missile:launch()
    end)

end
Also I send you a map to you can test them.
 

Attachments

  • BossFight.w3m
    124.2 KB · Views: 20
Building off of Uncle's and Pyro's observations, perhaps the triggers that are supposed to respond to the events are created on Lua root, which doesn't play too well with the garbage collector. If the triggers are the eyes and the event listeners the brains of the operation, then upon garbage collection, the eyes are gouged out, preventing the brain/s (event listeners) from processing information that would otherwise be relayed by the eyes (triggers).

Still, if further debugging is needed, you can use the following function: xpcall(func, print[, ...])
 
Level 24
Joined
Jun 26, 2020
Messages
1,852
Perhaps an issue with this custom function:
Lua:
RegisterSpellEffectEvent()
I though that, but don't see something weird there:
Lua:
--Lua RegisterSpellEffectEvent
do
    local tb =  {}
    local trig = nil
  
    function RegisterSpellEffectEvent(abil, onCast)
        if not trig then
            trig = CreateTrigger()
            TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            TriggerAddCondition(trig, Condition(function()
                local i = tb[GetSpellAbilityId()]
                if i then i() end
            end))
        end
        tb[abil] = onCast
    end
end
  
--syntax is:
--RegisterSpellEffectEvent(FourCC('Abil'), function() print(GetUnitName(GetTriggerUnit())) end)
@Uncle, @Pyrogasm if is the garbage collector, what can I do?
Still, if further debugging is needed, you can use the following function: xpcall(func, print[, ...])
I tried that, but as I said I don't get an error message, I even added a print at the top of the functions, but they are not called.

I added a lot of systems to my map, so I can't tell if some of them has to do with the problem.
 
Building off of Uncle's and Pyro's observations, perhaps the triggers that are supposed to respond to the events are created on Lua root, which doesn't play too well with the garbage collector.
Based on the code posted above and the following snippet:
Lua:
--Lua RegisterSpellEffectEvent
do
    local tb = {}
    local trig = nil
 
    function RegisterSpellEffectEvent(abil, onCast)
        if not trig then
            trig = CreateTrigger()
            TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            TriggerAddCondition(trig, Condition(function()
               local i = tb[GetSpellAbilityId()]
               if i then i() end
            end))
        end
        tb[abil] = onCast
    end
end

--syntax is:
--RegisterSpellEffectEvent(FourCC('Abil'), function() print(GetUnitName(GetTriggerUnit())) end)
The trigger handle created in RegisterSpellEffectEvent is created in the Lua root (before map initialization for simplicity's sake). Certain handles created before map initialization might be garbage collected by accident. To address this, you will have to create the trigger handle during map initialization. You'll also have to refactor the function so that you can still register callback functions to RegisterSpellEffectEvent in the Lua root.

The following snippet may help you in this endeavor: [Lua] - Global Initialization

After pasting the snippet into your map (make sure it's on top of everything else), here's the refactored version of the RegisterSpellEffectEvent function (there might be some logical errors).

Lua:
do
    local tb = {}
    local polledArgs = {abil={}, onCast={}, initialized=false,}
    local trig = nil

    local function pollSpellEffectEvent(abil, onCast)
        polledArgs[#polledArgs + 1] = true
        polledArgs.abil[#polledArgs] = abil
        polledArgs.onCast[#polledArgs] = onCast
    end

    --@param abil: integer (raw-code)
    --@param onCast: function
    function RegisterSpellEffectEvent(abil, onCast)
        if (not polledArgs.initialized) then
            pollSpellEffectEvent(abil, onCast)
            return
        end
        tb[abil] = onCast
    end

    --- This initializing function will create the trigger at
    --  Map Initialization point so that it will not be liable
    --  for garbage collection.
    OnGlobalInit(function()
        trig = CreateTrigger()
        TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        TriggerAddCondition(trig, Condition(function()
           local i = tb[GetSpellAbilityId()]
           if i then i() end
        end))

        polledArgs.initialized = true
        -- just in case this loop doesn't work as intended,
        -- check the next one
        for i, _ in ipairs(polledArgs) do
             tb[polledArgs.abil[i]] = polledArgs.onCast[i]
        end
        --[[
        local i = 1
        while i < #polledArgs do
             tb[polledArgs.abil[i] ] = polledArgs.onCast[i]
             i = i + 1
        end
        ]]
    end)
end
 
Last edited:
Level 24
Joined
Jun 26, 2020
Messages
1,852
Based on the code posted above and the following snippet:

The trigger handle created in RegisterSpellEffectEvent is created in the Lua root (before map initialization for simplicity's sake). Certain handles created before map initialization might be garbage collected by accident. To address this, you will have to create the trigger handle during map initialization. You'll also have to refactor the function so that you can still register callback functions to RegisterSpellEffectEvent in the Lua root.

The following snippet may help you in this endeavor: [Lua] - Global Initialization

After pasting the snippet into your map (make sure it's on top of everything else), here's the refactored version of the RegisterSpellEffectEvent function (there might be some logical errors).

Lua:
do
    local tb = {}
    local polledArgs = {abil={}, onCast={}, initialized=false,}
    local trig = nil

    local function pollSpellEffectEvent(abil, onCast)
        polledArgs[#polledArgs + 1] = true
        polledArgs.abil[#polledArgs] = abil
        polledArgs.onCast[#polledArgs] = onCast
    end

    --@param abil: integer (raw-code)
    --@param onCast: function
    function RegisterSpellEffectEvent(abil, onCast)
        if (not polledArgs.initialized) then
            pollSpellEffectEvent(abil, onCast)
            return
        end
        tb[abil] = onCast
    end

    --- This initializing function will create the trigger at
    --  Map Initialization point so that it will not be liable
    --  for garbage collection.
    OnGlobalInit(function()
        trig = CreateTrigger()
        TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        TriggerAddCondition(trig, Condition(function()
           local i = tb[GetSpellAbilityId()]
           if i then i() end
        end))

        polledArgs.initialized = true
        -- just in case this loop doesn't work as intended,
        -- check the next one
        for i, _ in ipairs(polledArgs) do
             tb[polledArgs.abil[i]] = polledArgs.onCast[i]
        end
        --[[
        local i = 1
        while i < #polledArgs do
             tb[polledArgs.abil[i] ] = polledArgs.onCast[i]
             i = i + 1
        end
        ]]
    end)
end
For now it seems to work, thank you
The following snippet may help you in this endeavor: [Lua] - Global Initialization
I already have it, but I didn't think I would need it here (In fact I have 2 because for some reason one of them under specific conditions make the map doesn't run, I could tell it to @Bribe, but I don't know what's going on).
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,468
I would recommend simply changing your spells to use OnGlobalInit or something as the container, rather than do...end.

Lua:
OnGlobalInit(function()
    local SPELL = FourCC('A001')
    local DURATION = 10. -- seconds
    local DAMAGE = 25. -- per second
    local AREA = 250.
    local INTERVAL = 0.03125
    local DMG_PER_TICK = DAMAGE * INTERVAL

    RegisterSpellEffectEvent(SPELL, function ()
        local caster = GetSpellAbilityUnit()
        local eff = AddSpecialEffectTarget("Abilities\\Spells\\Other\\Doom\\DoomTarget.mdl", caster, "origin")
        BlzSetSpecialEffectScale(eff, 2.)
        Timed.echo(function (node)
            if node.elapsed < DURATION then
                ForUnitsInRange(GetUnitX(caster), GetUnitY(caster), AREA, function (u)
                    if IsUnitEnemy(caster, GetOwningPlayer(u)) then
                        UnitDamageTarget(caster, u, DMG_PER_TICK, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS)
                        DestroyEffect(AddSpecialEffectTarget("Abilities\\Weapons\\LordofFlameMissile\\LordofFlameMissile.mdl", u, "chest"))
                    end
                end)
            else
                BlzSetSpecialEffectScale(eff, 0.01)
                DestroyEffect(eff)
                return true
            end
        end, INTERVAL)
    end)
end)

As for code not running, that would have to do with there being actual errors in your initialization code. I will push another update to Global Initialization, however, because currently the debug protocol in "pcall" doesn't seem to be working as intended, and I've found that @Eikonium's "try" method is working much better.
 
Level 24
Joined
Jun 26, 2020
Messages
1,852
I would recommend simply changing your spells to use OnGlobalInit or something as the container, rather than do...end.

Lua:
OnGlobalInit(function()
    local SPELL = FourCC('A001')
    local DURATION = 10. -- seconds
    local DAMAGE = 25. -- per second
    local AREA = 250.
    local INTERVAL = 0.03125
    local DMG_PER_TICK = DAMAGE * INTERVAL

    RegisterSpellEffectEvent(SPELL, function ()
        local caster = GetSpellAbilityUnit()
        local eff = AddSpecialEffectTarget("Abilities\\Spells\\Other\\Doom\\DoomTarget.mdl", caster, "origin")
        BlzSetSpecialEffectScale(eff, 2.)
        Timed.echo(function (node)
            if node.elapsed < DURATION then
                ForUnitsInRange(GetUnitX(caster), GetUnitY(caster), AREA, function (u)
                    if IsUnitEnemy(caster, GetOwningPlayer(u)) then
                        UnitDamageTarget(caster, u, DMG_PER_TICK, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS)
                        DestroyEffect(AddSpecialEffectTarget("Abilities\\Weapons\\LordofFlameMissile\\LordofFlameMissile.mdl", u, "chest"))
                    end
                end)
            else
                BlzSetSpecialEffectScale(eff, 0.01)
                DestroyEffect(eff)
                return true
            end
        end, INTERVAL)
    end)
end)

As for code not running, that would have to do with there being actual errors in your initialization code. I will push another update to Global Initialization, however, because currently the debug protocol in "pcall" doesn't seem to be working as intended, and I've found that @Eikonium's "try" method is working much better.
Thinking well, yes.
 
Status
Not open for further replies.
Top