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

Range Checker

This bundle is marked as awaiting update. A staff member has requested changes to it before it can be approved.
Due to the popularity of this resource, I have decided to update it to an improved Lua version which now uses the weapon range instead of acquisition range.

The effects' model will auto decay within 5 seconds.
There is a new feature to show both ranges if the unit uses both attack indexes.
You can modify the color of the effect in your own map to any that you wish.
Both SD & HD icons are included in the demo map, and automatically switch between graphics settings.
All assets required can be found within the demo map.

Lua:
if Debug then Debug.beginFile "RangeCheck" end
OnInit.global("RangeCheck", function(require)
    local ABILITY_ID = FourCC('A000')
    local MODEL_PATH = "RangeCircle.mdx" -- Set your model path.
    local SFX_HEIGHT = 0                 -- Set the height of the effect.
    local SFX_RED = 0                    -- Set the Red tint coloring of the effect: (0-255)
    local SFX_GREEN = 0                  -- Set the Green tint coloring of the effect: (0-255)
    local SFX_BLUE = 255                 -- Set the Blue tint coloring of the effect: (0-255)
    local SFX_ALPHA = 100                -- Set the Alpha coloring of the effect: (0-255)
    local t = CreateTrigger()
    TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    TriggerAddAction(t, function()
        local u = GetTriggerUnit()
        local s = ""
        if GetSpellAbilityId() == ABILITY_ID then
            if GetLocalPlayer() == GetOwningPlayer(u) then
                s = MODEL_PATH
            end
            for i = 0, 1 do
                local range = BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, 0) / 50
                if range > i then
                    local sfx = AddSpecialEffect(s, GetUnitX(u), GetUnitY(u))
                    BlzSetSpecialEffectX(sfx, GetUnitX(u))
                    BlzSetSpecialEffectY(sfx, GetUnitY(u))
                    BlzSetSpecialEffectZ(sfx, SFX_HEIGHT)
                    BlzSetSpecialEffectScale(sfx, BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, i) / 50)
                    BlzSetSpecialEffectColor(sfx, SFX_RED, SFX_GREEN, SFX_BLUE)
                    BlzSetSpecialEffectAlpha(sfx, SFX_ALPHA)
                    DestroyEffect(sfx)
                end
            end
        end
    end)
end)
if Debug then Debug.endFile() end

Note: The Lua upload utilizes both Bribe's Total Initialization and Eikonium's Debug Utils. Both of these are optional and not required to work.

Thanks to Elprede for a vJass version found below in the comments.
Contents

Range Checker Template (Map)

Reviews
Wrda
Separating the system from the example would be better, not everyone wants to display the range with an ability, they might want it on selection. Also, allow users to access the default colouring from a table, e.g. RangeChecker.red, while having API...
Level 18
Joined
Oct 17, 2012
Messages
820
Instead of forcing the use of Acquisition Range, which is important for game play, you could use the new native to get the unit's actual range.

The added benefit is that for maps well on their way to finalizing won't need all their units to have their Acquisition Range changed. Otherwise, this could become a hassle for these developers. For maps in the early phrase of development, less unnecessary work would be granted to them. Of course, third party tools could also circumvent this somewhat tedious work, but to install a third party tool for just this may not be well received as the past have dictated.

At the same time, the native makes it possible to get the ranges of both Attacks if the unit has a second one. In addition, via standalone functions, one could display range in any situation, not just during a spell cast.

A while back, the new native was mentioned as dysfunctional in List of non-working object data constants. I have tested the new native in the latest patch, and it is working fine.

