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

Dummy Caster is not disappearing

Level 24
Joined
Jun 26, 2020
Messages
1,852
Hello I made a Dummy Cast system in Lua, it cast an spell and is recycled 1 second after it ends the spell or didn't issued the order, it works fine:
Lua:
if Debug then Debug.beginFile("DummyCaster") end
OnInit("DummyCaster", function ()
    Require "WorldBounds"
    Require "Timed"

    -- System based on MUI DummyCaster

    -- Import the dummy from the object editor
    local DummyID = FourCC('n000')
    local MAX_DUMMIES = 99

    -- WARNING: Do not touch anything below this line!

    -- Default 3 values you may use, pick one as desired
    ---@enum CastType
    CastType = {
        IMMEDIATE = 0,
        POINT = 1,
        TARGET = 2
    }

    local Dummies = {}
    local Recycled = __jarray(false)
    local Abilities = __jarray(0)
    local Neutral = Player(PLAYER_NEUTRAL_PASSIVE)

    local function GetDummy(player, x, y, angle)
        local dummy = table.remove(Dummies)
        if not dummy then
            dummy = CreateUnit(player, DummyID, x, y, angle)
        else
            ShowUnitShow(dummy)
            SetUnitOwner(dummy, player, false)
            SetUnitPosition(dummy, x, y)
            BlzSetUnitFacingEx(dummy, angle)
        end
        Recycled[dummy] = false
        return dummy
    end

    local function RefreshDummy(dummy)
        if Recycled[dummy] then
            return
        end
        Recycled[dummy] = true
        if #Dummies == MAX_DUMMIES then
            RemoveUnit(dummy)
        else
            SetUnitOwner(dummy, Neutral, false)
            ShowUnitHide(dummy)
            SetUnitPosition(dummy, WorldBounds.maxX, WorldBounds.maxY)
            UnitRemoveAbility(dummy, Abilities[dummy])
            Abilities[dummy] = nil
            table.insert(Dummies, dummy)
        end
    end

    ---Casts a spell from a dummy caster, returns if the spell was successfully casted
    ---@param owner player
    ---@param x number
    ---@param y number
    ---@param abilId integer
    ---@param orderId integer
    ---@param level integer
    ---@param castType CastType
    ---@param tx? number | unit
    ---@param ty? number
    ---@return boolean
    function DummyCast(owner, x, y, abilId, orderId, level, castType, tx, ty)
        local angle = 0
        if castType == CastType.IMMEDIATE then
            if tx then
                error("Too much arguments", 2)
            end
        elseif castType == CastType.POINT then
            if not tx or not ty then
                error("You didn't set a target point", 2)
            elseif not type(tx) == "number" or not type(ty) == "number" then
                error("Invalid target", 2)
            end
            angle = math.atan(ty - y, tx - x)
        elseif castType == CastType.TARGET then
            if Wc3Type(tx) ~= "unit" then
                error("Invalid target", 2)
            end
            angle = math.atan(GetUnitX(tx) - y, GetUnitX(tx) - x)
        else
            error("Invalid target-type", 2)
        end
        local dummy = GetDummy(owner, x, y, angle)
        UnitAddAbility(dummy, abilId)
        SetUnitAbilityLevel(dummy, abilId, level)
        Abilities[dummy] = abilId
        local success = false
        if castType == CastType.IMMEDIATE then
            success = IssueImmediateOrderById(dummy, orderId)
        elseif castType == CastType.POINT then
            success = IssuePointOrderById(dummy, orderId, tx, ty)
        elseif castType == CastType.TARGET then
            success = IssueTargetOrderById(dummy, orderId, tx)
        end
        if not success then
            Timed.call(1., function ()
                RefreshDummy(dummy)
            end)
        end
        return success
    end

    local t = CreateTrigger()
    TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
    TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_FINISH)
    TriggerAddAction(t, function ()
        if GetUnitTypeId(GetSpellAbilityUnit()) == DummyID then
            local u = GetSpellAbilityUnit()
            Timed.call(1., function ()
                RefreshDummy(u)
            end)
        end
    end)
