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

[Snippet] UnitInRangeOfType

Level 5
Joined
Dec 4, 2006
Messages
110
UnitInRangeOfType

Version 1.03

Requirements
- JASS NewGen
- Event
- AIDS
- Table

JASS:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ UnitInRangeOfType ~~ By Sevion ~~ Version 1.03 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//  What is UnitInRangeOfType?
//         - UnitInRangeOfType implements an intuitive method of registering InRange events for unit-types.
//
//    =Pros=
//         - Blizzard-like Event Registration.
//         - Very intuitive to use.
//
//    =Cons=
//         - Fairly inefficient at the moment, but this isn't designed for heavy use right now anyhow.
//
//    Functions:
//         - TriggerRegisterUnitInRangeOfType( trigger whichTrigger, integer whichType, real range, boolexpr filter )
//
//           This function is used exactly as other event registration functions are. Returns an Event.
//
//         - GetCenterUnit()
//
//         - This function is used just as GetEnteringUnit is used. It's for retrieving the unit that was approached.
//           This can be used in the filter function as well.
//
//  Details:
//         - UnitInRangeOfType is used just as other Blizzard event registration functions are.
//
//         - Very intuitive to use because of this fact.
//
//         - I am still in the process of optimization, which should come in later updates.
//
//  How to import:
//         - Create a trigger named UnitInRangeOfType.
//         - Convert it to custom text and replace the whole trigger text with this.
//
//  Thanks:
//         - Bribe for some optimization suggestions.
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library UnitInRangeOfType requires AIDS, Event, Table
    globals
        private key EVENT_KEY
        private key TRIGGER_KEY
        private key RANGE_KEY
        private key TYPE_KEY
        private key UNIT_KEY
        private key DESTROY_KEY
    endglobals
    
    private struct data extends array
        readonly static trigger filterCheck
        public static hashtable hash
        readonly static unit lastcenter
        public static Table idlist
        
        private static method AIDS_filter takes unit u returns boolean
            return thistype.idlist.has(GetUnitTypeId(u))
        endmethod
        
        private static method OnEvent takes nothing returns boolean
            local unit u
            
            set u = thistype.lastcenter
            set thistype.lastcenter = LoadUnitHandle(thistype.hash, GetHandleId(GetTriggeringTrigger()), UNIT_KEY)
            
            if ( TriggerEvaluate(data.filterCheck) ) then
                call FireEvent(Event(LoadInteger(data.hash, LoadInteger(thistype.hash, GetHandleId(GetTriggeringTrigger()), TYPE_KEY), EVENT_KEY)))
            else
                set thistype.lastcenter = u
            endif
            
            set u = null
            return false
        endmethod
        
        private method AIDS_onCreate takes nothing returns nothing
            local trigger t = CreateTrigger()
            local integer i = GetUnitTypeId(this.unit)
            
            call TriggerRegisterUnitInRange(t, this.unit, LoadReal(thistype.hash, i, RANGE_KEY), null)
            call TriggerAddCondition(t, Condition(function thistype.OnEvent))
            call SaveInteger(thistype.hash, GetHandleId(t), TYPE_KEY, i)
            call SaveUnitHandle(thistype.hash, GetHandleId(t), UNIT_KEY, this.unit)
            call SaveTriggerHandle(thistype.hash, i, DESTROY_KEY, t)
            
            set t = null
        endmethod
        
        private static method AIDS_onInit takes nothing returns nothing
            set thistype.hash = InitHashtable()
            set thistype.lastcenter = null
            set thistype.filterCheck = CreateTrigger()
            set thistype.idlist = Table.create()
        endmethod
        
        private method AIDS_onDestroy takes nothing returns nothing
            call DestroyTrigger(LoadTriggerHandle(thistype.hash, GetHandleId(this.unit), DESTROY_KEY))
        endmethod
        
        //! runtextmacro AIDS()
    endstruct
    
    function GetCenterUnit takes nothing returns unit
        return data.lastcenter
    endfunction
    
    function TriggerRegisterUnitInRangeOfType takes trigger whichTrigger, integer whichType, real range, boolexpr filter returns Event
        local Event e
        
        if ( HaveSavedInteger(data.hash, whichType, EVENT_KEY) ) then
            set e = LoadInteger(data.hash, whichType, EVENT_KEY)
        else
            set e = Event.create()
            call SaveInteger(data.hash, whichType, EVENT_KEY, e)
        endif
        
        call TriggerRegisterEvent(whichTrigger, e)
        
        set data.idlist[whichType] = 1
        call SaveTriggerHandle(data.hash, whichType, TRIGGER_KEY, whichTrigger)
        call SaveReal(data.hash, whichType, RANGE_KEY, range)
        
        call TriggerAddCondition(data.filterCheck, filter)
        
        return e
    endfunction
