• 🏆 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] [Lua] Effect

A (not-so) simple snippet that will allow one to get more out of existing Blz natives related to special effects.

Lua:
--[[-----------------------------------------------------------------------------------------
    | 
    |
    |   EffectUtils
    |       v.1.1
    |       - MyPad
    |
    |---------------------------------------------------------------
    |
    |   Credits:
    |       - Bribe for Global Initialization and Hook
    |       - Trokkin for the out of bounds hiding trick.
    |
    |---------------------------------------------------------------
    |   Requires:
    |       - Global Initialization:
    |       - Hook: 
    |---------------------------------------------------------------
    |   
    |   Empowers the usage of natives related to special effects
    |   by wrapping the natives such that their behavior is
    |   best attuned to what the user expects without significant flaws.
    |
    |---------------------------------------------------------------
    |
    |   API:
    |
    |   ---------------
    |       Natives
    |   ---------------
    |
    |       ---@param fx effect
    |       ---@param dur? number
    |       - DestroyEffect(fx[, dur])
    |           - If "dur" is supplied and is greater than 0, a timer
    |             will be created internally to fully destroy the effect.
    |               - The effect cannot be directly destroyed while the
    |                 timer is running.
    |           - Not supplying "dur" will make it behave like the original
    |             native.
    |
    |       ---@param fx effect
    |       ---@param alpha integer
    |       - BlzSetSpecialEffectAlpha(fx, alpha)
    |           - When the fx is not visible (according to its visibility property),
    |             this function will only update its alpha value.
    |           - When the fx is visible (according to the same property),
    |             this function will behave as expected.
    |
    |       ---@param fx effect
    |       ---@param value number
    |       ---@param x number
    |       ---@param y number
    |       ---@param z number
    |       - BlzSetSpecialEffectX(fx, value[, ignore])
    |       - BlzSetSpecialEffectY(fx, value[, ignore])
    |       - BlzSetSpecialEffectZ(fx, value[, ignore])
    |       - BlzSetSpecialEffectPosition(fx, x, y, z[, ignore])
    |           - Works just like their regular equivalents.
    |           - When "ignore" is specified and evaluated as true,
    |             the information about the new location of the effect
    |             is not updated.
    |
    |       ---@param fx effect
    |       ---@return number
    |       - BlzGetLocalSpecialEffectX(fx)
    |       - BlzGetLocalSpecialEffectY(fx)
    |       - BlzGetLocalSpecialEffectZ(fx)
    |           - When not hidden, these work just like their regular equivalents.
    |           - When hidden, these will return their last known coordinates.
    |               - While hidden, the effect is moved away to the map bounds.
    |
    |       ---@param fx effect
    |       - BlzGetSpecialEffectHeight(fx)
    |           - QUASI-NATIVE
    |           - Returns the height of a special effect or 0 if the effect is nil.
    |
    |       ---@param fx effect
    |       - BlzGetSpecialEffectAlpha(fx)
    |           - QUASI-NATIVE
    |           - Returns the alpha color of a special effect or 255 if the effect is nil.
    |
    |       ---@param fx effect
    |       ---@param red integer
    |       ---@param green integer
    |       ---@param blue integer
    |       - BlzSetSpecialEffectColor(fx, red, green, blue)
    |           - Sets the rgb-values of the special effect's color attribute to the
    |             parameters provided.
    |
    |       ---@param fx effect
    |       ---@param whichPlayer player
    |       - BlzSetSpecialEffectPColor(fx, whichPlayer)
    |           - Sets the rgb-values of the special effect's color attribute to the
    |             color mapping assigned to the specified player.
    |
    |   ----------------
    |       Wrappers
    |   ----------------
    |
    |       SetEffectX  -> BlzSetSpecialEffectX(fx, value)
    |       SetEffectY  -> BlzSetSpecialEffectY(fx, value)
    |       SetEffectZ  -> BlzSetSpecialEffectZ(fx, value)
    |       SetEffectHeight -> BlzSetSpecialEffectHeight(fx, height)
    |       SetEffectAlpha  -> BlzSetSpecialEffectAlpha(fx, height)
    |       SetEffectColor  -> BlzSetSpecialEffectColor(fx, red, green, blue)
    |       SetEffectPColor -> BlzSetSpecialEffectColorByPlayer(fx, whichPlayer)
    |
    |       GetEffectX  -> BlzGetLocalSpecialEffectX(fx)
    |       GetEffectY  -> BlzGetLocalSpecialEffectY(fx)
    |       GetEffectZ  -> BlzGetLocalSpecialEffectZ(fx)
    |       GetEffectHeight -> BlzGetSpecialEffectHeight(fx)
    |       GetEffectAlpha  -> BlzGetSpecialEffectAlpha(fx)
    |
    |   ------------------------
    |       Unique Functions
    |   ------------------------
    |
    |       ---@param fx effect
    |       ---@param vis boolean
    |       - ShowEffect(fx, vis)
    |           - Shows or hides a special effect.
    |           - When hidden, the special effect's alpha color is set to 0, making it transparent.
    |           - As of 1.1, the special effect is moved out of bounds as well when hidden.
    |
    |       ---@param fx effect
    |       ---@return boolean
    |       - IsEffectShown(fx)
    |           - Checks if the effect is shown or not (via ShowEffect).
    | 
]]-------------------------------------------------------------------------------------------
do
    ---@alias handle userdata
    ---@alias effect handle
    --  Embedding GetZ into EffectUtils
    do
        if not GetZ then
            local _LOC
            OnMainInit(function()
                _LOC  = Location(0, 0)
            end)
            --- Returns the z-coordinate of the ground at the specified location.
            --- Only use in a manner that does not require "synchronous" behavior.
            ---@param x number
            ---@param y number
            ---@return number
            function GetZ(x, y)
                MoveLocation(_LOC, x, y)
                return GetLocationZ(_LOC)
            end
        end
        GetCoordinateZ  = GetZ
        GetPointZ       = GetZ
    end
    -- For garbage collection purposes
    local weakTable = {__mode = 'k'}
    local world     = {}
    local broken    = (BlzStartUnitAbilityCooldown and "1.32") or "1.31"
    local attr      = {
        height   = setmetatable({}, weakTable),
        alpha    = setmetatable({}, weakTable),
        vis      = setmetatable({}, weakTable),
        x        = setmetatable({}, weakTable),
        y        = setmetatable({}, weakTable),
        z        = setmetatable({}, weakTable)
    }
    local dest_flag = {}
    -- Hook all relevant natives.
    OnMainInit(function()
        -- Obtain the world bounds
        do
            world.rect  = GetWorldBounds()
            world.x     = GetRectMaxX(world.rect) + 8192.0
            world.y     = GetRectMaxY(world.rect) + 8192.0
            world.z     = 0.0
            RemoveRect(world.rect)
            world.rect  = nil
        end
        local function const_factory(funcName)
            AddHook(funcName,
            function(...)
                local fx        = _G[funcName].old(...)
                if (fx ~= nil) then
                    attr.height[fx] = 0.0
                    attr.alpha[fx]  = 255
                    attr.vis[fx]    = 0
                    attr.x[fx]      = BlzGetLocalSpecialEffectX(fx)
                    attr.y[fx]      = BlzGetLocalSpecialEffectY(fx)
                    attr.z[fx]      = BlzGetLocalSpecialEffectZ(fx)
                end
                return fx
            end)
        end
        const_factory("AddSpecialEffect")
        const_factory("AddSpecialEffectLoc")
        const_factory("AddSpecialEffectTarget")
        const_factory("AddSpellEffect")
        const_factory("AddSpellEffectById")
        const_factory("AddSpellEffectLoc")
        const_factory("AddSpellEffectByIdLoc")
        const_factory("AddSpellEffectTarget")
        const_factory("AddSpellEffectTargetById")
        AddHook("DestroyEffect",
        function(fx)
            if dest_flag[fx] then return
            end
            DestroyEffect.old(fx)
            attr.height[fx] = nil
            attr.alpha[fx]  = nil
            attr.vis[fx]    = nil
            attr.x[fx]      = nil
            attr.y[fx]      = nil
            attr.z[fx]      = nil
            dest_flag[fx]   = nil
        end)
        AddHook("DestroyEffect",
        function(fx, dur)
            if (not dur) or (dur <= 0) then
                DestroyEffect.old(fx)
                return
            end
            if dest_flag[fx] then return;
            end
            dest_flag[fx]   = true
            TimerStart(CreateTimer(), dur, false, function()
                dest_flag[fx]   = false
                DestroyEffect.old(fx)
            end)
        end, 1)
        AddHook("BlzSetSpecialEffectHeight",
        function(fx, height)
            attr.height[fx] = height
            if broken then
                height  = height + GetPointZ(BlzGetLocalSpecialEffectX(fx), BlzGetLocalSpecialEffectY(fx))
            end
            BlzSetSpecialEffectHeight.old(fx, height)
        end)
        local toInt = R2I
        AddHook("BlzSetSpecialEffectAlpha",
        function(fx, alpha)
            alpha           = ((alpha > 0xff) and 0xff) or ((alpha < 0) and 0) or toInt(alpha)
            attr.alpha[fx]  = alpha
            if attr.vis[fx] >= 0 then
                BlzSetSpecialEffectAlpha.old(fx, alpha)
            else
                BlzSetSpecialEffectAlpha.old(fx, 0)
            end
        end)
        AddHook("BlzSetSpecialEffectX",
        function(fx, value, ignore)
            if (attr.vis[fx] >= 0) or (not attr.vis[fx]) then
                BlzSetSpecialEffectX.old(fx, value)
            end
            if (not ignore) and (attr.x[fx]) then
                attr.x[fx]  = value
            end
        end)
        AddHook("BlzSetSpecialEffectY",
        function(fx, value, ignore)
            if (attr.vis[fx] >= 0) or (not attr.vis[fx]) then
                BlzSetSpecialEffectY.old(fx, value)
            end
            if (not ignore) and (attr.y[fx]) then
                attr.y[fx]  = value
            end
        end)
        AddHook("BlzSetSpecialEffectZ",
        function(fx, value, ignore)
            if (attr.vis[fx] >= 0) or (not attr.vis[fx]) then
                BlzSetSpecialEffectZ.old(fx, value)
            end
            if (not ignore) and (attr.z[fx]) then
                attr.z[fx]  = value
            end
        end)
        AddHook("BlzSetSpecialEffectPosition",
        function(fx, x, y, z, ignore)
            if (attr.vis[fx] >= 0) or (not attr.vis[fx]) then
                BlzSetSpecialEffectPosition.old(fx, x, y, z)
            end
            if not ignore then
                attr.x[fx]  = ((attr.x[fx] ~= nil) and x) or nil
                attr.y[fx]  = ((attr.y[fx] ~= nil) and y) or nil
                attr.z[fx]  = ((attr.z[fx] ~= nil) and z) or nil
            end
        end)
        AddHook("BlzGetLocalSpecialEffectX",
        function(fx)
            local result    = BlzGetLocalSpecialEffectX.old(fx)
            return ((attr.vis[fx] < 0) and (attr.z[fx])) or result
        end)
        AddHook("BlzGetLocalSpecialEffectY",
        function(fx)
            local result    = BlzGetLocalSpecialEffectY.old(fx)
            return ((attr.vis[fx] < 0) and (attr.y[fx])) or result
        end)
        AddHook("BlzGetLocalSpecialEffectZ",
        function(fx)
            local result    = BlzGetLocalSpecialEffectZ.old(fx)
            return ((attr.vis[fx] < 0) and (attr.z[fx])) or result
        end)
        SetEffectX      = BlzSetSpecialEffectX
        SetEffectY      = BlzSetSpecialEffectY
        SetEffectZ      = BlzSetSpecialEffectZ
        SetEffectHeight = BlzSetSpecialEffectHeight
        SetEffectAlpha  = BlzSetSpecialEffectAlpha
        SetEffectColor  = BlzSetSpecialEffectColor
        SetEffectPColor = BlzSetSpecialEffectColorByPlayer
        GetEffectX      = BlzGetLocalSpecialEffectX
        GetEffectY      = BlzGetLocalSpecialEffectY
        GetEffectZ      = BlzGetLocalSpecialEffectZ
        GetEffectHeight = BlzGetSpecialEffectHeight
        GetEffectAlpha  = BlzGetSpecialEffectAlpha
    end)
    
    if not BlzGetSpecialEffectHeight then
        --- Gets the height of a special effect, relative to the ground.
        ---@param fx effect
        ---@return number
        function BlzGetSpecialEffectHeight(fx)
            return attr.height[fx] or 0
        end
    end
    if not BlzGetSpecialEffectAlpha then
        --- Gets the Alpha color of a special effect.
        ---@param fx effect
        ---@return integer
        function BlzGetSpecialEffectAlpha(fx)
            return attr.alpha[fx] or 255
        end
    end
    ---Shows or hides an effect, depending on its internal visibility counter.
    ---This is separate from GetSpecialEffectAlpha.
    ---The behavior of the internal visibility counter is as follows:
        ---1. For any integer greater than or equal to 0 (n), the number of times the function must be called to hide the effect is (n + 1).
        ---2. For any negative integer "n", the number of times the function must be called to show the effect is |n|.
    ---@param fx effect
    ---@param vis boolean
    function ShowEffect(fx, vis)
        if (not attr.vis[fx]) then
            return
        end
        local inc       = (vis and 1) or (-1)
        attr.vis[fx]    = attr.vis[fx] + inc
        BlzSetSpecialEffectAlpha(fx, attr.alpha[fx])
        if (attr.vis[fx] == -1) and (not vis) then
            attr.vis[fx]    = 0
            BlzSetSpecialEffectX(fx, world.x, false)
            BlzSetSpecialEffectY(fx, world.y, false)
            BlzSetSpecialEffectZ(fx, world.z, false)
            attr.vis[fx]    = -1
        elseif (attr.vis[fx] == 0) and (vis) then
            BlzSetSpecialEffectX(fx, attr.x[fx], false)
            BlzSetSpecialEffectY(fx, attr.y[fx], false)
            BlzSetSpecialEffectZ(fx, attr.z[fx], false)
        end
    end
    ---Checks whether an effect is visible or not.
    ---This is separate from GetSpecialEffectAlpha.
    ---Defaults to false for already destroyed effects.
    ---@param fx effect
    ---@return boolean
    function IsEffectShown(fx)
        if (attr.vis[fx] == nil) then return false end
        return attr.vis[fx] >= 0
    end
