• 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.

[vJass] GetSourceUnit()

Level 10
Joined
Aug 19, 2008
Messages
491
Basically this lets you retrieve the source unit of a TriggerRegisterUnitInRange event.
Quite simple, yes, but personally I find it useful since Blizzard never made such a native.
This was made by me a while, but then a horrible flaw was discovered and it's now recoded by Element of Water
Not wasting much time on a fancy header with advertising, I hereby present GetSourceUnit library:


Requires TimerUtils and Table


JASS:
//===========================================================================
//==
//==                            GetSourceUnit()
//==                        
//==            This library lets you store and retrive the source                          
//==            unit of a TriggerRegisterUnitInRange() event.
//==            Especially useful for Auras.
//==           ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//==
//==    How to use
//==    ¯¯¯¯¯¯¯¯¯¯
//==    function TriggerRegisterUnitInRangeWithSource takes trigger whichTrigger, unit whichUnit, real range, boolexpr filter returns event
//==    - Just like the normal one, but saves whichUnit
//==
//==    function GetSourceUnit takes trigger whichTrigger, unit triggerUnit returns unit
//==    - Returns the source unit of the event
//==
//==    function ClearSource takes trigger whichTrigger returns nothing
//==    - Nullifies the source (just for cleanup)
//==
//==
//==    Requirements
//==    ¯¯¯¯¯¯¯¯¯¯¯¯
//==    - WarCraft III 1.24 by Blizzard (duh)
//==    - JassHelper by Vexorian
//==    - TimerUtils by Vexorian    
//==    - Table by Vexorian
//==
//==
//==    Important Notes
//==    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//==    - If two units (who are registered to the same trigger) are close together
//==      when a unit triggers the event, it may return the wrong source.
//==
//==    - If you want the GetSourceUnit() to null the source automatically
//==      set ClearSourceTime larger than 0.
//==
//==
//==    Version 2.1
//==    ¯¯¯¯¯¯¯¯¯¯¯
//==    Coded by Elements of Water
//==    Inspired by Cheezeman
//==
//===========================================================================
library InRangeWithSource initializer Init requires Table, TimerUtils
    globals
        private constant integer MAX_INSTANCES = 408000
        private constant integer MAX_UNITS = 100
        private constant real OFFSET = 20.00
        public real ClearSourceTime = 2.00

        private HandleTable ht
    endglobals

    private struct Data [MAX_INSTANCES]
        unit array registered[MAX_UNITS]
        real array registered_dist[MAX_UNITS]
        integer index = 0
        unit currentSource = null
    endstruct

    function TriggerRegisterUnitInRangeWithSource takes trigger whichTrigger, unit whichUnit, real range, boolexpr filter returns event
        local Data d
        if ht.exists(whichTrigger) then
            set d = ht[whichTrigger]
        else
            set d = Data.create()
            set ht[whichTrigger] = d
        endif

        if d.index < MAX_UNITS then
            set d.registered[d.index] = whichUnit
            set d.registered_dist[d.index] = range + OFFSET
            set d.index = d.index + 1
        else
            debug call BJDebugMsg("InRangeWithSource: Number of units registered to trigger exceeds MAX_UNITS. Please increase MAX_UNITS or register fewer units")
            return null
        endif

        return TriggerRegisterUnitInRange(whichTrigger, whichUnit, range, filter)
    endfunction

    private function ClearSourceTimed takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local Data d = GetTimerData(t)

        set d.currentSource = null

        call ReleaseTimer(t)
        set t = null
    endfunction

    function ClearSource takes trigger whichTrigger returns nothing
        local Data d = ht[whichTrigger]
        set d.currentSource = null
    endfunction

    function GetSourceUnit takes trigger whichTrigger, unit triggerUnit returns unit
        local Data d
        local integer i = 0
        local unit u
        local real udist
        local real dx
        local real dy
        local real dist
        local timer t
        if ht.exists(whichTrigger) then
            set d = ht[whichTrigger]
        else
            debug call BJDebugMsg("InRangeWithSource: Attempt to call GetSourceUnit on an unregistered trigger. Please register this trigger using TriggerRegisterUnitInRangeWithSource or do not call GetSourceUnit on this trigger.")
            return null
        endif

        if d.currentSource != null then
            return d.currentSource
        endif

        loop
            exitwhen i >= d.index

            set u = d.registered[i]
            set udist = d.registered_dist[i]

            set dx = GetUnitX(u) - GetUnitX(triggerUnit)
            set dy = GetUnitY(u) - GetUnitY(triggerUnit)
            set dist = SquareRoot(dx * dx + dy * dy)

            exitwhen dist <= udist

            set i = i + 1
        endloop

        if i >= d.index then
            debug call BJDebugMsg("InRangeWithSource: An unregistered unit fired a registered trigger. Please register this unit in the proper way.")
            return null
        endif

        set d.currentSource = u

        if ClearSourceTime > 0 then
            set t = NewTimer()
            call SetTimerData(t, d)
            call TimerStart(t, ClearSourceTime, false, function ClearSourceTimed)
        endif

        return u
    endfunction

    private function Init takes nothing returns nothing
        set ht = HandleTable.create()
    endfunction