endlibrary
JASS:
struct a extends array
    private static unit u
    implement Alloc
    
    private static method filt2 takes nothing returns boolean
        call BJDebugMsg("Filter 2")
        return true
    endmethod
    
    private static method filt takes nothing returns boolean
        call BJDebugMsg("Filter")
        return true
    endmethod
    
    private static method act takes nothing returns nothing
        call BJDebugMsg("Act")
        call KillUnit(GetEnteringUnit()) // Kills the Paladin
        //call KillUnit(GetCenterUnit()) // Kills the Footman
    endmethod
    
    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        set thistype.u = CreateUnit(Player(0), 'hfoo', 0, 0, 0)
        call CreateUnit(Player(0), 'Hpal', 550, 0, 0)
        call CreateUnit(Player(0), 'Hpal', -550, 0, 0)
        call TriggerRegisterUnitInRangeOfType(t, 'hfoo', 500, Condition(function thistype.filt))
        call TriggerRegisterUnitInRangeOfType(t, 'hfoo', 500, Condition(function thistype.filt2))
        call TriggerAddAction(t, function thistype.act)
    endmethod
endstruct

Still needs optimization work, but works nonetheless :thumbs_up:

Updates
- Version 1.03: Removed old code that was causing syntax errors.
- Version 1.02: Removed idlist, filterlist, and Alloc. Added Table and a static trigger to replace them.
- Version 1.01: Moved //! runtextmacro AIDS() to the bottom of the struct. Nulled some things.
- Version 1.00: Release.
 
Last edited:
Level 5
Joined
Dec 4, 2006
Messages
110
Good point. I originally had an older version that worked on the same principles, but wasn't as finished as this. I c'n'p'd the old code to here and never removed the key usage :p

However, don't keys simply compile to constant integers when JASSHelper runs? If that's the case then it shouldn't really matter, right?

The unit variable was a last minute introduction so that GetCenterUnit could be used in the filters. I forgot to null it :eek:

Fixed all the above as well as moved the AIDS textmacro to the bottom to avoid useless trigger evaluations.
 
Level 5
Joined
Dec 4, 2006
Messages
110
JASS:
                exitwhen i == 0
                set b = b or ( id == idlist[i].id )
                set i = i - 1

Change that to:

JASS:
exitwhen i == 0 or id == idlist[i].id
set i = i - 1

What is the point of the idlist struct anyway?

Will do. I have the idlist struct to keep a simple linked list of all the unit-type id's that have been registered.

function TriggerRegisterUnitInRangeOfType takes trigger whichTrigger, integer whichType, real range, boolexpr filter returns Event

So what you specify a range from middle of nowhere, you don't specify a center unit ?

No, you specify a range from any unit of type whichType.

This is a generic form of UnitInRange. Instead of taking a unit, you take a unit-type. It will then proceed to fire an event whenever a unit (any unit) comes within range of a unit of type whichType.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Sure. Every map needs to handle when a type X of unit enter within the range of any unit. Seriously ...

You don't get it, i mean create a library like that

This is generic since we don't have a GetCenterUnit() native function, and then when it's done you can make your library using it.
 
Level 5
Joined
Dec 4, 2006
Messages
110
It's possible. And I was asked multiple times for something like this.

That kind of library is totally possible. In fact, using this code, I could easily change it to that.

