• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

Unit Comes Within Range of [unit array]???

Level 18
Joined
Mar 16, 2008
Messages
721
Can't find a thread specifically addressing this...

Can anyone help me devise a trigger Event that would be a unit coming in range of a specific unit-type, that is yet to be built? Players may or may not build this structure and the amount build will vary. Does this make sense? Any ideas?
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,586
You can add Events to triggers whenever you'd like:
  • Build Tower
    • Events
      • Unit - A unit Finishes construction
    • Conditions
      • (Unit-type of (Constructed structure)) Equal to Arcane Tower
    • Actions
      • Trigger - Add to Within Range Tower <gen> the event (Unit - A unit comes within 256.00 of (Constructed structure))
  • Within Range Tower
    • Events
    • Conditions
    • Actions
      • Unit - Kill (Triggering unit)
I would personally use a Unit Group + Periodic Interval to look for nearby units, but the above should work.
 
Level 18
Joined
Mar 16, 2008
Messages
721
Ah good idea. Thank you. :thumbs_up:

Won't this cause a stutter? I can't stand those stutters! :p

EDIT:
So the Warden hates corrupted structures is the RP here...
  • corrupt built
    • Events
      • Unit - A unit Finishes construction
    • Conditions
      • Or - Any (Conditions) are true
        • Conditions
          • (Unit-type of (Constructed structure)) Equal to Corrupted Moon Well (players)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Altar of Elders (corrupt player)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Hunter's Hall (corrupt player)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Ancient Protector (custom merc)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Chimaera Roost (corrupt player)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Barrow Den (to be added in update)
    • Actions
      • Trigger - Add to warden near corrupt <gen> the event (Unit - A unit comes within 900.00 of (Constructed structure))
  • warden near corrupt
    • Events
    • Conditions
      • (Triggering unit) Equal to Warden (NPC) 0054 <gen>
    • Actions
      • Unit - Change ownership of Warden (NPC) 0054 <gen> to Neutral Passive and Retain color
      • Unit - Set Unit: (Triggering unit)'s Weapon Integer Field: Attack Damage Base ('ua1b')at Index:0 to Value: 5000
      • Unit - Order Warden (NPC) 0054 <gen> to Attack No unit

What do I call the structure? "Triggering Unit" would be the entering unit or the "Warden."
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,586
(Triggering unit) is the Unit that came within range of the structure. It's ALWAYS set to the unit mentioned in the Event:

Unit - A unit comes within...

I see your issue though, you need to Attack the (Constructed structure) but you've no reference to it. You could use a Pick Every Unit action and find the closest eligible target. Store those structures in a Unit Group and you can narrow it down and keep it efficient.

Here's an example that should work. It takes advantage of a Jass library for easily getting the closest object:
  • corrupt built
    • Events
      • Unit - A unit Finishes construction
    • Conditions
      • Or - Any (Conditions) are true
        • Conditions
          • (Unit-type of (Constructed structure)) Equal to Corrupted Moon Well (players)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Altar of Elders (corrupt player)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Hunter's Hall (corrupt player)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Ancient Protector (custom merc)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Chimaera Roost (corrupt player)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Barrow Den (to be added in update)
    • Actions
      • Unit Group - Add (Constructed structure) to Warden_Group
      • Trigger - Add to warden near corrupt <gen> the event (Unit - A unit comes within 900.00 of (Constructed structure))
  • warden near corrupt
    • Events
    • Conditions
      • (Triggering unit) Equal to Warden (NPC) 0054 <gen>
    • Actions
      • Set Variable Warden = (Triggering unit)
      • Unit - Change ownership of Warden to Neutral Passive and Retain color
      • Unit - Set Unit: Warden's Weapon Integer Field: Attack Damage Base ('ua1b')at Index:0 to Value: 5000
      • Custom script: set udg_Warden_Target = GetClosestUnitInGroup(GetUnitX(udg_Warden), GetUnitY(udg_Warden), udg_Warden_Group)
      • Unit - Order Warden to Attack Warden_Target
Just remember to remove destroyed structures from Warden_Group.

Here's the code for the GetClosestUnitInGroup function:
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
Put this code in a trigger/script that is positioned above your other triggers.
 
Last edited:
Level 18
Joined
Mar 16, 2008
Messages
721
(Triggering unit) is the Unit that came within range of the structure. It's ALWAYS set to the unit mentioned in the Event:

Unit - A unit comes within...

I see your issue, you need to Attack the (Constructed structure) but you've no reference to it. You could use a Pick Every Unit action and find the closest eligible target. Store those structures in a Unit Group and you can narrow it down and keep it efficient.
So this Editor Tooltip is wrong? Or I'm misunderstanding it...
1691011080991.png
 