endlibrary


1.0 - Initial Release

2.0 - The Elemental Era
- Complete remake of the code for optimization and being able to save more than 1 unit to a trigger.

2.1 - The Optional Release
- Added the option to remove the source unit manually or automatically
- Tweaked the Header
 
Last edited:
  • ClearSource = lame, just add a timer that will expire it after a few seconds.
  • GetHandleIdEx - 1.24, it's time for hashtables. They really aren't that hard to use.
  • You can inline your GetSourceUnit function -_-
    JASS:
        function GetSourceUnit takes trigger whichTrigger returns unit
            return InRangeSource[GetHandleIdEx( whichTrigger )]
        endfunction
  • I'm not really sure if there is a way to get the unit from this event, because I have personally never used this event so I can't be of any help there.
 
Level 10
Joined
Aug 19, 2008
Messages
491
ClearSource = lame, just add a timer that will expire it after a few seconds.
What if you want to have it a few seconds after? Not very common but still...
I could add a boolean checker, but if it's gonna use a timer I'm gonna need TimerUtils

GetHandleIdEx - 1.24, it's time for hashtables. They really aren't that hard to use.
Yes they seem simple. I've had a look on them already and I'm gonna see what I can do

You can inline your GetSourceUnit function -_-
JASS:
    function GetSourceUnit takes trigger whichTrigger returns unit
        return InRangeSource[GetHandleIdEx( whichTrigger )]
    endfunction
Right, I'll fix it...

I'm not really sure if there is a way to get the unit from this event, because I have personally never used this event so I can't be of any help there.
If there was such a native I wouldn't be making this system, now would I?


--


What if you wanted to register more than 1 unit in range event to the trigger?
You know, that's a very good point, I didn't think about that...
 
You know, that's a very good point, I didn't think about that...
I think the only way check exactly which unit is the source is by checking the position of the unit. I did some tests myself and the event seems to fire if the unit is about 20 distance more than the actual value you put in, so check if the unit is within the registered range + 20, and if it is, you've got the right source. The only problem with this is if two units registered to the same trigger are right next to each other and it enters both units radii at once - how would you discern which unit actually fired the event?
 
Level 10
Joined
Aug 19, 2008
Messages
491
So I'll add all the units to a group, then check if the unit is in range + 20 (or a bit more)?
I should actually make a Disc cause if there's another unit in the group closer, it will also be considered triggering unit.
But if a unit in the group is equally close (not too uncommon) then, just as you described, it can't determine which unit triggered it.

I could attach some number to all the units in the group (UnitUserData but with H2I instead) and when you call for the source you gotta pass the number to the function aswell, so both number and distance must be checked (which makes no sence now that I think about it)
Currently I have no idea how to do that.
 