This snippet was a project spawned of request, and so it wasn't made with submission in mind. If it were originally planned for this, I would have made them separate libraries. In fact, it would be a simple thing to create a GetCenterUnit() library with hooks.
 
Level 5
Joined
Dec 4, 2006
Messages
110
Eh? That doesn't seem like the right approach at all. What about something like:

JASS:
hook TriggerRegisterUnitInRange hook_TriggerRegisterUnitInRange

function GetSourceUnit takes nothing returns unit
    return LoadUnitHandle(hashtable, GetHandleId(GetTriggeringTrigger(), 0)
endfunction

private function hook_TriggerRegisterUnitInRange takes trigger whichTrigger, unit whichUnit, real range, boolexpr filter returns event
    call SaveUnitHandle(hashtable, GetHandleId(whichTrigger), 0, whichUnit)

    return null
endfunction

Ofc, this has its limitations, but I was thinking about if there were a way to get the last returned value (e.g. in this case the event) it'd be much better. I'm not sure if the "new" return bug works anymore (I doubt it does), but if there was something similar, we could easily implement it this way with GetTriggerEventId. If we had a way to get the event, we could simply use the key from the trigger and the child from the event. That would get us perfection. Technically we could get the event by requiring the user to do something like set GlobalEvent = TriggerRegisterUnitInRange(...), but I'm a fanboy of making things simple.

Anyhow, any other stuff I can fix with my snippet to get approval?
 
Well the problem with TriggerRegisterUnitInRange is that it doesn't also
fire an event when the unit is no longer in range, which basically requires
you to be using periodic checks any way -

Might as well be doing periodic group enums.

As for the events, I don't know why you're making this so complicated.
The boolexprs (filterlist struct) should be loaded onto a static trigger
that never loses its conditions, never needs to be recreated, just like
Nestharus does in his Event library.

The "idlist" struct still doesn't make sense to me. You don't have any
destructor for it, fine, but then why not make it "extend array" and
why not make it filter out id's that already exist?

You can also make it non-search based by using a Table instance with
the unitid as a key.
 
Level 5
Joined
Dec 4, 2006
Messages
110
Ah, that's a good suggestion.

You know, while I was coding this I was looking at filterlist and idlist thinking "I feel like I could optimize this... Can't think of a way... Someone else will figure it out for me :D *Post*".

I implemented a Table and on register it simply saves 1 to the Table at key unit-type.

Now it's a simple return thistype.idlist.has(GetUnitTypeId(u))
 
Last edited by a moderator:
Level 17
Joined
Apr 27, 2008
Messages
2,455
But that's clearly not a good idea, Bribe is a good guy, but he is busy, just wait, he will review it sooner or later like he said.

Personnaly i don't think it's worth a submission, at least not in the current state, just like i said, and also because of what Bribe said, but it's me.
 
Level 5
Joined
Dec 4, 2006
Messages
110
All you've said is from your misunderstanding of what this does.

This could be used in many situations. For example spells or towers.

The only problem is the thing Bribe pointed out: No UnitOutOfRange events exist, which if you need such functionality you should be using Group Enums anyhow.

Other than that one thing, there's no reason it should be submitted.
 
If you want a UnitOutOfRange event, all you have to do is create a link between 2 units via a struct instance and when the onMove event in IsUnitMoving fires, you'd check the distance between the units (retrieving the instance data from a hashtable lookup to find the correct distance.).
If the distance is greater than the range, you'd destroy the link between the units and reset the hashtable value of (Parent: unit1, Child: unit2) and finally, you'd fire the UnitOutOfRange event :p

See how easy that was? :D
I came up with it on the spot just now ^^
 
With a resource like this I can't think why you'd want just a unit-in-range-of-type, maybe it should be a "unit in range matching condition" but then the user might as well just use "unit in range".

It's already a hard enough system to implement just because each unit-in-range trigger creates an event of its own, making lots of handles. It apparently doesn't work well with in-game auras either (question Nestharus on this one though). Finally, I recommend working with FirstOfGroup loops with null enum filter, instead of this, because it lets you do so without creating an extra group and without needing all this complex and ugly trigger business.

Graveyarded. PM me if you disagree.
 
Top