end)
if Debug then Debug.endFile() end
But sometimes the dummy is not being recycled and stays there (and I don't why it appears in the minimap when I set the option of not appearing in the minimap to true), this happens when I'm emulating the cluster rockets spell:

Lua:
OnInit(function ()
    Require "AbilityUtils"

    local Spell = FourCC('A05E')
    local StrDmgFactor = 0.1
    local AgiDmgFactor = 0.65
    local IntDmgFactor = 0.65
    local AttackFactor = 1.3
    local MissieModel = "Abilities\\Weapons\\HarpyMissile\\HarpyMissile.mdl"
    -- The same as it is in the object editor
    local Area = 100.
    local Order = Orders.clusterrockets

    RegisterSpellEffectEvent(Spell, function ()
        local caster = GetSpellAbilityUnit()
        local owner = GetOwningPlayer(caster)
        local cx = GetUnitX(caster)
        local cy = GetUnitY(caster)
        local x = GetSpellTargetX()
        local y = GetSpellTargetY()
        -- Calculating the damage
        local damage = (GetAttributeDamage(caster, StrDmgFactor, AgiDmgFactor, IntDmgFactor) +
                       GetAvarageAttack(caster) * AttackFactor)
        -- --
        damage = damage / 4
        local counter = 8
        Timed.echo(function ()
            if counter == 0 or GetUnitCurrentOrder(caster) ~= Order then return true end
            local angle = 2 * math.pi * math.random()
            local dist = Area * math.random()
            local tx = x + dist * math.cos(angle)
            local ty = y + dist * math.sin(angle)
            local missile = Missiles:create(cx, cy, 150, tx, ty, 0)
            missile.source = caster
            missile.owner = owner
            missile.damage = damage
            missile:scale(0.5)
            missile:model(MissieModel)
            missile:speed(700.)
            missile:arc(30.)
            missile.onFinish = function ()
                ForUnitsInRange(missile.x, missile.y, Area, function (u)
                    if IsUnitEnemy(u, missile.owner) then
                        Damage.apply(caster, u, damage, true, false, udg_Air, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
                        -- Stun
                        DummyCast(
                            owner,
                            GetUnitX(caster), GetUnitY(caster),
                            STUN_SPELL,
                            STUN_ORDER,
                            2,
                            CastType.TARGET,
                            u
                        )
                    end
                end)
            end
            missile:launch()
            counter = counter - 1
        end, 0.125)
    end)

end)
And in case you are wondering, the problem is not the missile system, I checked that is the dummy caster, what's wrong?
 
Level 24
Joined
Jun 26, 2020
Messages
1,852
Question, is there a delay between the caster issues the order the cast the spell and when the unit actually starts to cast the spell even if is already prepared for cast (no need to move or facing the target first and the cast animation time is 0)? Because my theory is the target could die between that delay, so it would be a valid target to issue the order and then the caster doesn't get recycled immediatly, but when it ends the spell, but since the target died the spell can't be casted and then won't end, so it won't be recycled.
 
Level 24
Joined
Jun 26, 2020
Messages
1,852
At the end what I did is checking if the caster finished the order instead of ends the spell (I hope this solved the problem):
Lua:
if Debug then Debug.beginFile("DummyCaster") end
OnInit("DummyCaster", function ()
    Require "WorldBounds"
    Require "Timed"

    -- System based on MUI DummyCaster

    -- Import the dummy from the object editor
    local DummyID = FourCC('n000')
    local MAX_DUMMIES = 99

    -- WARNING: Do not touch anything below this line!

    -- Default 3 values you may use, pick one as desired
    ---@enum CastType
    CastType = {
        IMMEDIATE = 0,
        POINT = 1,
        TARGET = 2
    }

    local Dummies = {}
    local Recycled = __jarray(false)
    local Abilities = __jarray(0)
    local Neutral = Player(PLAYER_NEUTRAL_PASSIVE)

    local function GetDummy(player, x, y, angle)
        local dummy = table.remove(Dummies)

        if not dummy then
            dummy = CreateUnit(player, DummyID, x, y, angle)
        else
            ShowUnitShow(dummy)
            SetUnitOwner(dummy, player, false)
            SetUnitPosition(dummy, x, y)
            BlzSetUnitFacingEx(dummy, angle)
        end

        Recycled[dummy] = false
        return dummy
    end

    local function RefreshDummy(dummy)
        if Recycled[dummy] then
            return
        end

        Recycled[dummy] = true

        if #Dummies == MAX_DUMMIES then
            RemoveUnit(dummy)
        else
            SetUnitOwner(dummy, Neutral, false)
            ShowUnitHide(dummy)
            SetUnitPosition(dummy, WorldBounds.maxX, WorldBounds.maxY)
            UnitRemoveAbility(dummy, Abilities[dummy])
            Abilities[dummy] = nil
            table.insert(Dummies, dummy)
        end
    end

    ---Casts a spell from a dummy caster, returns if the spell was successfully casted
    ---@param owner player
    ---@param x number
    ---@param y number
    ---@param abilId integer
    ---@param orderId integer
    ---@param level integer
    ---@param castType CastType
    ---@param tx? number | unit
    ---@param ty? number
    ---@return boolean
    function DummyCast(owner, x, y, abilId, orderId, level, castType, tx, ty)
        local angle = 0
        if castType == CastType.IMMEDIATE then
            if tx then
                error("Too much arguments", 2)
            end
        elseif castType == CastType.POINT then
            if not tx or not ty then
                error("You didn't set a target point", 2)
            elseif not type(tx) == "number" or not type(ty) == "number" then
                error("Invalid target", 2)
            end
            angle = math.atan(ty - y, tx - x)
        elseif castType == CastType.TARGET then
            if Wc3Type(tx) ~= "unit" then
                error("Invalid target", 2)
            end
            angle = math.atan(GetUnitX(tx) - y, GetUnitX(tx) - x)
        else
            error("Invalid target-type", 2)
        end

        local dummy = GetDummy(owner, x, y, angle)
        UnitAddAbility(dummy, abilId)
        SetUnitAbilityLevel(dummy, abilId, level)
        Abilities[dummy] = abilId

        local success = false
        if castType == CastType.IMMEDIATE then
            success = IssueImmediateOrderById(dummy, orderId)
        elseif castType == CastType.POINT then
            success = IssuePointOrderById(dummy, orderId, tx, ty)
        elseif castType == CastType.TARGET then
            success = IssueTargetOrderById(dummy, orderId, tx)
        end

        if not success then
            RefreshDummy(dummy)
        else
            Timed.echo(0.02, function ()
                if GetUnitCurrentOrder(dummy) ~= orderId then
                    Timed.call(1., function ()
                        RefreshDummy(dummy)
                    end)
                    return true
                end
            end)
        end

        return success
    end
end)
if Debug then Debug.endFile() end
 
Top