HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Trackables Script

09-07-2009, 03:45 PM#1
Flame_Phoenix
Hi people. I wanted to use trackables for one of my projects, however now I see it is not the good solution. However I made this library and I think it can help other people in a nice way, so now I am transforming it into a resource and I intend to upload it.

I also took the liberty of adding some extra components to the basic stuff such as filling a region or area with trackables.
So far it needs Table, but I would like to make this resource auto-sufficient, I would like to know how to use hastables on my own.

I am open to suggestions, maybe I will create something like a "trackable group" later using a list, but I still don't know it can be of any use.

Collapse JASS:
//===========================================================================
//This simple library defines a trackable. It allows to store information
//about a trackable such as it's position, owner, facing angle, etc and it
//also allows for the user to attach a structure to the trackable. 
//This library also allows to create trackables over a region.
//
//Version 1.2.0
//
//Author:
// - Flame_Phoenix
//
//Requirements:
// - Table from Vexorian
//
//Credits:
// - Kattana, for his tutorial
// - Hans_Maulwurf, for code inspiration, ideas and typo reports
//
//Methods:
//static method advancedCreate takes real x, real y, real facing, string path, player owner, 
//code onTrack, code onClick returns Trackable
// - Create a Trackable object with the given data
//
////static method create takes real x, real y, player owner, 
//code onTrack, code onClick returns Trackable
// - Create a Trackable object with the given data. In this case the default the script will
//use DEFAULT_ANGLE and DEFAULT_PATH to create the trackable.
//
//method onDestroy takes nothing returns nothing
// - trackables cannot be destroyed. However we can disable and destroy the triggers that
//run them, and we can remove the Trackable object from table. This is what this method does.
//
//Members you can use:
//this.x - the X coordinate of the trackable. Has read permissions.
//this.y - the Y coordinate of the trackable. Has read permissions.
//this.facing - the facing angle of the trackable. Has read permissions.
//this.path - the path (model being used) of the tracable. Has read permissions.
//this.tracker - the trackable itself. Has read permissions.
//this.owner - the owner of the trackable. Has read permissions.
//this.data - it can be a structure attached to the Trackable object. Has write and read permissions.
//this.onClickTrgAction - this is the function that runs when you click the trackable. Has write permissions.
//this.onTrackTrgAction - this is the function that runs when you over the trackable. Has write permissions.
//
//Note: to change a member just do "myTrack.data = blablabal"
//Note: to access a member just do "local real myX = myTrack.x"
//
//Functions:
//function CreateTrackedRegion takes rect where, real trackSize, string path, real facing, player owner, 
//code onTrack, code onClick returns nothing
// - This function fills a region with trackables. There is little to say.
//
//function CreateTrackedArea takes real topLeftX, real topLeftY, real width, real height, real trackSize, 
//string path, real facing, player owner, code onTrack, code onClick returns nothing
// - This function does the same thing the previous function does, but it doesn't require a rect. Instead it
//takes the top left corner of the area and then it lets the magic happen.
//
//NOTE: you must be carefull when using functions CreateTrackedRegion and CreatedTrackedArea. If the area or 
//the region to fill is too big, you will hit the oplimit of the thread and the region will be 100% filled
//with trackables.
//
//function GetTrackableData takes trackable whichTrackable returns Trackable
// - this is used to get the information from a trackable. See the example:
//    private function TrackableHit takes nothing returns nothing
//        local Trackable tracker = GetTrackableData(GetTriggeringTrackable())
//        call BJDebugMsg("cliked!")
//        call BJDebugMsg("x = " + R2S(tracker.x))
//        call BJDebugMsg("y = " + R2S(tracker.y))
//    endfunction
//
//===========================================================================
library Trackable initializer Init requires Table
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
    globals 
    //destroying triggers has some obscure effects. In this case I allow it, 
    //but if you think this is causing porblems, just set the boolean valu to false.
    //Know that no matter the value this boolean flag holds, the triggers of a trackable
    //are always disabled when you call onDestroy.
        public constant boolean DESTROY_TRIGGERS = true
        public constant string DEFAULT_PATH = "units\\human\\Peasant\\Peasant.mdl"
        public constant real DEFAULT_ANGLE = 0.
    endglobals
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
    globals
        private HandleTable activeTable //your private Table's global variable
    endglobals
    
    struct Trackable
        private real tcX
        private real tcY
        private real tcFacing
        private string tcPath = ""
        private trackable track
        private player tcOwner
        private integer tcData
        private trigger onTrackTrg = null
        private trigger onClickTrg = null
        
        static method advancedCreate takes real x, real y, real facing, string path, player owner, code onTrack, code onClick returns Trackable
            local Trackable this = Trackable.allocate()
            
            if GetLocalPlayer() == owner then
                set .tcPath = path 
            endif
            
            set .tcX = x
            set .tcY = y
            set .tcFacing = facing
            set .tcOwner = owner
            set .track = CreateTrackable(.tcPath, .tcX, .tcY, .tcFacing)
            
            if onClick != null then
                set .onClickTrg = CreateTrigger()
                call TriggerRegisterTrackableHitEvent(.onClickTrg, .track)
                call TriggerAddAction(.onClickTrg, onClick)
            endif
            
            if onTrack != null then 
                set .onTrackTrg = CreateTrigger()
                call TriggerRegisterTrackableTrackEvent(.onTrackTrg, .track)
                call TriggerAddAction(.onTrackTrg, onTrack)
            endif

            //save this struct into Table
            set activeTable[.track] = this 
            
            return this
        endmethod
        
        static method create takes real x, real y, player owner, code onTrack, code onClick returns Trackable
            local Trackable this = Trackable.allocate()
            
            if GetLocalPlayer() == owner then
                set .tcPath = DEFAULT_PATH
            endif
            
            set .tcX = x
            set .tcY = y
            set .tcFacing = DEFAULT_ANGLE
            set .tcOwner = owner
            set .track = CreateTrackable(.tcPath, .tcX, .tcY, DEFAULT_ANGLE)
            
            if onClick != null then
                set .onClickTrg = CreateTrigger()
                call TriggerRegisterTrackableHitEvent(.onClickTrg, .track)
                call TriggerAddAction(.onClickTrg, onClick)
            endif
            
            if onTrack != null then 
                set .onTrackTrg = CreateTrigger()
                call TriggerRegisterTrackableTrackEvent(.onTrackTrg, .track)
                call TriggerAddAction(.onTrackTrg, onTrack)
            endif

            //save this struct into Table
            set activeTable[.track] = this 
            
            return this
        endmethod
        
        method onDestroy takes nothing returns nothing
            
            //disable the triggers
            if .onTrackTrg != null then
                call DisableTrigger(.onTrackTrg)
            endif
            
            if .onClickTrg!= null then
                call DisableTrigger(.onClickTrg)
            endif
            
            //the user may not want to destroy the triggers
            if DESTROY_TRIGGERS then 
                if .onTrackTrg != null then
                    call DestroyTrigger(.onTrackTrg)
                endif
                
                if .onClickTrg!= null then
                    call DestroyTrigger(.onClickTrg)
                endif
            endif
            
            //remove trackable from Table
            call activeTable.flush(.track)
        endmethod
        
        //getters
        method operator x takes nothing returns real
            return .tcX
        endmethod
        
        method operator y takes nothing returns real
            return .tcY
        endmethod
        
        method operator facing takes nothing returns real
            return .tcFacing
        endmethod
    
        method operator path takes nothing returns string
            return .tcPath
        endmethod
        
        method operator tracker takes nothing returns trackable
            return .track
        endmethod
        
        method operator owner takes nothing returns player
            return .tcOwner
        endmethod
        
        method operator data takes nothing returns integer
            return .tcData
        endmethod
        
        //setters        
        method operator data= takes integer newData returns nothing
            set .tcData = newData
        endmethod
        
        method operator onClickTrgAction= takes code newFunction returns nothing
            //we get rid of the old trigger
            if .onClickTrg != null then
                call DisableTrigger(.onClickTrg)
                if DESTROY_TRIGGERS then 
                    call DestroyTrigger(.onClickTrg)
                endif
            endif
            
            //create a new one
            set .onClickTrg = CreateTrigger()
            call TriggerRegisterTrackableHitEvent(.onClickTrg, .track)
            
            //register actions
            call TriggerAddAction(.onClickTrg, newFunction)
        endmethod
        
        method operator onTrackTrgAction= takes code newFunction returns nothing
            //we get rid of the old trigger
            if .onTrackTrg != null then
                call DisableTrigger(.onTrackTrg)
                if DESTROY_TRIGGERS then 
                    call DestroyTrigger(.onTrackTrg)
                endif
            endif
            
            //create a new one
            set .onTrackTrg = CreateTrigger()
            call TriggerRegisterTrackableHitEvent(.onTrackTrg, .track)
            
            //register actions
            call TriggerAddAction(.onTrackTrg, newFunction)
        endmethod 
    endstruct
