• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[System] Trackable2

Level 8
Joined
Oct 3, 2008
Messages
367
Trackable oriented functions, duh. Fun, fun.

Requires Event by Jesus4Lyf.

How to use:

Event responses:
JASS:
GetTriggeringTrackable2()
//Returns the triggered trackable 2. Just like GetTriggerUnit
GetTrackedPlayer()
//Returns the player that clicked a Trackable2. Fun fun.

This is the regular interface. Same efficiency, but much shorter to type.
JASS:
//Static methods
.create(modelPath, x, y, z, facing)
//Takes the given info and creates a Trackable2 for all players. Also returns it.
.createForPlayer(modelPath, x, y, z, facing)
//Creates a Trackable2 for a single player.

//Methods
.registerClick(trigger whichTrigger)
//Registers a trigger for detecting Trackable2 clicks. It's an event.
.registerTrack(trigger whichTrigger)
//Does the same as click, but fires when the mouse hovers over the trackable.

//Variables
.X
//X coordinate of the Trackable2.
.Y
//Y coordinate of the Trackable2.
.Z
//Z coordinate of the Trackable2.
.Facing
//Facing angle of the Trackable2. Inaccurate, for some reason...
.Model
//The model of the Trackable2, most useful for special effects and the like.

Test script:
JASS:
scope Trackable2Test initializer Init

globals
    private integer Count = 0
endglobals

private function Actions takes nothing returns nothing
    local Trackable2 i = GetTriggeringTrackable2()
    set Count = Count + 1
    call BJDebugMsg("------------------------------------")
    call BJDebugMsg("Clicks detected thus far: " + I2S(Count))
    //Get the triggering trackable...
    call BJDebugMsg("This trackable's model: " + i.Model)
    call BJDebugMsg("This trackable's X: " + R2S(i.X))
    call BJDebugMsg("This trackable's Y: " + R2S(i.Y))
    call BJDebugMsg("This trackable's Z: " + R2S(i.Z))
    call BJDebugMsg("This trackable's Facing: " + R2S(i.Facing))
    call BJDebugMsg("The triggering player: " + GetPlayerName(GetTrackedPlayer()))
    //Displaying all the stats! Soooo complicated.
endfunction

private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    local integer i = 20
    local Trackable2 tr
    loop
        exitwhen i == 0
        set tr = Trackable2.create("units\\human\\peasant\\peasant.mdl", GetRandomReal(-1000, 1000), GetRandomReal(-1000, 1000), GetRandomReal(0, 200), GetRandomReal(1, 360))
        call tr.registerClick(t)
        set i = i - 1
    endloop
    call TriggerAddAction(t, function Actions)
endfunction

endscope

This is the alternate interface, recommended for those who don't know structs.

JASS:
CreateTrackable2 takes string modelPath, real x, real y, real z, real facing
//This creates a Trackable2 and returns it for event registry and the like.
CreateTrackable2ForPlayer takes string modelPath, real x, real y, real z, real facing, player forPlayer
//Creates a Trackable2 for a single player.

TriggerRegisterTrackable2HitEvent takes trigger whichTrigger, Data d
//Registers a Trackable2 to a trigger. Every time a Trackable2 is clicked, the trigger will fire.
TriggerRegisterTrackable2TrackEvent takes trigger whichTrigger, Data d
//The same as HitEvent, but fires when the mouse hovers over the Trackable2.

GetTriggeringTrackable2 takes nothing
//Returns the triggering Trackable2. Much better than the IsTriggeringTrackable(whatever) of version 1.
GetTrackedPlayer takes nothing
//Returns the player that clicked the Trackable2.

GetTrackable2X takes Data which
//Returns the X position of the given Trackable2.
GetTrackable2Y takes Data which
//Returns the Y position of the given Trackable2.
GetTrackable2Z takes Data which
//Returns the Z position of the given Trackable2.
GetTrackable2Facing takes Data which
//Returns the facing angle of the given Trackable2. For some reason, trackable facing is inaccurate. Strange.
GetTrackable2ForPlayer takes Data which
//Returns the player the Trackable2 was created for, if there is one. If there is not, it will return null.
GetTrackable2Model takes Data which
//Returns the model of the given Trackable2. You could probably use this to add a special effect somewhere.
GetTrackable2ForTrigger takes Data which
//Returns the last trigger the given Trackable2 was registered to. Totally useless, but it's there...

SetTrackable2Data takes Data whichTrackable, integer whatData
//Sets the user data of the given Trackable2 to the given value.
GetTrackable2Data takes Data whichTrackable
//Returns the user data set before. If there is no data, it returns 0.

