• 🏆 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] SpecialEffect

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688

Overview

API & Documentation

Code

Example Usage

Credits

Version History

SpecialEffect v1.0.1
Special effect library for Lua


Description

This library provides the SpecialEffect class which allows you to conveniently cluster together multiple effect handles and control it as a single entity.
This is supposed to replace the dummy + sfx method that is widely used before but has become obsolete since patch 1.31 due to the new additions to effect manipulation natives.


Dependencies
  • N/A
Lua:
--[[

    API:

        Constructors:
            SpecialEffect(x, y, z)

        Destructor:
            function SpecialEffect:destroy()

        Getters:
            function SpecialEffect:alive() -> boolean
            function SpecialEffect:visible() -> boolean

            function SpecialEffect:x() -> number
            function SpecialEffect:y() -> number
            function SpecialEffect:z() -> number        - absolute z
            function SpecialEffect:height() -> number   - height relative to ground
            function SpecialEffect:position()           - returns position x, y, & z

            function SpecialEffect:yaw() -> number
            function SpecialEffect:pitch() -> number
            function SpecialEffect:roll() -> number
            function SpecialEffect:orientation()        - returns yaw, pitch, & roll

        Iterators:
            function SpecialEffect:__pairs()
            - Returns an iterator for all effect handles, NOT including ones
              from attached SpecialEffect objects
            - Returns an empty iterator if this SpecialEffect is dead (i.e. has
              been killed using SpecialEffect:kill())

            function SpecialEffect:attachedIter()
            - Returns an iterator for all attached SpecialEffect objects

        Methods:
            function SpecialEffect:add(arg:string|SpecialEffect) -> effect|nil
            - Adds a special effect model to this SpecialEffect object if <arg>
              is a string. Otherwise, it attaches the input SpecialEffect object
              to this one.

            function SpecialEffect:remove(arg:string|SpecialEffect)
            - Removes the special effect model from this SpecialEffect if <arg>
              is a valid model string, else, dettaches the input SpecialEffect
              object from this one.

            function SpecialEffect:scatter(chainScatter:boolean)
            - Dettaches all attached SpecialEffects
            - If <chainScatter> is true, recursively scatters all connected
              SpecialEffect objects

            function SpecialEffect:clear(scatter:boolean, chainClear:boolean)
            - Removes all the special effect models of this SpecialEffect object
            - If <scatter> is true, also scatters all attached SpecialEffects
            - If <chainClear> is true, the clear operation is recursively
              applied to all connected SpecialEffect objects

            function SpecialEffect:kill(deathDuration:number, chainKill:boolean)
            - Plays the death animation of this SpecialEffect and schedules it
              for destruction after <deathDuration> seconds
            - If <chainKill> is true, recursively kills all connected
              SpecialEffects

            function SpecialEffect:move(x:number, y:number, z:number)
            - Moves this SpecialEffect including all attached SpecialEffects
              while keeping their relative spacing from each other
            - All parameters are optional

            function SpecialEffect:orient(yaw:number, pitch:number, roll:number)
            - Changes this SpecialEffect's orientation
            - All parameters are optional

            function SpecialEffect:animate(whichAnim:animtype, timeScale:number, chainAnimate:boolean)
            - Makes this SpecialEffect play the given animation <whichAnim>,
              with an animation speed specified by <timeScale>
            - If <timeScale> is nil, animation plays at default effect time
              scale
            - If <chainAnimate> is true, this method is called recursively for
              all connected SpecialEffect objects

            function SpecialEffect:show(force:boolean)
            function SpecialEffect:hide(force:boolean)
            - Hides/Shows the SpecialEffect object
            - If <force> is false, each call to show/hide increments/decrements
              a visibility flag counter internally, of which a counter value of
              0 causes SpecialEffect object to be hidden
            - If <force> is true, the show/hide command ignores the counter and
              resets its value
            - Can be safely used inside local-code blocks

        Method Interfaces:
            function SpecialEffect:onAttach(to:SpecialEffect)
            - If this method is present, it will be called whenever a
              SpecialEffect is attached into another SpecialEffect using the
              SpecialEffect:add() method
            - <self> is the one being attached and <to> is where <self> is being
              attached to

            function SpecialEffect:onDettach(from:SpecialEffect)
            - If this method is present, it will be called whenever a
              SpecialEffect is dettached from another SpecialEffect using the
              SpecialEffect:remove() method
            - <self> is the one being dettached and <from> is where <self> is
              being dettached from
            - NOTE: Everytime a recursive scatter is performed using one of the
              methods above, the order of scatter operation is always from the
              'parent' SpecialEffect all the way through the 'descendants'

]]--
Lua:
-- Resource Link: https://www.hiveworkshop.com/threads/lua-specialeffect.331757/