//===========================================================================
    function GetTrackableData takes trackable whichTrackable returns Trackable
        return activeTable[whichTrackable]
    endfunction
//===========================================================================
    private function PlaceTrackables takes real maxX, real minX, real maxY, real minY, real trackSize, string path, real facing, player owner, code onTrack, code onClick returns nothing
        local real currentX = minX 
        local real currentY = minY
        local integer i = 0

        loop
            exitwhen currentY >= maxY
            
            loop
                exitwhen currentX >= maxX
                call Trackable.advancedCreate(currentX, currentY, 0., path, owner, onTrack, onClick)
                set currentX = currentX + trackSize
            endloop

            set currentX = minX 
            set currentY = currentY + trackSize
        endloop
    endfunction
//===========================================================================
    function CreateTrackedArea takes real topLeftX, real topLeftY, real width, real height, real trackSize, string path, real facing, player owner, code onTrack, code onClick returns nothing
        local real maxX = topLeftX + width
        local real maxY = topLeftY
        local real minX = topLeftX
        local real minY = topLeftY - height
        
        call PlaceTrackables(maxX, minX, maxY, minY, trackSize, path, facing, owner, onTrack, onClick)
    endfunction
//===========================================================================
    function CreateTrackedRegion takes rect where, real trackSize, string path, real facing, player owner, code onTrack, code onClick returns nothing
        local real maxX = GetRectMaxX(where)
        local real maxY = GetRectMaxY(where)
        local real minX = GetRectMinX(where)
        local real minY = GetRectMinY(where)
        
        call PlaceTrackables(maxX, minX, maxY, minY, trackSize, path, facing, owner, onTrack, onClick)
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        //setting our globals
        set activeTable = HandleTable.create() //Create our spell's private Table for casters
    endfunction