Alternate interface test script:
JASS:
scope Test initializer Init

private function Actions takes nothing returns nothing
    local integer i = GetTriggeringTrackable2()
    //Get the triggering trackable...
    call BJDebugMsg("This trackable's model: " + GetTrackable2Model(i))
    call BJDebugMsg("This trackable's X: " + R2S(GetTrackable2X(i)))
    call BJDebugMsg("This trackable's Y: " + R2S(GetTrackable2Y(i)))
    call BJDebugMsg("This trackable's Z: " + R2S(GetTrackable2Z(i)))
    call BJDebugMsg("This trackable's Facing: " + R2S(GetTrackable2Facing(i)))
    call BJDebugMsg("The triggering player: " + GetPlayerName(GetTrackedPlayer()))
    //Displaying all the stats! Soooo complicated.
endfunction

private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    //Create our trigger.
    local integer i = CreateTrackable2("units\\human\\peasant\\peasant.mdl", 0, 0, 0, 270)
    //Creating the trackable itself. Important step.
    call TriggerRegisterTrackable2HitEvent(t, i)
    //Register our trackable, and...
    call TriggerAddAction(t, function Actions)
    //If you want, you can add more trackables.
    set i = CreateTrackable2("units\\human\\peasant\\peasant.mdl", 200, 200, 200, 90)
    call TriggerRegisterTrackable2TrackEvent(t, i)
endfunction

endscope

I don't know how to explain it any better. Now, the code itself.

<3, Azlier and Jesus4Lyf.
JASS:
library Trackable2 requires Event

// Hah! There are no configurables. What did you expect?

private struct Cache
    Trackable2 theTrack
    player forPlayer
endstruct

globals
    private Cache TrigCache
    private Trackable2 LastCreatedTrackable2
endglobals

private function SetTriggerData takes trigger t, integer i returns nothing
    loop
        exitwhen i == 0
        call TriggerExecute(t)
        set i = i - 1
    endloop
endfunction

function GetTrackedPlayer takes nothing returns player
    return TrigCache.forPlayer
endfunction

function GetTriggeringTrackable2 takes nothing returns Trackable2
    return TrigCache.theTrack
endfunction

function GetLastCreatedTrackable2 takes nothing returns Trackable2
    return LastCreatedTrackable2
endfunction

struct Trackable2
    
    private Event onClickEv
    private Event onTrackEv
    
    private static boolexpr onClick
    private static boolexpr onTrack
    
    real X
    real Y
    real Z
    real Facing
    string Model
    
    integer userData
    
    private static method onClickFunc takes nothing returns boolean
        set TrigCache = GetTriggerExecCount(GetTriggeringTrigger())
        call TrigCache.theTrack.onClickEv.fire()
        return false
    endmethod
    
    private static method onTrackFunc takes nothing returns boolean
        set TrigCache = GetTriggerExecCount(GetTriggeringTrigger())
        call TrigCache.theTrack.onTrackEv.fire()
        return false
    endmethod
    
    method registerClick takes trigger t returns nothing
        call .onClickEv.register(t)
    endmethod
    
    method registerTrack takes trigger t returns nothing
        call .onTrackEv.register(t)
    endmethod
    
    static method create takes string modelPath, real x, real y, real z, real facing returns thistype
        local thistype this = thistype.allocate()
        local Cache c
        local integer i = 11
        local string s = ""
        local trigger t
        local trackable tr
        local destructable platform = CreateDestructableZ('Otip', x, y, z, 0, 1, 0)
        set .onClickEv = Event.create()
        set .onTrackEv = Event.create()
        set .X = x
        set .Y = y
        set .Z = z
        set .Facing = facing
        set .Model = modelPath
        loop
            exitwhen i < 0
            if GetLocalPlayer() == Player(i) then
                set s = modelPath
            else
                set s = ""
            endif
            set tr = CreateTrackable(s, x, y, facing)
            set t = CreateTrigger()
            set c = Cache.create()
            set c.theTrack = this
            set c.forPlayer = Player(i)
            call SetTriggerData.execute(t, c)
            call TriggerAddCondition(t, .onClick)
            call TriggerRegisterTrackableHitEvent(t, tr)
            set t = CreateTrigger()
            call SetTriggerData.execute(t, c)
            call TriggerAddCondition(t, .onTrack)
            call TriggerRegisterTrackableTrackEvent(t, tr)
            set i = i - 1
        endloop
        call RemoveDestructable(platform)
        set platform = null
        set LastCreatedTrackable2 = this
        return this
    endmethod
    
    static method createForPlayer takes player forPlayer, string modelPath, real x, real y, real z, real facing returns thistype
        local thistype this = thistype.allocate()
        local Cache c
        local string s = ""
        local trigger t
        local trackable tr
        local destructable platform = CreateDestructableZ('Otip', x, y, z, 0, 1, 0)
        set .X = x
        set .Y = y
        set .Z = z
        set .Facing = facing
        set .Model = modelPath
        if GetLocalPlayer() == forPlayer then
            set s = modelPath
        endif
        set tr = CreateTrackable(s, x, y, facing)
        set t = CreateTrigger()
        set c = Cache.create()
        set c.theTrack = this
        set c.forPlayer = forPlayer
        call SetTriggerData.execute(t, c)
        call TriggerAddCondition(t, .onClick)
        call TriggerRegisterTrackableHitEvent(t, tr)
        set t = CreateTrigger()
        call SetTriggerData.execute(t, c)
        call TriggerAddCondition(t, .onTrack)
        call TriggerRegisterTrackableTrackEvent(t, tr)
        call RemoveDestructable(platform)
        set platform = null
        set LastCreatedTrackable2 = this
        return this
    endmethod
        
    private static method onInit takes nothing returns nothing
        set .onClick = Condition(function thistype.onClickFunc)
        set .onTrack = Condition(function thistype.onTrackFunc)
    endmethod
    