Level 18
Joined
Mar 16, 2008
Messages
721
As for giving Warden back to the AI after it destroys the corrupted building that it happened to walk by,

perhaps if unit in udg_warden_group dies then give warden back to yellow?
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,586
Yeah, you would Remove the dying unit from Warden_Group as well as anything else that comes to mind. I don't recommend using the Condition (Is unit in group) since it needs to enumerate over the entire group, so I would suggest marking the Corrupted units with a hidden ability or some kind of classification:
  • Events
    • Unit - A unit dies
  • Conditions
    • (Level of Corrupted Class (Hidden) for (Triggering unit)) Equal to 1
  • Actions
    • Unit Group - Remove (Triggering unit) from Warden_Group
    • Unit - Change ownership of Warden to Player 7 (Green) and Retain color
You can give this ability to all of your Corrupted units in the Object Editor or Add it to them in your "Finishes construction" trigger.
 
Last edited:
Level 18
Joined
Mar 16, 2008
Messages
721
:thumbs_up: :thumbs_up: :thumbs_up: :thumbs_up:
1691012974787.png

This seems to be working flawlessly. I added Spinnaker's library directly to the map script. I used [Aosp] Spinked Baracades (icon) and put it's command card to (0,-11) to hide it.

  • corrupt built
    • Events
      • Unit - A unit Finishes construction
    • Conditions
      • Or - Any (Conditions) are true
        • Conditions
          • (Unit-type of (Constructed structure)) Equal to Corrupted Moon Well (players)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Altar of Elders (corrupt player)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Hunter's Hall (corrupt player)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Ancient Protector (custom merc)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Chimaera Roost (corrupt player)
          • (Unit-type of (Constructed structure)) Equal to Corrupted Barrow Den (corrupt player)
    • Actions
      • Unit Group - Add (Constructed structure) to corrupted_player_buildings
      • Trigger - Add to warden near corrupt <gen> the event (Unit - A unit comes within 900.00 of (Constructed structure))
  • warden near corrupt
    • Events
    • Conditions
      • (Triggering unit) Equal to Warden (NPC) 0054 <gen>
    • Actions
      • Set VariableSet Warden_var = (Triggering unit)
      • Set VariableSet Warden_base_damage = (Base Damage of Warden (NPC) 0054 <gen> for weapon index 1)
      • Unit - Set Warden (NPC) 0054 <gen> movement speed to 400.00
      • Unit - Change ownership of Warden (NPC) 0054 <gen> to Neutral Passive and Retain color
      • Unit - Set Unit: Warden_var's Weapon Integer Field: Attack Damage Base ('ua1b')at Index:1 to Value: 5000
      • Custom script: set udg_Warden_Target = GetClosestUnitInGroup(GetUnitX(udg_Warden_var), GetUnitY(udg_Warden_var), udg_corrupted_player_buildings)
      • Unit - Order Warden (NPC) 0054 <gen> to Attack Warden_Target
  • corrupt building dies
    • Events
      • Unit - A unit owned by Player 1 (Red) Dies
      • Unit - A unit owned by Player 2 (Blue) Dies
      • Unit - A unit owned by Player 3 (Teal) Dies
      • Unit - A unit owned by Player 4 (Purple) Dies
      • Unit - A unit owned by Player 13 (Maroon) Dies
      • Unit - A unit owned by Player 14 (Navy) Dies
      • Unit - A unit owned by Player 15 (Turquoise) Dies
      • Unit - A unit owned by Player 16 (Violet) Dies
    • Conditions
      • (Level of Corrupted Class (hidden) for (Dying unit)) Equal to 1
    • Actions
      • Unit Group - Remove (Dying unit) from corrupted_player_buildings.
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Killing unit) Equal to Warden (NPC) 0054 <gen>
        • Then - Actions
          • Sound - Play maive_corrupt_atk_sound[(Random integer number between 0 and 2)]
          • Unit - Change ownership of Warden (NPC) 0054 <gen> to Player 5 (Yellow) and Retain color
          • Unit - Set Warden (NPC) 0054 <gen> movement speed to (Default movement speed of Warden (NPC) 0054 <gen>)
          • Unit - Set Unit: Warden_var's Weapon Integer Field: Attack Damage Base ('ua1b')at Index:1 to Value: Warden_base_damage
          • Hero - Set Warden (NPC) 0054 <gen> Hero-level to ((Hero level of (Killing unit)) + 1), Show level-up graphics
        • Else - Actions
 
Last edited:
Top