JASS:
library RangeShower
    globals
        // ---------------- CONFIGURATION

        // Default values for sfx coloring in decimal format
        private constant integer    RED                     = 0
        private constant integer    GREEN                   = 0
        private constant integer    BLUE                    = 255

        // Model displayed to show range
        private constant string     FX_MODEL                = "RangeCircle.mdx"

        // Value to adjust size of sfx with
        private constant real       FX_SCALE_ADJUSTMENT     = 50.

        // ---------------- END OF CONFIGURATION
    endglobals

    globals
        private effect sfx
    endglobals

    // Use 0 for weaponIndex to get the range of Attack 1
    // Use 1 for Attack 2
    function ShowRangeEx takes unit u, integer weaponIndex, integer red, integer green, integer blue returns effect
        local string path = ""
        if GetLocalPlayer() == GetOwningPlayer(u) then
            set path = FX_MODEL
        endif
        set sfx = null
        set sfx = AddSpecialEffect(path, GetUnitX(u), GetUnitY(u))
        call BlzSetSpecialEffectX(sfx, GetUnitX(u))
        call BlzSetSpecialEffectY(sfx, GetUnitY(u))
        call BlzSetSpecialEffectZ(sfx, 0)
        call BlzSetSpecialEffectScale(sfx, BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, weaponIndex) / FX_SCALE_ADJUSTMENT)
        call BlzSetSpecialEffectColor(sfx, red, green, blue) // SET TEXTURE COLOR
        return sfx
    endfunction

    function ShowRange takes unit u, integer weaponIndex returns effect
        return ShowRangeEx(u, weaponIndex, RED, GREEN, BLUE)
    endfunction
endlibrary

For spell cast, just code a demo that peeps can copy to their map like so.

JASS:
scope RangeDemo initializer Init
    globals
        private constant real       DEATH_TIME = 10.0
        private constant integer    SPELL_ID = 'A000'
    endglobals

    globals
        private hashtable hash = InitHashtable()
    endglobals

    private function RemoveEffect takes nothing returns nothing
        local integer id = GetHandleid(GetExpiredTimer())
        local effect sfx = LoadEffectHandle(hash, id, 0)
        // Move effect to edge of map, so it is not seen by players
        call BlzSetSpecialEffectPosition(sfx, GetRectMaxX(GetWorldBounds()), GetRectMaxY(GetWorldBounds()), 0)

        // Clean up
        call FlushChildHashtable(hash, id)
        call DestroyEffect(sfx)
        call DestroyTimer(GetExpiredTimer())
        set sfx = null
    endfunction

    private function Display takes nothing returns nothing
        local timer t
        local effect sfx
        if GetSpellAbilityId() == SPELL_ID then
            set sfx = ShowRange(GetTriggerUnit(), 0)
            if DEATH_TIME == 5 then
                // Effect disappears after 5 seconds after this function call since model has a 5 seconds death animation
                call DestroyEffect(sfx)
            else
                set t = CreateTimer()
                call SaveEffectHandle(hash, GetHandleId(t), 0, sfx)
                call TimerStart(t, DEATH_TIME, false, function RemoveEffect)
                set t = null
            endif
            set sfx = null
        endif
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddAction(t, function Display)
    endfunction

endscope

Aniki, at one point, also made a similar system, using images for the display. His could benefit from automatic range detection.
 
Last edited:
Level 9
Joined
May 27, 2012
Messages
116
Not sure Im ok with this post considering its my code. That i wrote for him. Feels like stealing :p

At the very least give credits for code.

Also as respons to @HeavenSmitingDevilEmperor
Indeed that would be the correct way to do it.
Sadly when I wrote the code we did not have that native. If I would have uploaded this myself i would have updated it first.
 
Last edited:
Instead of forcing the use of Acquisition Range, which is important for game play, you could use the new native to get the unit's actual range.

The added benefit is that for maps well on their way to finalizing won't need all their units to have their Acquisition Range changed. Otherwise, this could become a hassle for these developers. For maps in the early phrase of development, less unnecessary work would be granted to them. Of course, third party tools could also circumvent this somewhat tedious work, but to install a third party tool for just this may not be well received as the past have dictated.

At the same time, the native makes it possible to get the ranges of both Attacks if the unit has a second one. In addition, via standalone functions, one could display range in any situation, not just during a spell cast.

A while back, the new native was mentioned as dysfunctional in List of non-working object data constants. I have tested the new native in the latest patch, and it is working fine.

