• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Get Shortest Path Unit 1.1

  • Like
Reactions: deepstrasz
What does it do
This library takes advantage of some of Wc3's own pathfinding from the ability Call to Arms to detect the unit from a group with the shortest path to specified xy co-ordinates which has two limitations:
1. Detects the shortest path unit as if it were a ground unit with less than 32 collision size
2. Has limited "range" to find each shortest path unit
NOTE: Requires xy co-ordinates to have terrain walkability

How does it work

It's based around the instant ability Call to Arms. When a peasant issues Call to Arms, it will try to find the shortest path to a Town Hall

How to use
GetShortestPathUnit takes group g, real x, real y returns unit

Example usage:
  • Custom script: set udg_ShortestPathUnit = GetShortestPathUnit(udg_YourGroup, GetUnitX(udg_YourUnit), GetUnitY(udg_YourUnit))
  • Custom script: set udg_ShortestPathUnit = GetShortestPathUnit(udg_YourGroup, GetLocationX(udg_YourPoint), GetLocationY(udg_YourPoint))
  • Custom script: set udg_ShortestPathUnit = GetShortestPathUnit(udg_YourGroup, udg_X, udg_Y)
Recommendations
The function can be resource intensive so use it with care

1. Pick units for your group in a certain range around the main location
2. Filter your group properly and dont forget to filter out units on unpathable terrain
3. Dont run the function too many times in the same frame

Finally, a higher Receiver amount (RA) results in better performance for larger groups. That is because the method in its simplicity requires one Receiver unit per unit in the group but this function checks in batches of RA instead, thus using a much lower amount of Receiver units. RA is the amount of permanent Receiver units created to accommodate the function

JASS
JASS:
library GetShortestPathUnit requires optional IsPointReachable//Duckfarter

/*    Configuration:
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯    */
    globals
        private constant integer TRANSMITTER = 'nipt'
        private constant integer RECEIVER = 'nipr'
        private constant integer C1 = 'Aipt'
        private constant integer C2 = 'Aipr'

    /*    The amount of permanent Receiver units created. A higher Receiver amount results in better performance 
        for larger groups. 4 low, 8 medium, 12 high    */
        private constant integer RA = 4

    /*    Closest distance. I dont recommend changing this    */
        private constant real DC = 16
    endglobals