endstruct

// Alternate interface, recommended for those who fail at structs:

function CreateTrackable2 takes string modelPath, real x, real y, real z, real facing returns Trackable2
    return Trackable2.create(modelPath, x, y, z, facing)
endfunction

function CreateTrackable2ForPlayer takes player forPlayer, string modelPath, real x, real y, real z, real facing returns Trackable2
    return Trackable2.createForPlayer(forPlayer, modelPath, x, y, z, facing)
endfunction

function TriggerRegisterTrackable2HitEvent takes trigger whichTrigger, Trackable2 which returns nothing
    call which.registerClick(whichTrigger)
endfunction

function TriggerRegisterTrackable2TrackEvent takes trigger whichTrigger, Trackable2 which returns nothing
    call which.registerTrack(whichTrigger)
endfunction

function SetTrackable2Data takes Trackable2 which, integer data returns nothing
    set which.userData = data
endfunction

function GetTrackable2Data takes Trackable2 which returns integer
    return which.userData
endfunction

//! textmacro Trackable2__InterfaceFunc takes NAME, TYPE
function GetTrackable2$NAME$ takes Trackable2 which returns $TYPE$
    return which.$NAME$
endfunction
//! endtextmacro

//! runtextmacro Trackable2__InterfaceFunc("Model", "string")
//! runtextmacro Trackable2__InterfaceFunc("X", "real")
//! runtextmacro Trackable2__InterfaceFunc("Y", "real")
//! runtextmacro Trackable2__InterfaceFunc("Z", "real")
//! runtextmacro Trackable2__InterfaceFunc("Facing", "real")
    
endlibrary

v3.1
Doubled instance limit
Minor efficiency gain in trackable creation

v3.0 - Total Rewrite
Murdered Al Gore, put head on pike.
Shorter, yet more efficient.

v2.4
Ended global warming
Removed an O(n) search
Increased instance limit (secretly)

v2.3
Table version now most reccommended.
Trackables can now hold user data, because someone actually needed that.

v2.2
Table version available
Increased efficiency very minorly
GetTrackedPlayer() no longer requires an argument

v2.1
Can now register multiple trackables to the same trigger.

v2.0 - Total rewrite
Much more usable. Better internal coding, as well.

v1.5
Optimized some (thanks to Romek).
Documented CreateTrackableForForce.

v1.4b
Added CreateTrackableForForce.

v1.4
Readded CreateTrackableForPlayer, because it has use after all

v1.3
Removed silly GetTrackable, replaced with IsTriggerTrackable()
Fixed minor documentation error

v1.2
Added GetTrackingPlayer()

v1.1
Added Z support, fixed demo map

v1.0
Initial release
 

Attachments

  • Trackable2 Demo Map.w3x
    23.9 KB · Views: 336
Last edited:
Level 8
Joined
Oct 3, 2008
Messages
367
It's not like the Get functions aren't inlined...

I tried renaming the struct, but when I try to use it in the test scope, it tells me there is no such type as 'Trackable2', even though I have an unprefixed struct named Trackable2 in my library. :eekani:

Clearly I'm forgetting something important.
 