JASS:
library RangeShower
    globals
        // ---------------- CONFIGURATION

        // Default values for sfx coloring in decimal format
        private constant integer    RED                     = 0
        private constant integer    GREEN                   = 0
        private constant integer    BLUE                    = 255

        // Model displayed to show range
        private constant string     FX_MODEL                = "RangeCircle.mdx"

        // Value to adjust size of sfx with
        private constant real       FX_SCALE_ADJUSTMENT     = 50.

        // ---------------- END OF CONFIGURATION
    endglobals

    globals
        private effect sfx
    endglobals

    // Use 0 for weaponIndex to get the range of Attack 1
    // Use 1 for Attack 2
    function ShowRangeEx takes unit u, integer weaponIndex, integer red, integer green, integer blue returns effect
        local string path = ""
        if GetLocalPlayer() == GetOwningPlayer(u) then
            set path = FX_MODEL
        endif
        set sfx = null
        set sfx = AddSpecialEffect(path, GetUnitX(u), GetUnitY(u))
        call BlzSetSpecialEffectX(sfx, GetUnitX(u))
        call BlzSetSpecialEffectY(sfx, GetUnitY(u))
        call BlzSetSpecialEffectZ(sfx, 0)
        call BlzSetSpecialEffectScale(sfx, BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, weaponIndex) / FX_SCALE_ADJUSTMENT)
        call BlzSetSpecialEffectColor(sfx, red, green, blue) // SET TEXTURE COLOR
        return sfx
    endfunction

    function ShowRange takes unit u, integer weaponIndex returns effect
        return ShowRangeEx(u, weaponIndex, RED, GREEN, BLUE)
    endfunction
endlibrary

For spell cast, just code a demo that peeps can copy to their map like so.

JASS:
scope RangeDemo initializer Init
    globals
        private constant real       DEATH_TIME = 10.0
        private constant integer    SPELL_ID = 'A000'
    endglobals

    globals
        private hashtable hash = InitHashtable()
    endglobals

    private function RemoveEffect takes nothing returns nothing
        local integer id = GetHandleid(GetExpiredTimer())
        local effect sfx = LoadEffectHandle(hash, id, 0)
        // Move effect to edge of map, so it is not seen by players
        call BlzSetSpecialEffectPosition(sfx, GetRectMaxX(GetWorldBounds()), GetRectMaxY(GetWorldBounds()), 0)

        // Clean up
        call FlushChildHashtable(hash, id)
        call DestroyEffect(sfx)
        call DestroyTimer(GetExpiredTimer())
        set sfx = null
    endfunction

    private function Display takes nothing returns nothing
        local timer t
        local effect sfx
        if GetSpellAbilityId() == SPELL_ID then
            set sfx = ShowRange(GetTriggerUnit(), 0)
            if DEATH_TIME == 5 then
                // Effect disappears after 5 seconds after this function call since model has a 5 seconds death animation
                call DestroyEffect(sfx)
            else
                set t = CreateTimer()
                call SaveEffectHandle(hash, GetHandleId(t), 0, sfx)
                call TimerStart(t, DEATH_TIME, false, function RemoveEffect)
                set t = null
            endif
            set sfx = null
        endif
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddAction(t, function Display)
    endfunction

endscope

Aniki, at one point, also made a similar system, using images for the display. His could benefit from automatic range detection.
At the time this was made, it was still on that list :p


Not sure Im ok with this post considering its my code. That i wrote for him. Feels like stealing :p

At the very least give credits for code and model.
does the site have a pool noodle emoji
 
At the time this was made, it was still on that list :p



does the site have a pool noodle emoji
Excellent model Mayday! I would adopt HeavenSmitingDevilEmperor's code as it is well documented and formatted. I like having more control over when to display the effect vs using an ability.

Also you could make the system more versatile by allowing for duration. To do this you make the effect constant and never destroy it. Then you locally move it and change it as needed and with timers you can play the death animation. However, the way HeavenSmitingDevilEmperor wrote the library is perfect for how light weight it is and all that other fancy stuff can be done thru an effect library.
Not sure Im ok with this post considering its my code. That i wrote for him. Feels like stealing :p

At the very least give credits for code.