end

  • v.1.0
    • Release
  • v.1.1
    • Updated to take advantage of the following resources:
      • Hook
      • Global Initialization
    • Documentation has been provided for most of the newly hooked functions. New functions have also been documented in accordance with Lua EmmyAnnotation.

    • IsEffectShown(fx: effect)[/code] introduced. [*]In addition to setting the alpha color value of the special effect to 0 when hiding the effect, the special effect is moved out of bounds as well. Special credits to Trokkin for that. [*][icode=lua]GetPointZ(x:number, y:number)[/code] snippet now included within the script itself. [/LIST] [/LIST] [/spoiler]
 
Last edited:
Level 8
Joined
Jan 23, 2015
Messages
121
Could you provide more information about what your snippet actually does and what the API functions are?
Code is pretty self-explanatory, so here you go:
1. Fixes pre-1.32 setHeight function to consider terrain height
2. Implements GetHeight, GetAlpha and Show/Hide functionality for effects
3. Doubles all Blz-prefixed functions into ones with better naming
All of that is achieved with function substitution and memorization, which wouldn't be great for perfomance in over-the-top effect-heavy map, but that is rarely a concern.

What is a concern though, is that effects which make sounds or leave textures on the ground (like thunderclap effect) would still make them if hidden. I believe a better approach would be to also move the effect to some point onthe map like (world.maxX,world.maxY) from where user presumably won't hear the sound.

Also, the snippet could use a better name, at least something like 'EffectUtils'. Plain 'Effect' provides little information about itself and sounds to me like some giant complicated system.
 
Last edited:
Top