You made a few mistakes in the script header. The function ClearSource is now private and is called by a timer. You should mention the variable InRangeWithSource_ClearSourceTime (it's how long it takes for the source to be recycled, can be dynamically set).

Oh, and the constants need explaining:

JASS:
private constant integer MAX_INSTANCES = 408000 //Maximum instances of the struct. This shouldn't be changed unless you experience efficiency problems. Changing it to 8191 will make it much more efficient.
        private constant integer MAX_UNITS = 100 //The maximum number of units which can be registered to a trigger. Increase it if you want to register more than 100 units.
        private constant real OFFSET = 20.00 //This shouldn't be changed unless you find the system isn't detecting units entering the source's radius right. Increase it a little bit if you encounter problems.

Also, you need to mention that it may and return the wrong source unit if two registered units are standing close together.

JASS:
//== local unit u = GetSourceUnit( trigger whichTrigger )
Finally, this is wrong. The function also needs to take the triggering unit. It should be:
JASS:
//== local unit u = GetSourceUnit( trigger whichTrigger, unit triggerUnit )

PS: To explain its efficiency - the first time you call it in a trigger, it may be quite slow (depending how many units are registered to the trigger). All calls for the same trigger after this, however, will be much faster, so you don't need to store the value in a variable unless you are completely freaked about speed.

So yeah, fix all that, and maybe explain about the efficiency thing...

EDIT: I think a test map is in order...
 

Attachments

  • InRangeWithSource.w3m
    46.7 KB · Views: 68
Last edited:
Level 10
Joined
Aug 19, 2008
Messages
491
If we use the TriggerRegisterAnyUnitEvent for an aura, why would we even want such a timer?
I say that it should be removed manually.
Oh and I updated the header.

Edit: I have no idea how to remove and replace the timer you're using to clear the source. I still wish that it will be removed manually.
 
Ok then, I'll change it so if you set ClearSourceTimer to 0.00, it doesn't clear it, and I'll add a public clear source function. Give me some time...

EDIT: You didn't change the header right. The function GetSourceUnit takes the trigger unit, the ClearSource function won't need to...

Wait, the timer thing is probably better for auras anyway - the source unit will stay the same, so it will actually make the system quicker. I'll still add a way of doing it manually though...

EDIT2: Done

JASS:
library InRangeWithSource initializer Init requires Table, TimerUtils
    globals
        private constant integer MAX_INSTANCES  = 408000
        private constant integer MAX_UNITS      = 100
        private constant real OFFSET            = 20.00
        public real ClearSourceTime             = 2.00
        
        private HandleTable ht
    endglobals
    
    private struct Data [MAX_INSTANCES]
        unit array registered[MAX_UNITS]
        real array registered_dist[MAX_UNITS]
        integer index = 0
        unit currentSource = null
    endstruct
    
    function TriggerRegisterUnitInRangeWithSource takes trigger whichTrigger, unit whichUnit, real range, boolexpr filter returns event
        local Data d
        if ht.exists(whichTrigger) then
            set d = ht[whichTrigger]
        else
            set d = Data.create()
            set ht[whichTrigger] = d
        endif
        
        if d.index < MAX_UNITS then
            set d.registered[d.index] = whichUnit
            set d.registered_dist[d.index] = range + OFFSET
            set d.index = d.index + 1
        else
            debug call BJDebugMsg("InRangeWithSource: Number of units registered to trigger exceeds MAX_UNITS. Please increase MAX_UNITS or register fewer units")
            return null
        endif
        
        return TriggerRegisterUnitInRange(whichTrigger, whichUnit, range, filter)
    endfunction
    
    private function ClearSourceTimed takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local Data d = GetTimerData(t)
        
        set d.currentSource = null
        
        call ReleaseTimer(t)
        set t = null
    endfunction
    
    function ClearSource takes trigger whichTrigger returns nothing
        local Data d = ht[whichTrigger]
        set d.currentSource = null
    endfunction
    
    function GetSourceUnit takes trigger whichTrigger, unit triggerUnit returns unit
        local Data d
        local integer i = 0
        local unit u
        local real udist
        local real dx
        local real dy
        local real dist
        local timer t
        if ht.exists(whichTrigger) then
            set d = ht[whichTrigger]
        else
            debug call BJDebugMsg("InRangeWithSource: Attempt to call GetSourceUnit on an unregistered trigger. Please register this trigger using TriggerRegisterUnitInRangeWithSource or do not call GetSourceUnit on this trigger.")
            return null
        endif
        
        if d.currentSource != null then
            return d.currentSource
        endif
        
        loop
            exitwhen i >= d.index
            
            set u = d.registered[i]
            set udist = d.registered_dist[i]
            
            set dx = GetUnitX(u) - GetUnitX(triggerUnit)
            set dy = GetUnitY(u) - GetUnitY(triggerUnit)
            set dist = SquareRoot(dx * dx + dy * dy)
            
            exitwhen dist <= udist
            
            set i = i + 1
        endloop
        
        if i >= d.index then
            debug call BJDebugMsg("InRangeWithSource: An unregistered unit fired a registered trigger. Please register this unit in the proper way.")
            return null
        endif
        
        set d.currentSource = u
        
        if ClearSourceTime > 0 then
            set t = NewTimer()
            call SetTimerData(t, d)
            call TimerStart(t, ClearSourceTime, false, function ClearSourceTimed)
        endif
        
        return u
    endfunction
    
    private function Init takes nothing returns nothing
        set ht = HandleTable.create()
    endfunction
endlibrary

Please see the test map I did. I think you should attach that to the main post (the code will need updating slightly).

Oh, and check the actual function declarations for what to put in the header since you don't seem to understand me properly when I explain it...
 
Level 8
Joined
Oct 3, 2008
Messages
367
Fanning through the code, it appears to be the best way to do this task.

Top notch work, except for one flaw. The function GetSourceUnit leaks the unit u. Returning it sadly doesn't necessary null the variable. Fix that, and I'll be happy to approve this.
 
JASS:
//===========================================================================
//==
//== GetSourceUnit()
//==
//== This library lets you store and retrive the source
//== unit of a TriggerRegisterUnitInRange() event.
//== Especially useful for Auras.
//== ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//==
//== How to use
//== ¯¯¯¯¯¯¯¯¯¯
//== function TriggerRegisterUnitInRangeWithSource takes trigger whichTrigger, unit whichUnit, real range, boolexpr filter returns event
//== - Just like the normal one, but saves whichUnit
//==
//== function GetSourceUnit takes trigger whichTrigger, unit triggerUnit returns unit
//== - Returns the source unit of the event
//==
//== function ClearSource takes trigger whichTrigger returns nothing
//== - Nullifies the source (just for cleanup)
//==
//==
//== Requirements
//== ¯¯¯¯¯¯¯¯¯¯¯¯
//== - WarCraft III 1.24 by Blizzard (duh)
//== - JassHelper by Vexorian
//== - TimerUtils by Vexorian
//== - Table by Vexorian
//==
//==
//== Important Notes
//== ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//== - If two units (who are registered to the same trigger) are close together
//== when a unit triggers the event, it may return the wrong source.
//==
//== - If you want the GetSourceUnit() to null the source automatically
//== set ClearSourceTime larger than 0.
//==
//==
//== Version 2.1
//== ¯¯¯¯¯¯¯¯¯¯¯
//== Coded by Elements of Water
//== Inspired by Cheezeman
//==
//===========================================================================
library InRangeWithSource initializer Init requires Table, TimerUtils
    globals
        private constant integer MAX_INSTANCES = 408000
        private constant integer MAX_UNITS = 100
        private constant real OFFSET = 20.00
public real ClearSourceTime = 2.00

        private HandleTable ht
    endglobals

    private struct Data [MAX_INSTANCES]
        unit array registered[MAX_UNITS]
        real array registered_dist[MAX_UNITS]
        integer index = 0
        unit currentSource = null
    endstruct

    function TriggerRegisterUnitInRangeWithSource takes trigger whichTrigger, unit whichUnit, real range, boolexpr filter returns event
        local Data d
        if ht.exists(whichTrigger) then
            set d = ht[whichTrigger]
        else
            set d = Data.create()
            set ht[whichTrigger] = d
        endif

        if d.index < MAX_UNITS then
            set d.registered[d.index] = whichUnit
            set d.registered_dist[d.index] = range + OFFSET
            set d.index = d.index + 1
        else
            debug call BJDebugMsg("InRangeWithSource: Number of units registered to trigger exceeds MAX_UNITS. Please increase MAX_UNITS or register fewer units")
            return null
        endif

        return TriggerRegisterUnitInRange(whichTrigger, whichUnit, range, filter)
endfunction

    private function ClearSourceTimed takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local Data d = GetTimerData(t)

        set d.currentSource = null

        call ReleaseTimer(t)
        set t = null
    endfunction

    function ClearSource takes trigger whichTrigger returns nothing
        local Data d = ht[whichTrigger]
        set d.currentSource = null
    endfunction

    function GetSourceUnit takes trigger whichTrigger, unit triggerUnit returns unit
        local Data d
        local integer i = 0
        local unit u
        local real udist
        local real dx
        local real dy
        local real dist
        local timer t
        if ht.exists(whichTrigger) then
            set d = ht[whichTrigger]
        else
            debug call BJDebugMsg("InRangeWithSource: Attempt to call GetSourceUnit on an unregistered trigger. Please register this trigger using TriggerRegisterUnitInRangeWithSource or do not call GetSourceUnit on this trigger.")
            return null
        endif

        if d.currentSource != null then
            return d.currentSource
        endif

        loop
            exitwhen i >= d.index

            set u = d.registered[i]
            set udist = d.registered_dist[i]

            set dx = GetUnitX(u) - GetUnitX(triggerUnit)
            set dy = GetUnitY(u) - GetUnitY(triggerUnit)
            set dist = SquareRoot(dx * dx + dy * dy)

            exitwhen dist <= udist

            set i = i + 1
        endloop

        if i >= d.index then
            debug call BJDebugMsg("InRangeWithSource: An unregistered unit fired a registered trigger. Please register this unit in the proper way.")
            return null
        endif

        set d.currentSource = u
        set u = null

        if ClearSourceTime > 0 then
            set t = NewTimer()
            call SetTimerData(t, d)
            call TimerStart(t, ClearSourceTime, false, function ClearSourceTimed)
        endif

        return d.currentSource
    endfunction

    private function Init takes nothing returns nothing
        set ht = HandleTable.create()
    endfunction
endlibrary
I think this fixes it?

I don't think Cheezman visits THW anymore, so could you edit the first post?
 
Top