Also as respons to @HeavenSmitingDevilEmperor
Indeed that would be the correct way to do it.
Sadly when I wrote the code we did not have that native. If I would have uploaded this myself i would have updated it first.
Pffft the code is so minimal x)
Instead of forcing the use of Acquisition Range, which is important for game play, you could use the new native to get the unit's actual range.

The added benefit is that for maps well on their way to finalizing won't need all their units to have their Acquisition Range changed. Otherwise, this could become a hassle for these developers. For maps in the early phrase of development, less unnecessary work would be granted to them. Of course, third party tools could also circumvent this somewhat tedious work, but to install a third party tool for just this may not be well received as the past have dictated.

At the same time, the native makes it possible to get the ranges of both Attacks if the unit has a second one. In addition, via standalone functions, one could display range in any situation, not just during a spell cast.

A while back, the new native was mentioned as dysfunctional in List of non-working object data constants. I have tested the new native in the latest patch, and it is working fine.

JASS:
library RangeShower
    globals
        // ---------------- CONFIGURATION

        // Default values for sfx coloring in decimal format
        private constant integer    RED                     = 0
        private constant integer    GREEN                   = 0
        private constant integer    BLUE                    = 255

        // Model displayed to show range
        private constant string     FX_MODEL                = "RangeCircle.mdx"

        // Value to adjust size of sfx with
        private constant real       FX_SCALE_ADJUSTMENT     = 50.

        // ---------------- END OF CONFIGURATION
    endglobals

    globals
        private effect sfx
    endglobals

    // Use 0 for weaponIndex to get the range of Attack 1
    // Use 1 for Attack 2
    function ShowRangeEx takes unit u, integer weaponIndex, integer red, integer green, integer blue returns effect
        local string path = ""
        if GetLocalPlayer() == GetOwningPlayer(u) then
            set path = FX_MODEL
        endif
        set sfx = null
        set sfx = AddSpecialEffect(path, GetUnitX(u), GetUnitY(u))
        call BlzSetSpecialEffectX(sfx, GetUnitX(u))
        call BlzSetSpecialEffectY(sfx, GetUnitY(u))
        call BlzSetSpecialEffectZ(sfx, 0)
        call BlzSetSpecialEffectScale(sfx, BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, weaponIndex) / FX_SCALE_ADJUSTMENT)
        call BlzSetSpecialEffectColor(sfx, red, green, blue) // SET TEXTURE COLOR
        return sfx
    endfunction

    function ShowRange takes unit u, integer weaponIndex returns effect
        return ShowRangeEx(u, weaponIndex, RED, GREEN, BLUE)
    endfunction
endlibrary

For spell cast, just code a demo that peeps can copy to their map like so.

JASS:
scope RangeDemo initializer Init
    globals
        private constant real       DEATH_TIME = 10.0
        private constant integer    SPELL_ID = 'A000'
    endglobals

    globals
        private hashtable hash = InitHashtable()
    endglobals

    private function RemoveEffect takes nothing returns nothing
        local integer id = GetHandleid(GetExpiredTimer())
        local effect sfx = LoadEffectHandle(hash, id, 0)
        // Move effect to edge of map, so it is not seen by players
        call BlzSetSpecialEffectPosition(sfx, GetRectMaxX(GetWorldBounds()), GetRectMaxY(GetWorldBounds()), 0)

        // Clean up
        call FlushChildHashtable(hash, id)
        call DestroyEffect(sfx)
        call DestroyTimer(GetExpiredTimer())
        set sfx = null
    endfunction

    private function Display takes nothing returns nothing
        local timer t
        local effect sfx
        if GetSpellAbilityId() == SPELL_ID then
            set sfx = ShowRange(GetTriggerUnit(), 0)
            if DEATH_TIME == 5 then
                // Effect disappears after 5 seconds after this function call since model has a 5 seconds death animation
                call DestroyEffect(sfx)
            else
                set t = CreateTimer()
                call SaveEffectHandle(hash, GetHandleId(t), 0, sfx)
                call TimerStart(t, DEATH_TIME, false, function RemoveEffect)
                set t = null
            endif
            set sfx = null
        endif
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddAction(t, function Display)
    endfunction

