1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.
    Dismiss Notice
  4. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  5. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  6. The results are out! Check them out.
    Dismiss Notice
  7. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  8. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  9. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[Snippet] GetClosestWidget

Discussion in 'JASS Resources' started by Bannar, Sep 24, 2011.

  1. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,087
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    Old.
    Special thanks to Bribe, baassee and Troll-Brain.

    Rewritten. Modularity, configurability and efficiency - sums up the update.
    Note: treeOnly parameter (destructable module) cease to exist, reasoning: there is a filter argument already, thus if you want to seach for trees-only do it there.

    Code (vJASS):
    /*****************************************************************************
    *
    *    GetClosestWidget v3.0.1.3
    *       by Bannar aka Spinnaker
    *
    *    Allows finding closest widget with ease.
    *
    ******************************************************************************
    *
    *    Configurables:
    *
    *       Choose which modules should or should not be implemented.
    *
    *          constant boolean UNITS_MODULE
    *          constant boolean GROUP_MODULE
    *          constant boolean ITEMS_MODULE
    *          constant boolean DESTS_MODULE
    *
    *       Define start and final distances for search iterations within generic GetClosest functions.
    *       If final value is reached, enumeration is performed on whole map.
    *
    *          constant real START_DISTANCE
    *          constant real FINAL_DISTANCE
    *
    ******************************************************************************
    *
    *    Functions:
    *
    *       Units:
    *        | function GetClosestUnit takes real x, real y, boolexpr filter returns unit
    *        |    returns unit closest to coords(x, y)
    *        |
    *        | function GetClosestUnitInRange takes real x, real y, real radius, boolexpr filter returns unit
    *        |    returns unit closest to coords(x, y) within range radius
    *        |
    *        | function GetClosestUnitInGroup takes real x, real y, group g returns unit
    *        |    returns unit closest to coords(x, y) within group g
    *
    *
    *       Group:
    *        | function GetClosestNUnitsInRange takes real x, real y, real radius, integer n, group dest, boolexpr filter returns nothing
    *        |    adds to group dest up to N units, closest to coords(x, y) within range radius
    *        |
    *        |  function GetClosestNUnitsInGroup takes real x, real y, integer n, group source, group dest returns nothing
    *        |    adds to group dest up to N units, closest to coords(x, y) within group source
    *
    *
    *       Items:
    *        | function GetClosestItem takes real x, real y, boolexpr filter returns item
    *        |    returns item closest to coords(x, y)
    *        |
    *        | function GetClosestItemInRange takes real x, real y, real radius, boolexpr filter returns item
    *        |    returns item closest to coords(x, y) within range radius
    *
    *
    *       Destructables:
    *        | function GetClosestDestructable takes real x, real y, boolexpr filter returns destructable
    *        |    returns destructable closest to coords(x, y)
    *        |
    *        | function GetClosestDestructableInRange takes real x, real y, real radius, boolexpr filter returns destructable
    *        |    returns destructable closest to coords(x, y) within range radius
    *
    *
    *****************************************************************************/

    library GetClosestWidget

        globals
            private constant boolean UNITS_MODULE   = true
            private constant boolean GROUP_MODULE   = true
            private constant boolean ITEMS_MODULE   = true
            private constant boolean DESTS_MODULE   = true

            private constant real    START_DISTANCE = 800
            private constant real    FINAL_DISTANCE = 3200
        endglobals

        globals
            private real distance
            private real coordX
            private real coordY
        endglobals

        private keyword GroupModule

        private function calcDistance takes real x, real y returns real
            local real dx = x - coordX
            local real dy = y - coordY
            return ( (dx*dx + dy*dy) / 10000 )
        endfunction

        private struct ClosestWidget extends array
            static if UNITS_MODULE then
                static unit unit
                static group group = CreateGroup()
            endif

            static if GROUP_MODULE then
                static if not UNITS_MODULE then
                    static group group = CreateGroup()
                endif
                static integer count = 0
                static unit array sorted
                static real array vector

                implement GroupModule
            endif

            static if ITEMS_MODULE then
                static item item
                static rect area = Rect(0, 0, 0, 0)
            endif

            static if DESTS_MODULE then
                static destructable destructable
                static if not ITEMS_MODULE then
                    static rect area = Rect(0, 0, 0, 0)
                endif
            endif
        endstruct

        private function Defaults takes real x, real y returns nothing
            static if UNITS_MODULE then
                set ClosestWidget.unit = null
            endif
            static if ITEMS_MODULE then
                set ClosestWidget.item = null
            endif
            static if DESTS_MODULE then
                set ClosestWidget.destructable = null
            endif

            set distance = 100000
            set coordX = x
            set coordY = y
        endfunction

        static if UNITS_MODULE then
            //! runtextmacro DEFINE_GCW_UNIT_MODULE()
        endif
        static if GROUP_MODULE then
            //! runtextmacro DEFINE_GCW_GROUP_MODULE()
        endif
        static if ITEMS_MODULE then
            //! runtextmacro DEFINE_GCW_MODULE("Item", "item")
        endif
        static if DESTS_MODULE then
            //! runtextmacro DEFINE_GCW_MODULE("Destructable", "destructable")
        endif

    //! textmacro DEFINE_GCW_UNIT_MODULE

        private function doEnumUnits takes unit u returns nothing
            local real dist = calcDistance(GetUnitX(u), GetUnitY(u))

            if ( dist < distance ) then
                set ClosestWidget.unit = u
                set distance = dist
            endif
        endfunction

        private function enumUnits takes nothing returns nothing
            call doEnumUnits(GetEnumUnit())
        endfunction

        function GetClosestUnit takes real x, real y, boolexpr filter returns unit
            local real r = START_DISTANCE
            local unit u
            call Defaults(x, y)

            loop
                if ( r > FINAL_DISTANCE ) then
                    call GroupEnumUnitsInRect(ClosestWidget.group, GetWorldBounds(), filter)
                    exitwhen true
                else
                    call GroupEnumUnitsInRange(ClosestWidget.group, x, y, r, filter)
                    exitwhen FirstOfGroup(ClosestWidget.group) != null
                endif
                set r = 2*r
            endloop

            loop
                set u = FirstOfGroup(ClosestWidget.group)
                exitwhen u == null
                call doEnumUnits(u)
                call GroupRemoveUnit(ClosestWidget.group, u)
            endloop

            return ClosestWidget.unit
        endfunction

        function GetClosestUnitInRange takes real x, real y, real radius, boolexpr filter returns unit
            local unit u
            call Defaults(x, y)

            if ( radius >= 0 ) then
                call GroupEnumUnitsInRange(ClosestWidget.group, x, y, radius, filter)
                loop
                    set u = FirstOfGroup(ClosestWidget.group)
                    exitwhen u == null
                    call doEnumUnits(u)
                    call GroupRemoveUnit(ClosestWidget.group, u)
                endloop
            endif

            return ClosestWidget.unit
        endfunction

        function GetClosestUnitInGroup takes real x, real y, group g returns unit
            call Defaults(x, y)
            call ForGroup(g, function enumUnits)
            return ClosestWidget.unit
        endfunction

    //! endtextmacro

    //! textmacro DEFINE_GCW_GROUP_MODULE

        private module GroupModule

            static method doSaveUnits takes unit u returns nothing
                set count = count + 1
                set sorted[count] = u
                set vector[count] = calcDistance(GetUnitX(u), GetUnitY(u))
            endmethod

            static method saveUnits takes nothing returns nothing
                call doSaveUnits(GetEnumUnit())
            endmethod

            static method sortUnits takes integer lo, integer hi returns nothing
                local integer i = lo
                local integer j = hi
                local real pivot = vector[(lo+hi)/2]

                loop
                    loop
                        exitwhen vector[i] >= pivot
                        set i = i + 1
                    endloop
                    loop
                        exitwhen vector[j] <= pivot
                        set j = j - 1
                    endloop

                    exitwhen i > j

                    set vector[0] = vector[i]
                    set vector[i] = vector[j]
                    set vector[j] = vector[0]

                    set sorted[0] = sorted[i]
                    set sorted[i] = sorted[j]
                    set sorted[j] = sorted[0]

                    set i = i + 1
                    set j = j - 1
                endloop

                if ( lo < j ) then
                    call sortUnits(lo, j)
                endif
                if ( hi > i ) then
                    call sortUnits(i, hi)
                endif
            endmethod

            static method fillGroup takes integer n, group dest returns nothing
                loop
                    exitwhen count <= 0 or sorted[count] == null
                    if ( count <= n ) then
                        call GroupAddUnit(dest, sorted[count])
                    endif
                    set sorted[count] = null
                    set count = count - 1
                endloop
            endmethod

        endmodule

        function GetClosestNUnitsInRange takes real x, real y, real radius, integer n, group dest, boolexpr filter returns nothing
            local unit u
            call Defaults(x, y)

            if ( radius >= 0 )then
                call GroupEnumUnitsInRange(ClosestWidget.group, x, y, radius, filter)
                loop
                    set u = FirstOfGroup(ClosestWidget.group)
                    exitwhen u == null
                    call ClosestWidget.doSaveUnits(u)
                    call GroupRemoveUnit(ClosestWidget.group, u)
                endloop

                call ClosestWidget.sortUnits(1, ClosestWidget.count)
                call ClosestWidget.fillGroup(n, dest)
            endif
        endfunction

        function GetClosestNUnitsInGroup takes real x, real y, integer n, group source, group dest returns nothing
            local integer i = 0
            call Defaults(x, y)

            call ForGroup(source, function ClosestWidget.saveUnits)
            call ClosestWidget.sortUnits(1, ClosestWidget.count)
            call ClosestWidget.fillGroup(n, dest)
        endfunction

    //! endtextmacro

    //! textmacro DEFINE_GCW_MODULE takes NAME, TYPE

        private function enum$NAME$s takes nothing returns nothing
            local $TYPE$ temp = GetEnum$NAME$()
            local real dist = calcDistance(Get$NAME$X(temp), Get$NAME$Y(temp))

            if ( dist < distance ) then
                set ClosestWidget.$TYPE$ = temp
                set distance = dist
            endif

            set temp = null
        endfunction

        function GetClosest$NAME$ takes real x, real y, boolexpr filter returns $TYPE$
            local real r = START_DISTANCE
            call Defaults(x, y)

            loop
                if ( r > FINAL_DISTANCE ) then
                    call Enum$NAME$sInRect(GetWorldBounds(), filter, function enum$NAME$s)
                    exitwhen true
                else
                    call SetRect(ClosestWidget.area, x-r, y-r, x+r, y+r)
                    call Enum$NAME$sInRect(ClosestWidget.area, filter, function enum$NAME$s)
                    exitwhen ClosestWidget.$TYPE$ != null
                endif
                set r = 2*r
            endloop

            return ClosestWidget.$TYPE$
        endfunction

        function GetClosest$NAME$InRange takes real x, real y, real radius, boolexpr filter returns $TYPE$
            call Defaults(x, y)

            if ( radius > 0 ) then
                call SetRect(ClosestWidget.area, x-radius, y-radius, x+radius, y+radius)
                call Enum$NAME$sInRect(ClosestWidget.area, filter, function enum$NAME$s)
            endif

            return ClosestWidget.$TYPE$
        endfunction

    //! endtextmacro

    endlibrary


    Demo code:
    Code (vJASS):
    globals
        unit paladin = null
        group all = CreateGroup()
        group closest = CreateGroup()
    endglobals

    struct gcw_test extends array
        static method filter takes nothing returns boolean
            return GetFilterUnit() != paladin
        endmethod

        static method printU takes string prefix, unit u returns nothing
            call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, prefix + GetUnitName(u))
        endmethod

        static method printI takes string prefix, item i returns nothing
            call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, prefix + GetItemName(i))
        endmethod

        static method printD takes string prefix, destructable d returns nothing
            call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, prefix + GetDestructableName(d))
        endmethod

        static method call_test takes nothing returns boolean
            local real x = GetUnitX(paladin)
            local real y = GetUnitY(paladin)

            call GroupClear(closest)
            call ClearTextMessages()
            call GetClosestNUnitsInGroup(x, y, 1, all, closest)

            call printU("closest unit: ", GetClosestUnit(x, y, Filter(function thistype.filter)))
            call printU("closest unit within group: ", FirstOfGroup(closest))
            call printI("closest item: ", GetClosestItem(x, y, null))
            call printD("closest destructable: ", GetClosestDestructable(x, y, null))
            return false
        endmethod

        static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterTimerEvent(t, 3, true)
            call TriggerAddCondition(t, function thistype.call_test)
            set t = null

            set paladin = CreateUnit(Player(0), 'Hpal', 0, 0, 0)
            call GroupEnumUnitsInRange(all, 0, 0, 5000, function thistype.filter)
        endmethod
    endstruct
     
    Last edited: Jul 15, 2014
  2. Magtheridon96

    Magtheridon96

    Joined:
    Dec 12, 2008
    Messages:
    6,006
    Resources:
    26
    Maps:
    1
    Spells:
    8
    Tutorials:
    7
    JASS:
    10
    Resources:
    26
    GetClosestWidget


    ;)
     
  3. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,087
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    Woah, should I change the name Maggy? ^^
     
  4. Ceday

    Ceday

    Joined:
    Feb 22, 2010
    Messages:
    1,098
    Resources:
    0
    Resources:
    0
    Maybe add a function same name with library,

    Code (vJASS):
    GetClosestObject
    //or
    GetClosestWidget


    Edit:Mag faster
     
  5. Magtheridon96

    Magtheridon96

    Joined:
    Dec 12, 2008
    Messages:
    6,006
    Resources:
    26
    Maps:
    1
    Spells:
    8
    Tutorials:
    7
    JASS:
    10
    Resources:
    26
    *AWWWWWW YEAH* :p


    When I said GetClosestWidget, I was referring to a possible function, but when I thought about it's practical application, I got nothing :p

    GetClosestWidget sounds good for a library name ;D
     
  6. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,087
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    Okey, library's name has been changed to 'GetClosestWidget' omg -.-
    I'll ask moderators to change thread's name latter..
     
  7. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,058
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    I recommend a FirstOfGroup loop instead of enumerating units, as a bonus it will auto-clear the group when done looping which keeps the RAM slightly lower.

    globalsShouldBeCasedLikeThis, NotLikeThis
     
  8. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,087
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    globals names fixed.

    FirstOfGroup, but in which functions? in 'NUnits' ones? I would have to implement additional loop per function; instead of having one enumerate for entire library.
     
  9. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,058
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Wrapper functions ;)

    I think for the resultGroup, it may be easier to allow the user to pop in their own group that they want to be the resultGroup, that way they don't have to use GroupAddGroup if they wanted to save them for later.
     
  10. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,087
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    So basicaly you mean to get rid of resultGroup, in replacement we add additional parameter required by 'NUnits' functions, yeah?
    If yes, would it be okey to clear the passed group first? or just add units into it?
    Code (vJASS):
        function GetClosestNUnits takes real x, real y, integer n, boolexpr filter returns group
            call GroupEnumUnitsInRect(bj_lastCreatedGroup, bj_mapInitialPlayableArea, filter)
            call ResetData(x,y)
            call GroupClear(resultGroup)
            loop
                exitwhen 0 == n
                call ForGroup(bj_lastCreatedGroup, function EnumUnits)
                exitwhen closestUnit == null
                call GroupRemoveUnit(bj_lastCreatedGroup, closestUnit)
                call GroupAddUnit(resultGroup, closestUnit)
                set closestUnit = null
                set closestDistance = 100000
                set n = n - 1
            endloop
            return resultGroup
        endfunction


    ->>
    Code (vJASS):
        function GetClosestNUnits takes real x, real y, integer n, group g, boolexpr filter returns group
            call GroupEnumUnitsInRect(bj_lastCreatedGroup, bj_mapInitialPlayableArea, filter)
            call ResetData(x,y)
            loop
                exitwhen 0 == n
                call ForGroup(bj_lastCreatedGroup, function EnumUnits)
                exitwhen closestUnit == null
                call GroupRemoveUnit(bj_lastCreatedGroup, closestUnit)
                call GroupAddUnit(g, closestUnit)
                set closestUnit = null
                set closestDistance = 100000
                set n = n - 1
            endloop
        endfunction
     
  11. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,058
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Adding units to it would be fine because the user should have cleared the group his/her self in such a case.

    The reason I would really find it useful is if you wanted to acquire all the targets for a chain lightining type spell. To be fair, it's almost not worth even including GetClosestNUnits because the situations are limited. But it's your call.
     
  12. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,087
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    I think it's reasonable thinking truely. If user wants group of units, he probably will need his own group anyways, plus yeah, it smoother and gives oportunity to use that group later.

    ~Removed the GetClosestNUnits
    You are right once again Bribe, if user wants unit group without specific range, he can type enormous number anyways..
    Heh, I was testing posibilities not to enumerate immidiately the entire map, and it ends with removing whole function.. thanks Bribe ;<

    Fixed GetClosestNUnitsInGroup() function; it was picking all the time the same unit.
    ~Updated: Version 1.0.1.0
     
  13. baassee

    baassee

    Joined:
    Nov 14, 2008
    Messages:
    3,220
    Resources:
    17
    Spells:
    14
    Tutorials:
    3
    Resources:
    17
    Good work with this one!

    Only thing I could spot right now was:

    call GroupClear(bj_lastCreatedGroup)


    No need to that when
    GroupEnumUnitsInRange
    clears the group before enumeration. I gues that
    GroupEnumUnitsInRect
    works the same (else my point here is useless).

    In the GetClosestUnit, forgot to mention.

    Also, is that module initializer really necessary? Can't you just initialize it in the globals block?
     
  14. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,058
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    IIRC "Rect" returns null from the globals block.
     
  15. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,087
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    Yup! I haven't been checking it for a long time but it's like creating a unit from global block - sucks :p

    Are you 100% sure that clearing bj_lastCreatedGroup isn't necessary? In case I couldn't test in completely and wasn't sure about that fact. Everyone agrees that it isn't necessary?
     
  16. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,058
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    GroupEnum... clears the group first. It would be better if it didn't of course, because there would be more flexibility then ;)

    But you know Blizzard ;)
     
  17. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,087
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    Okey great, line removed.
     
  18. baassee

    baassee

    Joined:
    Nov 14, 2008
    Messages:
    3,220
    Resources:
    17
    Spells:
    14
    Tutorials:
    3
    Resources:
    17
    I think this is a good thing :)
     
  19. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,058
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    It's only a good thing if that's the result you want, but if you don't want it then you have to use two groups, one for enumerating and one for storing the units.
     
  20. baassee

    baassee

    Joined:
    Nov 14, 2008
    Messages:
    3,220
    Resources:
    17
    Spells:
    14
    Tutorials:
    3
    Resources:
    17
    Oh I see your point, anyway that's what GroupUtils/Recycle's for? :p