/*
                GetShortestPathUnit 1.1    
                ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    Description:
    ¯¯¯¯¯¯¯¯¯¯¯¯
        This library takes advantage of some of Wc3's own pathfinding from the ability Call to Arms to detect 
        the unit from a group with the shortest path to specified xy co-ordinates which has two limitations:

        1. Detects the shortest path unit as if it were a ground unit with less than 32 collision size
        2. Has limited "range" to detect each shortest path unit

        NOTE: Requires xy co-ordinates to have terrain walkability

    API:
    ¯¯¯¯
        | function GetShortestPathUnit takes group g, real x, real y returns unit

    How to import:
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        1. Copy abilities Call to Arms (Transmitter) ID: Aipt, and Call To Arms (Receiver) ID: Aipr
        2. Copy units Transmitter (1) ID: nipt, and Receiver (2) ID: nipr
        3. Copy trigger GetClosestPathUnit

    Link:
    ¯¯¯¯¯
        https://www.hiveworkshop.com/threads/get-shortest-path-unit-1-1.359940/#post-3670368

*/
    globals
        private unit uT
        private unit array uR
        private unit array uF
        private unit uC
        private unit uSP
        private integer i
        private integer j
        private integer gSize
        private real xF
        private real yF
        private real d
        private real dC
        private boolean array disabled
        private group swap = CreateGroup()
    endglobals

    private function FilterShortestPathUnit takes real x, real y returns unit

        set uC = null
        
        set i = 1
        loop
            exitwhen uF[i] == null
            set xF = GetUnitX(uF[i])
            set yF = GetUnitY(uF[i])
            //Stops false negatives with ~ equal XY
            set d = SquareRoot((xF-x)*(xF-x) + (yF-y)*(yF-y))
            if d <= dC then
                set dC = d
                set uC = uF[i]
            elseif dC == DC then
                call SetUnitX(uR[i], xF)
                call SetUnitY(uR[i], yF)
            endif
            set i = i + 1
        endloop
        set i = i - 1

        //If a shortest path unit exists within 16 range it skips the Call to Arms check
        if dC < DC then
            if uC != null then
                set gSize = gSize + 1
            endif
            return uC
        endif

        //Disables Receiver abilities when gSize < RA
        set j = RA
        loop
            exitwhen i >= j
            if disabled[j] == false then
                set disabled[j] = true
                call BlzUnitDisableAbility(uR[j], C2, true, false)
                set uF[j] = null
            endif
            set j = j - 1
        endloop

        //Initiate Call to Arms check
        call IssueImmediateOrderById(uT, 852072)
        //Initial check to see if there is a shortest path unit in the batch
        if GetUnitCurrentOrder(uT) == 0 then
            return null
        endif
        //Unhides and hides Receivers to interrupt the Call to Arms order
        set j = 1
        loop
            exitwhen j > i
            call ShowUnit(uR[j], true)
            call ShowUnit(uR[j], false)
            if GetUnitCurrentOrder(uT) == 0 then
                set gSize = gSize + 1
                return uF[j]
            endif
            set j = j + 1
        endloop

        return null

    endfunction

    function GetShortestPathUnit takes group g, real x, real y returns unit

        //Aborts if gSize is 0
        set gSize = BlzGroupAddGroupFast(g, swap)
        if gSize == 0 then
            return null
        endif

        set dC = DC
        set uSP = null
        call SetUnitX(uT, x)
        call SetUnitY(uT, y)

        //Enables abilities
        call BlzUnitDisableAbility(uT, C1, false, false)
        call BlzUnitDisableAbility(uT, 'Amov', false, false)
        set i = 1
        loop
            exitwhen i > RA or i > gSize
            set disabled[i] = false
            call BlzUnitDisableAbility(uR[i], C2, false, false)
            set i = i + 1
        endloop

        //Loops through g in batches of RA to find the shortest path unit
        set i = 1
        loop
            exitwhen gSize == 0
            set uF[i] = FirstOfGroup(swap)
            call GroupRemoveUnit(swap, uF[i])
            set gSize = gSize - 1
            if i == RA or gSize == 0 then
                set uSP = FilterShortestPathUnit(x, y)
                call GroupAddUnit(swap, uSP)
                exitwhen gSize == 1 and uSP != null
                set i = 0
            endif
            set i = i + 1
        endloop
        call GroupRemoveUnit(swap, uSP)
        set uC = null

        //Disables abilities
        call BlzUnitDisableAbility(uT, C1, true, false)
        call BlzUnitDisableAbility(uT, 'Amov', true, false)
        set i = 1
        loop
            exitwhen i > RA
            if disabled[i] == false then
                set disabled[i] = true
                call BlzUnitDisableAbility(uR[i], C2, true, false)
                set uF[i] = null
            endif
            set i = i + 1
        endloop

        return uSP

    endfunction

    //For future use
    function GetReceiverAmount takes nothing returns integer
        return RA
    endfunction

    function GetReceiverUnitByIndex takes integer i returns unit
        return uR[i]
    endfunction

    private module Init
        private static method onInit takes nothing returns nothing
            call init()
        endmethod
    endmodule

    private struct InitStruct extends array
        private static method init takes nothing returns nothing
            static if LIBRARY_IsPointReachable then
                set uT = GetTransmitterUnit()
                set uR[1] = GetReceiverUnit()
                set disabled[1] = true
                set i = 2
            else
                set uT = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), TRANSMITTER, 0, 0, 0)
                call BlzUnitDisableAbility(uT, C1, true, false)
                call BlzUnitDisableAbility(uT, 'Amov', true, false)
                call ShowUnit(uT, false)
                set i = 1
            endif
            loop
                exitwhen i > RA
                set uR[i] = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), RECEIVER, 0, 0, 0)
                set disabled[i] = true
                call BlzUnitDisableAbility(uR[i], C2, true, false)
                call ShowUnit(uR[i], false)
                set i = i + 1
            endloop
        endmethod
        implement Init
    endstruct

endlibrary
11-05-2025
1.0 initial release
13-05-2025
1.1 fixed misspelling of Receiver, updated description, added Demo Necro to showcase AI usage
Previews
Contents

Get Shortest Path Unit 1.1 (Map)

Reviews
Antares
Everything good. Like the IsPointReachable function, this uses some forbidden magic that I don't fully understand in its algorithm. Approved
Can you expand on what changing the variable RA does? It is not clear.

I was also confused at first about what "shortest path unit" meant in your description. I think you should write it as "unit with the shortest path to a specified point on the map" or something similar.

Receiver is misspelled.

The code looks good, the function does what it's advertising, and the demo map works well, so everything perfect there.
 
Level 24
Joined
Feb 27, 2019
Messages
837
Can you expand on what changing the variable RA does? It is not clear.
Do you think it makes more sense now?
I was also confused at first about what "shortest path unit" meant in your description. I think you should write it as "unit with the shortest path to a specified point on the map" or something similar.
Ill pretty much steal that.
Receiver is misspelled.
If a misspelling is part of the critique its a good sign. Not like Receiver is mentioned everywhere and is a main component 🙄 I very much appreciate the correction.
 
Top