endscope

Aniki, at one point, also made a similar system, using images for the display. His could benefit from automatic range detection.
Mang having multiple global blocks in one library is gross. There is no need to null sfx before you assign a new effect to it.
JASS:
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
Null that trigger. (I know its a demo but it is likely that people will end up using it)
 
Last edited:
The core functionality of the library can be divorced from the example used in the test map.
There are some oddities I've found in the code though, which I'll list below:

JASS:
// It would be better if the path variable was initially assigned
// as RangeCircle.mdx and set to "" if the local player is not
// the owning player. That way, the string table can remain
// synced.
private function ShowRange takes nothing returns nothing
        local string path = ""
        // ...
            if GetLocalPlayer() == GetOwningPlayer(GetTriggerUnit()) then
                set path = "RangeCircle.mdx"
            endif
        // ...
endfunction

At its current state, I think the library needs an API overhaul. I've come up with an API
template below:

JASS:
//! novjass
library RangeDisplay
    // This is the core function, which will take the x and y coordinates of the
    // unit/destructable/item and display its range specified by the range parameter
    public function ShowRange takes real x, real y, real range returns effect

    // A utility function that might prove useful to some, in case the range
    // is dynamic.
    public function ScaleRange takes effect rangeEffect, real scale returns nothing

    // A utility function that can prove useful to some, in case the object the
    // range shower is attached to is capable of 2D movement.
    public function MoveRange takes effect rangeEffect, real x, real y returns nothing
endlibrary
//! endnovjass

A while back, the new native was mentioned as dysfunctional in List of non-working object data constants. I have tested the new native in the latest patch, and it is working fine.
My mistake there. I forgot to update the thread since that time.
 

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,870
After all that had been said, pretty much the use of the new native, specifically BlzGetUnitWeaponRealField(whichUnit, UNIT_WEAPON_RF_ATTACK_RANGE, weaponIndex), is better to use, along with what MyPad suggested, it has more flexibility and it's a general improvement.
It's still a nice idea, and definetely useful in a lot of scenarios such as TDs, survivals, zombie maps etc.
However, this deserves an update for a better API despite being good enough for approval
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
This should definitely be expandable to all sorts of different uses. Something like a function called "ShowAttackRange" that allows a parameter to be specified to indicate the duration the effect should remain active for. The model's decay time should not play a role; it should be allowed to be removed instantly.

To echo what @MyPad wrote, this should be able to be displayed locally per-player.

The color should also be configurable per-instance.

Lua API could be something like:

Lua:
local range = RangeIndicator(unit, player) --player is optional, will show effect for all players if it is nil
range.duration = 5    --otherwise unlimited
range.color(0,0,255)
range.destroy() --destroys the effect manually.

One more thing to consider would be to recycle the effects, so that they can be shown/hidden to local players without causing desync.
 
Last edited:

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,870
Separating the system from the example would be better, not everyone wants to display the range with an ability, they might want it on selection. Also, allow users to access the default colouring from a table, e.g. RangeChecker.red, while having API (inspired from MyPad) such as:
Lua:
    -- A utility function that might prove useful to some, in case the range
    -- is dynamic.
    function RangeChecker.scaleRange(rangeEffect, scale)

    -- A utility function that can prove useful to some, in case the object the
    -- range shower is attached to is capable of 2D movement.
    function RangeChecker.moveRange(rangeEffect,  x, y)
I don't understand the difference between his ShowRange and MoveRange.

Lua:
-- Creates a rangeEffect with the default colouring, for a specific player.
function RangeChecker.create(effect, x, y, whichPlayer)

-- Creates a rangeEffect with the default colouring, for a specific force.
function RangeChecker.createEx(effect, x, y, whichForce)
Lua:
local range = BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, 0) / 50

"50" didn't give me good results when I tested with units going further away from the tower, the effect showed me the unit was outside range, yet tower was still able to attack. "40" was more accurate for me.
I like the model, it's perfect, but I don't like its death animation. One has to move it outside of map area if the intention is to "remove it" immediately, on that precise moment. With the death animation, you have to wait 5 seconds.
 
Top