endlibrary
09-07-2009, 04:59 PM#2
Here-b-Trollz
I bet it DC's.
09-07-2009, 05:05 PM#3
Hans_Maulwurf
Ok, I just read it and here are my 2 cent:
It's basicly (or even exactly?) the same as mine. I don't see what you mean with "different approach". Please explain :) The only real difference is, that you let the user choose the facing and the path.
You said it's "easier for the user", but i think you got the exectly opposite effect.
Let me explain:
imo the only use of such a lib is, to create invis tracks across an area, to catch mouseaction there.
You don't need a lib to create a single track with the model of a unit or sth. Whats the sense of having a region covered with peasants in a 45° facing every 50 units? In 99+% the user will have to enter 0 and the path of a transparent dummytrackmodel. I tryed to make it userfriendly, by reducing the amount of arguments the functions take to a minimum. So much for the only real difference i saw.

However - to finish with sth positive - the coding is nice and clean. It reads like you thought about what you want to do, before you started typing (or cleaned it up carefully before posting). I allways have to add a new variable here or add sth i forget there, etc or even have to completly change it while developing.....

PS: I think you got a typo with CreatedTrackArea
09-07-2009, 05:17 PM#4
Flame_Phoenix
Quote:
I bet it DC's.
!?
Quote:
It's basicly (or even exactly?) the same as mine.
Not exactly, I saw how you coded yours, I swear that I was sure this approach was different lol...

Quote:
The only real difference is, that you let the user choose the facing and the path.
The facing, the path, ether he wants or not to destroy the triggers when destroying a trackable object, and the fact that you can create a region of trackables besides an area using a rect.
Using rects is something it's really handy, to be honest I wanted to do the same with your CreateButton lib, but that would be stealing.
I may also add groups of trackables, but has I said before, I will give me a lot of work.
In a future version I will also allow the user to change the actions and the triggers, thus making trackable re-use easier (or so I hope). However I am not sure is destroying actions from triggers works properly.
Quote:
You don't need a lib to create a single track with the model of a unit or sth.
That depends, specially for campaigns. You can have a shop that it's a trackable, and when you click it or pass over it something happens. I've seen traps in campaigns that follow this pattern, it is something funny, you never expect it =)

As for the typo, I will fix it soon!
09-07-2009, 05:44 PM#5
Here-b-Trollz
Consider that structs are made of global variables, and that assigning different local values to a global variable causes a DC.
09-07-2009, 05:58 PM#6
Flame_Phoenix
I still don't know what a DC is ...
The code works fine as far as I know, I don't see where the problem could be.
09-07-2009, 06:04 PM#7
Veev
DC is short for disconnect.
09-07-2009, 06:24 PM#8
Flame_Phoenix
Right .... I seriously don't see how my code can cause a disconnect ... is it because of these lines?:

Collapse JASS:
        private trigger onTrackTrg = CreateTrigger()
        private trigger onClickTrg = CreateTrigger()
09-07-2009, 06:27 PM#9
Veev
I believe Here-b-Trollz is referring to this set of lines in struct Trackable create method:

Collapse JASS:
if GetLocalPlayer() == owner then
     set .tcPath = path 
endif
09-07-2009, 06:37 PM#10
Flame_Phoenix
Man ... that is what makes trackables work in multiplayer ... Kattana used it and he had no problems, I don't see why I would have ...
This problem is something I was not expecting ... if this is true, how can I fix it?
09-07-2009, 06:44 PM#11
Strilanc
Assigning different values to globals doesn't cause desyncs (by itself). I'm sure I've tested it before.
09-07-2009, 06:53 PM#12
Bobo_The_Kodo
That doesn't DC
09-07-2009, 07:37 PM#13
Flame_Phoenix
Yeee !!!!
So guys, any suggestions on how I can get rid of Table? I want to learn how to use hastables, if anyone is kind enough I would appreciate.

I am also open to other suggestions.
09-07-2009, 07:50 PM#14
Bobo_The_Kodo
Table is just like interface for hashtable & inlined, so same speed pretty much ..
09-07-2009, 07:54 PM#15
Here-b-Trollz
Quote:
Originally Posted by Strilanc
Assigning different values to globals doesn't cause desyncs (by itself). I'm sure I've tested it before.
I guess I'm wrong then.