---@class SpecialEffect
SpecialEffect = setmetatable({HIDDEN_PLACE_X = 0., HIDDEN_PLACE_Y = 10000.}, {__name = 'SpecialEffect'})

do
    local specialeffect = getmetatable(SpecialEffect)
    specialeffect.__index = specialeffect

    local function is_a(o, class)
        if o and type(o) == 'table' then
            local mt = getmetatable(o)
            while (mt) do
                if mt == class then return true end
                mt = getmetatable(mt)
            end
            return false
        end
    end

    local loc = Location(0., 0.)
    local function get_terrain_z(x, y)
        MoveLocation(loc, x, y)
        return GetLocationZ(loc)
    end

    local function vanish_effect(e)
        BlzSetSpecialEffectX(e, SpecialEffect.HIDDEN_PLACE_X)
        BlzSetSpecialEffectY(e, SpecialEffect.HIDDEN_PLACE_Y)
        DestroyEffect(e)
    end

    -- Constructors/Destructor

    ---@param x number
    ---@param y number
    ---@param z number
    ---@return SpecialEffect
    function specialeffect:__call(x, y, z)
        local e = setmetatable(
            {
                _x = x, _y = y, _z = z,
                _yaw = 0., _pitch = 0., _roll = 0.,
                _visibility_count = 1,
                _alive = true,
                _handles = {}, _attached = {}
            },
            specialeffect
        )

        return e
    end

    function specialeffect:__gc()
        self:destroy()
    end

    -- Gettters

    ---@return boolean
    function specialeffect:alive() return self._alive end

    ---@return boolean
    function specialeffect:visible() return self._visibility_count > 0 end

    ---@return number
    function specialeffect:x() return self._x end
    ---@return number
    function specialeffect:y() return self._y end
    ---@return number
    function specialeffect:z() return self._z end
    ---@return number
    function specialeffect:height() return self._z - get_terrain_z(self._x, self._y) end
    ---@return number, number, number
    function specialeffect:position() return self._x, self._y, self._z end

    ---@return number
    function specialeffect:yaw() return self._yaw end
    ---@return number
    function specialeffect:pitch() return self._pitch end
    ---@return number
    function specialeffect:roll() return self._roll end
    ---@return number, number, number
    function specialeffect:orientation() return self._yaw, self._pitch, self._roll end

    -- Iterators

    local empty_t = {}
    function specialeffect:__pairs()
        return next, self._alive and self._handles or empty_t, nil
    end

    function specialeffect:attachedIter()
        return ipairs(self._attached)
    end

    --- Methods

    ---@param arg string|SpecialEffect
    ---@return effect|nil
    function specialeffect:add(arg)
        if type(arg) == 'string' then
            if self._alive then
                local e = AddSpecialEffect(arg, self._x, self._y)
                BlzSetSpecialEffectPosition(e, self._x, self._y, self._z)
                BlzSetSpecialEffectOrientation(e, self._yaw, self._pitch, self._roll)
                self._handles[arg] = e

                return e
            end

        elseif is_a(arg, specialeffect) then
            if arg.onAttach then
                arg:onAttach(self)
            end

            self._attached[#self._attached + 1] = arg
        end
    end

    ---@param arg string|SpecialEffect
    function specialeffect:remove(arg)
        if type(arg) == 'string' then
            if self._alive then
                local e = self._handles[arg]
                if e then
                    vanish_effect(e)
                end
            end

        elseif is_a(arg, specialeffect) then
            local n = #self._attached
            for i = 1, n do
                if self._attached[i] == arg then
                    if arg.onDettach then
                        arg:onDettach(self)
                    end

                    self._attached[i] = nil
                end
            end
        end
    end

    ---@param chainScatter boolean
    function specialeffect:scatter(chainScatter)
        for i = #self._attached, 1, -1 do
            local a = self._attached[i]

            if a.onDettach then
                a:onDettach(self)
            end
            self._attached[i] = nil

            if chainScatter then
                a:scatter(true)
            end
        end
    end

    ---@param x number
    ---@param y number
    ---@param z number
    ---@param relativeZ boolean
    function specialeffect:move(x, y, z, relativeZ)
        if relativeZ and z then
            z = z + get_terrain_z(x, y)
        end
        x, y, z = x or self._x, y or self._y, z or self._z
        local dx, dy, dz = x - self._x, y - self._y, z - self._z

        if self:visible() then
            for _, e in pairs(self._handles) do
                BlzSetSpecialEffectPosition(e, x, y, z)
            end
        end
        self._x, self._y, self._z = x, y, z

        for _, v in ipairs(self._attached) do
            v:move(v._x + dx, v._y + dy, v._z + dz)
        end
    end

    ---@param yaw number
    ---@param pitch number
    ---@param roll number
    function specialeffect:orient(yaw, pitch, roll)
        yaw, pitch, roll = yaw or self._yaw, pitch or self._pitch, roll or self._roll

        for _, v in pairs(self._handles) do
            BlzSetSpecialEffectOrientation(v, yaw, pitch, roll)
        end

        self._yaw, self._pitch, self._roll = yaw, pitch, roll
    end

    ---@param whichAnim animtype
    ---@param timeScale number
    ---@param chainAnimate boolean
    function specialeffect:animate(whichAnim, timeScale, chainAnimate)
        if self._alive then
            for _, v in pairs(self._handles) do
                if timeScale then
                    BlzPlaySpecialEffectWithTimeScale(v, whichAnim, timeScale)
                else
                    BlzPlaySpecialEffect(v, whichAnim)
                end
            end

            if chainAnimate then
                for _, v in ipairs(self._attached) do
                    v:animate(whichAnim, nil, true)
                end
            end
        end
    end

    ---@param force boolean
    function specialeffect:show(force)
        if force then self._visibility_count = 0 end

        if self._visibility_count == 0 then
            for _, e in pairs(self._handles) do
                BlzSetSpecialEffectPosition(e, self._x, self._y, self._z)
            end
        end

        self._visibility_count = self._visibility_count + 1
    end

    ---@param force boolean
    function specialeffect:hide(force)
        if force then self._visibility_count = 1 end

        self._visibility_count = self._visibility_count - 1

        if self._visibility_count == 0 then
            for _, e in pairs(self._handles) do
                BlzSetSpecialEffectX(e, SpecialEffect.HIDDEN_PLACE_X)
                BlzSetSpecialEffectY(e, SpecialEffect.HIDDEN_PLACE_Y)
            end
        end
    end

    local timer_t = {}
    local function on_kill()
        local t = GetExpiredTimer()
        timer_t[t]:destroy()
        DestroyTimer(t)
    end

    ---@param deathDuration number
    ---@param chainKill boolean
    function specialeffect:kill(deathDuration, chainKill)
        if self._alive then
            self:animate(ANIM_TYPE_DEATH)
            local t = CreateTimer()
            timer_t[t] = self
            TimerStart(t, deathDuration, false, on_kill)

            self._alive = false

            if chainKill then
                for _, v in ipairs(self._attached) do
                    v:kill(deathDuration, true)
                end
            end
        end
    end

    ---@param scatter boolean
    ---@param chainClear boolean
    function specialeffect:clear(scatter, chainClear)
        local t = {table.unpack(self._attached)}

        if scatter then
            self:scatter()
        end
        if self._alive then
            for _, v in pairs(self._handles) do
                vanish_effect(v)
            end
        end

        if chainClear then
            for _, v in ipairs(t) do
                v:clear(scatter, true)
            end
        end
    end

    function specialeffect:destroy()
        self:clear(true, false)
        setmetatable(self, nil)
    end
end
Lua:
< Sample code + Output screenshot will be added soon >
v1.0.1
- Small fixes on the SpecialEffect:add() and SpecialEffect:remove() functions

v1.0.0
- Initial Release






specialeffect.lua
Lua:
--[[ specialeffect.lua v1.0.1 by AGD | https://www.hiveworkshop.com/threads/331757/

    Special effect library for Lua.
    Based on: https://www.hiveworkshop.com/threads/325954/


    Description:

        This library provides the SpecialEffect class which allows you to
        conveniently cluster together multiple effect handles and control it as
        a single entity.
        This is supposed to replace the dummy + sfx method that is widely used
        before but has become obsolete since patch 1.31 due to the new additions
        to effect manipulation natives.


    Requirements:

        - None

]]--
--[[

    API:

        Constructors:
            SpecialEffect(x, y, z)

        Destructor:
            function SpecialEffect:destroy()

        Getters:
            function SpecialEffect:alive() -> boolean
            function SpecialEffect:visible() -> boolean

            function SpecialEffect:x() -> number
            function SpecialEffect:y() -> number
            function SpecialEffect:z() -> number        - absolute z
            function SpecialEffect:height() -> number   - height relative to ground
            function SpecialEffect:position()           - returns position x, y, & z

            function SpecialEffect:yaw() -> number
            function SpecialEffect:pitch() -> number
            function SpecialEffect:roll() -> number
            function SpecialEffect:orientation()        - returns yaw, pitch, & roll

        Iterators:
            function SpecialEffect:__pairs()
            - Returns an iterator for all effect handles, NOT including ones
              from attached SpecialEffect objects
            - Returns an empty iterator if this SpecialEffect is dead (i.e. has
              been killed using SpecialEffect:kill())

            function SpecialEffect:attachedIter()
            - Returns an iterator for all attached SpecialEffect objects

        Methods:
            function SpecialEffect:add(arg:string|SpecialEffect) -> effect|nil
            - Adds a special effect model to this SpecialEffect object if <arg>
              is a string. Otherwise, it attaches the input SpecialEffect object
              to this one.

            function SpecialEffect:remove(arg:string|SpecialEffect)
            - Removes the special effect model from this SpecialEffect if <arg>
              is a valid model string, else, dettaches the input SpecialEffect
              object from this one.

            function SpecialEffect:scatter(chainScatter:boolean)
            - Dettaches all attached SpecialEffects
            - If <chainScatter> is true, recursively scatters all connected
              SpecialEffect objects

            function SpecialEffect:clear(scatter:boolean, chainClear:boolean)
            - Removes all the special effect models of this SpecialEffect object
            - If <scatter> is true, also scatters all attached SpecialEffects
            - If <chainClear> is true, the clear operation is recursively
              applied to all connected SpecialEffect objects

            function SpecialEffect:kill(deathDuration:number, chainKill:boolean)
            - Plays the death animation of this SpecialEffect and schedules it
              for destruction after <deathDuration> seconds
            - If <chainKill> is true, recursively kills all connected
              SpecialEffects

            function SpecialEffect:move(x:number, y:number, z:number)
            - Moves this SpecialEffect including all attached SpecialEffects
              while keeping their relative spacing from each other
            - All parameters are optional

            function SpecialEffect:orient(yaw:number, pitch:number, roll:number)
            - Changes this SpecialEffect's orientation
            - All parameters are optional

            function SpecialEffect:animate(whichAnim:animtype, timeScale:number, chainAnimate:boolean)
            - Makes this SpecialEffect play the given animation <whichAnim>,
              with an animation speed specified by <timeScale>
            - If <timeScale> is nil, animation plays at default effect time
              scale
            - If <chainAnimate> is true, this method is called recursively for
              all connected SpecialEffect objects

            function SpecialEffect:show(force:boolean)
            function SpecialEffect:hide(force:boolean)
            - Hides/Shows the SpecialEffect object
            - If <force> is false, each call to show/hide increments/decrements
              a visibility flag counter internally, of which a counter value of
              0 causes SpecialEffect object to be hidden
            - If <force> is true, the show/hide command ignores the counter and
              resets its value
            - Can be safely used inside local-code blocks

        Method Interfaces:
            function SpecialEffect:onAttach(to:SpecialEffect)
            - If this method is present, it will be called whenever a
              SpecialEffect is attached into another SpecialEffect using the
              SpecialEffect:add() method
            - <self> is the one being attached and <to> is where <self> is being
              attached to

            function SpecialEffect:onDettach(from:SpecialEffect)
            - If this method is present, it will be called whenever a
              SpecialEffect is dettached from another SpecialEffect using the
              SpecialEffect:remove() method
            - <self> is the one being dettached and <from> is where <self> is
              being dettached from
            - NOTE: Everytime a recursive scatter is performed using one of the
              methods above, the order of scatter operation is always from the
              'parent' SpecialEffect all the way through the 'descendants'

]]--

---@class SpecialEffect
SpecialEffect = setmetatable({HIDDEN_PLACE_X = 0., HIDDEN_PLACE_Y = 10000.}, {__name = 'SpecialEffect'})

do
    local specialeffect = getmetatable(SpecialEffect)
    specialeffect.__index = specialeffect

    local function is_a(o, class)
        if o and type(o) == 'table' then
            local mt = getmetatable(o)
            while (mt) do
                if mt == class then return true end
                mt = getmetatable(mt)
            end
            return false
        end
    end

    local loc = Location(0., 0.)
    local function get_terrain_z(x, y)
        MoveLocation(loc, x, y)
        return GetLocationZ(loc)
    end

    local function vanish_effect(e)
        BlzSetSpecialEffectX(e, SpecialEffect.HIDDEN_PLACE_X)
        BlzSetSpecialEffectY(e, SpecialEffect.HIDDEN_PLACE_Y)
        DestroyEffect(e)
    end

    -- Constructors/Destructor

    ---@param x number
    ---@param y number
    ---@param z number
    ---@return SpecialEffect
    function specialeffect:__call(x, y, z)
        local e = setmetatable(
            {
                _x = x, _y = y, _z = z,
                _yaw = 0., _pitch = 0., _roll = 0.,
                _visibility_count = 1,
                _alive = true,
                _handles = {}, _attached = {}
            },
            specialeffect
        )

        return e
    end

    function specialeffect:__gc()
        self:destroy()
    end

    -- Gettters

    ---@return boolean
    function specialeffect:alive() return self._alive end

    ---@return boolean
    function specialeffect:visible() return self._visibility_count > 0 end

    ---@return number
    function specialeffect:x() return self._x end
    ---@return number
    function specialeffect:y() return self._y end
    ---@return number
    function specialeffect:z() return self._z end
    ---@return number
    function specialeffect:height() return self._z - get_terrain_z(self._x, self._y) end
    ---@return number, number, number
    function specialeffect:position() return self._x, self._y, self._z end

    ---@return number
    function specialeffect:yaw() return self._yaw end
    ---@return number
    function specialeffect:pitch() return self._pitch end
    ---@return number
    function specialeffect:roll() return self._roll end
    ---@return number, number, number
    function specialeffect:orientation() return self._yaw, self._pitch, self._roll end

    -- Iterators

    local empty_t = {}
    function specialeffect:__pairs()
        return next, self._alive and self._handles or empty_t, nil
    end

    function specialeffect:attachedIter()
        return ipairs(self._attached)
    end

    --- Methods

    ---@param arg string|SpecialEffect
    ---@return effect|nil
    function specialeffect:add(arg)
        if type(arg) == 'string' then
            if self._alive then
                local e = AddSpecialEffect(arg, self._x, self._y)
                BlzSetSpecialEffectPosition(e, self._x, self._y, self._z)
                BlzSetSpecialEffectOrientation(e, self._yaw, self._pitch, self._roll)
                self._handles[arg] = e

                return e
            end

        elseif is_a(arg, specialeffect) then
            if arg.onAttach then
                arg:onAttach(self)
            end

            self._attached[#self._attached + 1] = arg
        end
    end

    ---@param arg string|SpecialEffect
    function specialeffect:remove(arg)
        if type(arg) == 'string' then
            if self._alive then
                local e = self._handles[arg]
                if e then
                    vanish_effect(e)
                end
            end

        elseif is_a(arg, specialeffect) then
            local n = #self._attached
            for i = 1, n do
                if self._attached[i] == arg then
                    if arg.onDettach then
                        arg:onDettach(self)
                    end

                    self._attached[i] = nil
                end
            end
        end
    end

    ---@param chainScatter boolean
    function specialeffect:scatter(chainScatter)
        for i = #self._attached, 1, -1 do
            local a = self._attached[i]

            if a.onDettach then
                a:onDettach(self)
            end
            self._attached[i] = nil

            if chainScatter then
                a:scatter(true)
            end
        end
    end

    ---@param x number
    ---@param y number
    ---@param z number
    ---@param relativeZ boolean
    function specialeffect:move(x, y, z, relativeZ)
        if relativeZ and z then
            z = z + get_terrain_z(x, y)
        end
        x, y, z = x or self._x, y or self._y, z or self._z
        local dx, dy, dz = x - self._x, y - self._y, z - self._z

        if self:visible() then
            for _, e in pairs(self._handles) do
                BlzSetSpecialEffectPosition(e, x, y, z)
            end
        end
        self._x, self._y, self._z = x, y, z

        for _, v in ipairs(self._attached) do
            v:move(v._x + dx, v._y + dy, v._z + dz)
        end
    end

    ---@param yaw number
    ---@param pitch number
    ---@param roll number
    function specialeffect:orient(yaw, pitch, roll)
        yaw, pitch, roll = yaw or self._yaw, pitch or self._pitch, roll or self._roll

        for _, v in pairs(self._handles) do
            BlzSetSpecialEffectOrientation(v, yaw, pitch, roll)
        end

        self._yaw, self._pitch, self._roll = yaw, pitch, roll
    end

    ---@param whichAnim animtype
    ---@param timeScale number
    ---@param chainAnimate boolean
    function specialeffect:animate(whichAnim, timeScale, chainAnimate)
        if self._alive then
            for _, v in pairs(self._handles) do
                if timeScale then
                    BlzPlaySpecialEffectWithTimeScale(v, whichAnim, timeScale)
                else
                    BlzPlaySpecialEffect(v, whichAnim)
                end
            end

            if chainAnimate then
                for _, v in ipairs(self._attached) do
                    v:animate(whichAnim, nil, true)
                end
            end
        end
    end

    ---@param force boolean
    function specialeffect:show(force)
        if force then self._visibility_count = 0 end

        if self._visibility_count == 0 then
            for _, e in pairs(self._handles) do
                BlzSetSpecialEffectPosition(e, self._x, self._y, self._z)
            end
        end

        self._visibility_count = self._visibility_count + 1
    end

    ---@param force boolean
    function specialeffect:hide(force)
        if force then self._visibility_count = 1 end

        self._visibility_count = self._visibility_count - 1

        if self._visibility_count == 0 then
            for _, e in pairs(self._handles) do
                BlzSetSpecialEffectX(e, SpecialEffect.HIDDEN_PLACE_X)
                BlzSetSpecialEffectY(e, SpecialEffect.HIDDEN_PLACE_Y)
            end
        end
    end

    local timer_t = {}
    local function on_kill()
        local t = GetExpiredTimer()
        timer_t[t]:destroy()
        DestroyTimer(t)
    end

    ---@param deathDuration number
    ---@param chainKill boolean
    function specialeffect:kill(deathDuration, chainKill)
        if self._alive then
            self:animate(ANIM_TYPE_DEATH)
            local t = CreateTimer()
            timer_t[t] = self
            TimerStart(t, deathDuration, false, on_kill)

            self._alive = false

            if chainKill then
                for _, v in ipairs(self._attached) do
                    v:kill(deathDuration, true)
                end
            end
        end
    end

    ---@param scatter boolean
    ---@param chainClear boolean
    function specialeffect:clear(scatter, chainClear)
        local t = {table.unpack(self._attached)}

        if scatter then
            self:scatter()
        end
        if self._alive then
            for _, v in pairs(self._handles) do
                vanish_effect(v)
            end
        end

        if chainClear then
            for _, v in ipairs(t) do
                v:clear(scatter, true)
            end
        end
    end

    function specialeffect:destroy()
        self:clear(true, false)
        setmetatable(self, nil)
    end
end
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I'd like to see this resource using some kind of timer recycling system for the delayed effects (e.g. TimerUtils).

Merging "on_kill" into the TimerStart call as an anonymous function will remove your dependency on the timer data attachment and allow you to access the original local variables.

Also, a "do everything" function would be good - AddSpecialEffectTimed is a very noteworthy vJass resource. Most people just want to add an effect that self-destructs after a certain period of time. How would this resource look in that very simple implementation?
 
Top