Level 8
Joined
Oct 3, 2008
Messages
367
I know about top-bottom parsing. My struct is in a library, I'm trying to make a struct type in a scope. Jasshelper isn't letting me. I used the exact struct name, no prefix whatsoever. Oh, and I can't use vJass at the moment. Something's going wrong...
 
Level 8
Joined
Oct 3, 2008
Messages
367
Second total rewrite completed. This version is about as good as it gets :thumbs_up:.

EDIT: Is there something... wrong with textmacros on the Hive?

My script shows this:

//! textmacro Trackable2__InterfaceFunc takes NAME, TYPE
function GetTrackable2$NAME$GetTrackable2 takes Trackable2 which returns $TYPE$
return which.$NAME$
endfunction
//! endtextmacro

while, if you quote my post, it clearly shows

//! textmacro Trackable2__InterfaceFunc takes NAME, TYPE
function GetTrackable2$NAME$ takes Trackable2 which returns $TYPE$
return which.$NAME$
endfunction
//! endtextmacro

I mean, seriously. Wtf?

(Can't put in JASS tags, or it bugs)
 
Last edited:
Level 8
Joined
Aug 4, 2006
Messages
357
When I first looked through this, I did not know what trackables were. I tested the test map and was like "Uh, isn't this just a bunch of locusted units that detect unit select events?" Then I did a lil research on trackables and actually looked through the code and realized how awesome this is. Here is what I would suggest changing in the test map, so that your average JASS user will understand what this does:

Replace your current Trackable2 Test trigger with this:
JASS:
scope Trackable2Test initializer Init

globals
    private integer ClickCount = 0
    private integer TrackCount = 0
endglobals

private function ClickActions takes nothing returns nothing
    local Trackable2 i = GetTriggeringTrackable2()
    set ClickCount = ClickCount + 1
    call BJDebugMsg("------------------------------------")
    call BJDebugMsg("Clicks detected thus far: " + I2S(ClickCount))
    //Get the triggering trackable...
    call BJDebugMsg("This trackable's model: " + i.Model)
    call BJDebugMsg("This trackable's X: " + R2S(i.X))
    call BJDebugMsg("This trackable's Y: " + R2S(i.Y))
    call BJDebugMsg("This trackable's Z: " + R2S(i.Z))
    call BJDebugMsg("This trackable's Facing: " + R2S(i.Facing))
    call BJDebugMsg("The triggering player: " + GetPlayerName(GetTrackedPlayer()))
    //Displaying all the stats! Soooo complicated.
endfunction

private function TrackActions takes nothing returns nothing
    local Trackable2 i = GetTriggeringTrackable2()
    set TrackCount = TrackCount + 1
    call BJDebugMsg("------------------------------------")
    call BJDebugMsg("Tracks detected thus far: " + I2S(TrackCount))
    //Get the triggering trackable...
    call BJDebugMsg("This trackable's model: " + i.Model)
    call BJDebugMsg("This trackable's X: " + R2S(i.X))
    call BJDebugMsg("This trackable's Y: " + R2S(i.Y))
    call BJDebugMsg("This trackable's Z: " + R2S(i.Z))
    call BJDebugMsg("This trackable's Facing: " + R2S(i.Facing))
    call BJDebugMsg("The triggering player: " + GetPlayerName(GetTrackedPlayer()))
    //Displaying all the stats! Soooo complicated.
endfunction

private function Init takes nothing returns nothing
    local trigger clickTrig = CreateTrigger()
    local trigger trackTrig = CreateTrigger()
    local integer i = 20
    local Trackable2 tr
    loop
        exitwhen i == 0
        set tr = Trackable2.create("units\\human\\peasant\\peasant.mdl", GetRandomReal(-1000, 1000), GetRandomReal(-1000, 1000), GetRandomReal(0, 200), GetRandomReal(1, 360))
        call tr.registerClick(clickTrig)
        call tr.registerTrack(trackTrig)
        set i = i - 1
    endloop
    call TriggerAddAction(clickTrig, function ClickActions)
    call TriggerAddAction(trackTrig, function TrackActions)
endfunction

endscope
If you use that, people will be like ":O how do you detekt teh mouseover events?!? +rep 5/5"
 
Level 8
Joined
Aug 4, 2006
Messages
357
Silvenon, I guess the functions might be easier to use than the methods for people who aren't familiar with vJASS. The functions also imitate the way native trackable functions work, which is probably a good thing. Idk, I don't really see any downsides to using functions to call methods since they should get inlined anyway.

Nevertheless, I think he should show off the capabilities of trackables to track the mouse in the test map, since that's really what trackables are good for.
 
Top