• 🏆 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!

[vJASS] [Library] Text Tag

  • Why is a texttag destroyed and recreated in method setPos?
  • Changing a texttag's position should decouple the texttag from a binded unit.
  • Document all operators, the ones that do only return are missing...
  • Some API is completly missing.
  • GetLocalPlayer() could be a global/static, assigned once onInit.
  • instanceCheck is a bad timer name.
  • Why not attaching pIndex to the timer?
  • instancePauseddoes also speak for all player textags
 
  • Why is a texttag destroyed and recreated in method setPos?

I experienced some sort of bug where I could not move it once the vector velocity of the text tag was defined. SetTextTagVelocity. (I tried to mimic recycling of text tags). I tried some vatiations and ended up recreating the text tag as a solution.

  • Changing a texttag's position should decouple the texttag from a binded unit.

I'll see if I can change that behaviour.

  • Document all operators, the ones that do only return are missing...
  • Some API is completly missing.

Wouldn't getters imply retrieval already? I'll add the API for those members.


I find describing everything in a library a bit cumbersome. I think that certain methods, specifically operators, are best left implied by their complements. (E.g. imply getters from setters)


  • GetLocalPlayer() could be a global/static, assigned once onInit.

That .. is a very useful tip. I'm just keeping on the safe side, not using a global for it.

  • instanceCheck is a bad timer name.
  • Why not attaching pIndex to the timer?

You may have a point with instanceCheck. With pIndex being attached, I could add an optional requirement. (Table)

  • instancePaused does also speak for all player textags

Looks like I'll need to clarify that in the API. Thanks for spotting that. :grin:


Also, this version has trigger evaluations, which could be inconvenient. I forgot to upload an updated version.
 
~Updating the version to 1.3.06

MyPad
I have a problem, I used your library to create the text and when compiling got the error "Undeclared variable y" . I can send text of what is, can you help.


"call CreateTextTagBJ (Player(0),GetUnitX(GetAttacker()),GetUnitY(GetAttacker()),70,255,0,255, R2S (damage))"

Sorry for the extremely late response. It should work as intended now.
 
Level 1
Joined
Apr 8, 2020
Messages
110
I am rather late in saying this, but this system of yours still uses textmacros from an old version of your Alloc library. So, your backwards compatibility did not help much in preventing errors from popping up.

The faulty ones:
JASS:
//  These textmacros generate a module for the library to use.
//! runtextmacro link_module("local", "private")
//! runtextmacro link_module("non_perma", "private")

So...which would those above refer to, AllocT or just double linked list?

Nvm...I figured it out.
 
Last edited:
Level 1
Joined
Apr 8, 2020
Messages
110
So, I changed the example to fit my purposes. And well, the texttag does not show up, and the system keeps spamming complaints that pIndex is invalid. I am led to believe that the struct methods are not working. Strangely enough, the JASS function is working and it also uses the struct methods internally. What am I doing wrong here?
JASS:
scope DebugTextTag initializer Init
    private function Cond takes nothing returns nothing
        local string s = GetEventPlayerChatString()
        local string s1 = SubString(s, 5, StringLength(s))
        local integer i = S2I(s1)
        local TextTag tt

        if i <= 0 then
            call BJDebugMsg("Exception: Text Tag cannot be generated by this trigger. (Parameter i is less than 0).")
        else
            if i > bj_MAX_PLAYER_SLOTS then
                call BJDebugMsg("Creating text tag for all!")
            else
                call BJDebugMsg("Creating text tag for Player " + I2S(i))
            endif
            //call CreateTextTagBJ(Player(i - 1), 0, 0, 0, 0, 255, 255, "some string")
            set tt = TextTag.create(i)
            set tt.Red = 0
            set tt.Blue = 255
            set tt.Green = 255
            set tt.Alpha = 0
            set tt.duration = TextTag.DEFAULT_DURATION
            set tt.fade = TextTag.DEFAULT_DURATION - 0.5
            call tt.setPos(0, 0, 0)
            call tt.setVelocEx(TextTag.DEFAULT_VELOC_SPEED, TextTag.DEFAULT_VELOC_ANGLE)
            set tt.permanent = false
            set tt.height = TextTag.DEFAULT_HEIGHT
            set tt.message = "Good old burn...."
            call tt.setPos(0, 0, 0)
            //call tt.visible_to_force(GetForceOfPlayer(Player(0)), true)
            if vj_lastCreatedTextTag != -1 then
                call BJDebugMsg("Text tag successfully created!")
            endif
            call BJDebugMsg("Text tag instance created: " + I2S(vj_lastCreatedTextTag))
        endif
    endfunction
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterPlayerChatEvent(t, Player(0), "-tag", false)
        call TriggerAddCondition(t, function Cond)
        set t = null
    endfunction
endscope
JASS:
library TextTag requires /*

    */  AllocationAndLinks, /* https://www.hiveworkshop.com/threads/allocation-and-linked-list-bundle.293621/#post-3159130
    *   -   Instantiates an allocator and linked list for the TextTag
    *
    */  optional Table      /* https://www.hiveworkshop.com/threads/snippet-new-table.188084/
    *   -   Allows binding of the global timer instances.
    */
  
    //==========================//
    //  TextTag                 //
    //      -   By  MyPad       //
    //                          //
    //  Version:                //
    //      v.1.3.06            //
    //==========================//

    //  Description:    //

    //======================================================================================//
    //  TextTag is a library that provides convenience for the creation of local text tags. //
    //  Thus, it is lightweight in usage, heavy in code, and grants the user a bit of ease  //
    //  in coding.                                                                          //
    //======================================================================================//


    //  API (Application Programming Interface)                                                   //
    //      Note: Section is in vJASS... but the description slowly starts to resemble Wurst      //

    //! novjass
        struct TextTag
    
            Creation and destruction:
        
            TextTag.create(integer playerId) -> TextTag
                //  Creates a local texttag for a certain player.
            
                //  Note that the playerId parameter takes the actual number of the player,
                //  not the player itself
          
                //  To create a global instance, request 16 or higher (Must be greater than or equal to bj_MAX_PLAYER_SLOTS)
            local TextTag tag = TextTag.create(integer playerId) -> TextTag
                //  Creates a TextTag instance.
            ..
        
            tag.destroy()
                //  This destroys a TextTag instance. How it manages to destroy an instance
                //  for a player is determined durationally or explicitly.
          
            method operator/s:
                tag.height2 == real z_coord
                //  Returns the member z_coord
                tag.height2 = real height
                //  Internally calls method setPos()
              
                tag.height == real size
                //  Returns the member size
                tag.height = real size
                //  sets the size of a Text Tag
              
                tag.permanent == boolean isPermanent
                //  Returns the member isPermanent
                tag.permanent = boolean bool
                    //  Sets the permanence of a TextTag
              
                tag.duration == real dur
                //  Returns the member dur -> tells duration.
                tag.duration = real newDur
                //  If it is not permanent, a TextTag's duration is reset to the requested (r).
              
                tag.msg or tag.message == string s
                //  message is just an abstraction of msg, a method operator that returns a member s.
              
                tag.msg = string sometext
                tag.message = string sometext
                //  Sets the message of a texttag
                
                tag.Red = integer int (0 - 255)
                tag.Blue = integer int (0 - 255)
                tag.Green = integer int (0 - 255)
                tag.Alpha = integer int (0 - 255)
                //  Returns the red, green, blue, and alpha properties of the text tag.
              
                //  Wrapper method operators for above
                tag.red = integer int(0 - 255)
                tag.green = integer int(0 - 255)
                tag.blue = integer int(0 - 255)
                tag.alpha = integer int(0 - 255)
              
                //  Alpha levels determine how visible is the locally created text tag.
                //  255 means the instance is very visible while 0 means not visible.
                  
                tag.widget = widget wid
                tag.unit = unit uni
                //  Attaches a text tag to a widget or a unit.
                
            method /(s):
                //tag
              
                tag.setPosPrime(real x, real y, real heightOffset, boolean break)
                    //  Sets the position of a TextTag instance to the requested coordinates
                    //  with a z-offset denoted as heightOffset
              
                tag.setPos(real x, real y, real heightOffset)
                    //  Calls setPosPrime with a false parameter for variable break.
                tag.setVeloc(real xvel, real yvel)
                    //  Sets the pace at which the tag moves at a certain direction.
            
                tag.setVelocEx(real speed, real angle)
                    //  Wrapper function for setVeloc
                  
                tag.generate(integer playerId) -> TextTag
                    //  Generates the same text tag for another player
              
                tag.transfer(integer playerId) -> TextTag
                    //  Same as generate, but destroys the base instance.
              
                tag.visible_to_force(force f, boolean b)
                    //  Only works when the Text Tag instance is a global one.
                    //  Sets the visibility of a global text tag to flag b,
                    //  towards players belonging to a certain force f
                  
        endstruct
    
        function CreateTextTagBJ(player p, real x, real y, real offset, integer red, integer green, integer blue, string msg) {TextTag}  // or returns TextTag, -> TextTag
            //  Creates a TextTag instance with the default settings.
            //  If not going to use a variable, the snippet returns a global variable "vj_lastCreatedTextTag"
            {vj_lastCreatedTextTag} // or return vj_lastCreatedTextTag
          
    //! endnovjass
  
    //! novjass
        Update History:
      
        v.1.3.0.6 - Added some details on method operator getters. (Denoted by ==)
                  - Fixed setVeloc-s slightly buggy behavior. (It didn-t originally pause the texttag when set to 0)
                  - Exposed a new method setPosPrime (does what setPos did before, with a boolean parameter)
                  - setPos now calls setPosPrime with a false flag.
                
        v.1.3.0.5 - Improved API documentation.
                  - Added a new method operator height2
                  - Backwards compatible!
                
        v.1.3.0.4 - Update history created!
                  - method operator unit= fixed.
                
    //! endnovjass

    globals
        //  This determines the check rate of the timers.
        private constant real INTERVAL = 1/32.
    endglobals
  
    //  These textmacros generate a module for the library to use.
    ///! runtextmacro link_module("local", "private")
    ///! runtextmacro link_module("non_perma", "private")
    //! runtextmacro DLinkedListT("local", "private")
    //! runtextmacro DLinkedListT("non_perma", "private")
  
    private module TextTag_Mod
        //  How many texttags are possible per head node list
        //  Defined to be (number of desired text tags + 1)
        readonly static constant integer ALLOC_MULTIPLIER = 101
    
        //  These are the default settings for heights
        //  Don't touch unless you know what you are doing
        readonly static constant real DEFAULT_HEIGHT = 10.
        readonly static constant real HEIGHT_OFFSET = 0.023 / 10.
    
        //  Default offset value for velocity
        readonly static constant real VELOCITY_OFFSET = 0.071 / 128.
    
        //  Default duration of a texttag instance
        readonly static constant real DEFAULT_DURATION = 2.
    
        //  Default speed and angle of a texttag
        readonly static constant real DEFAULT_VELOC_SPEED = 80.
        readonly static constant real DEFAULT_VELOC_ANGLE = 90.
    
        //  The list of deallocated instances.
        readonly thistype recCount
      
        readonly integer tag_count
      
        //  In favor of using an AllocationAndLinks library, I had just reduced the text into implementing a locally
        //  generated module..
        implement DoubleLink_local
      
        //  A linked list for non permanent text tags.
        implement DoubleLink_non_perma
      
        //  This member can only be written to the head nodes. It is best to not refer to it as it only works for
        //  head nodes.
        boolean instancePaused
      
        //  The timer generated only exists for head nodes. They do not exist anywhere else, thus the readonly
        //  behaviour. Likewise, instanceData behaves in the same manner.
      
        readonly timer timerChecker
        readonly integer instanceData
      
        method deallocate takes nothing returns nothing
            local integer pIndex = this/ALLOC_MULTIPLIER
        
            set recCount = thistype(pIndex*ALLOC_MULTIPLIER).recCount
            set thistype(pIndex*ALLOC_MULTIPLIER).recCount = this
        endmethod
      
        //  The allocation scheme is quite different, having head nodes (integer playerId * ALLOC_MULTIPLIER)
        static method allocate takes integer pIndex returns thistype
            local thistype this = thistype(pIndex*ALLOC_MULTIPLIER).recCount
          
            //  The if-statement checks if the next instance is to be recycled or generated
            if this.recCount == pIndex*ALLOC_MULTIPLIER then
                // We make sure that the instance does not encroach on another head node's instance count.
                // So, Player 1 has a head node of 0, Player 2 has it at 101, Player 3 has it at 202, and so on...
                if integer(this) < (pIndex+1)*ALLOC_MULTIPLIER then
                    //  From indices pIndex*ALLOC_MULTIPLIER to (pIndex+1)*ALLOC_MULTIPLIER - 1
                    set this = this + 1
                    set thistype(pIndex*ALLOC_MULTIPLIER).recCount = this
                    set this.recCount = pIndex*ALLOC_MULTIPLIER
                else
                    //  Error, text tag count exceeded...
                    set this = -1
                endif
            else
                //  Recycling; it is impossible for the allocate method to go over the max.
                set thistype(pIndex*ALLOC_MULTIPLIER).recCount = this.recCount
                set this.recCount = pIndex*ALLOC_MULTIPLIER
            endif
            return this
        endmethod
      
        //  Add a Table instance if it exists
        static if LIBRARY_Table then
            readonly static Table timerData = 0
        endif
      
        private static method onInit takes nothing returns nothing
            local thistype this = 0
          
            static if LIBRARY_Table then
                set thistype.timerData = Table.create()
            endif
          
            loop
                exitwhen integer(this) > bj_MAX_PLAYER_SLOTS
              
                //  These are the head nodes that are going to be initialized
                //  Push them first as heads of their respective lists.
                call thistype(this*ALLOC_MULTIPLIER).insert(this*ALLOC_MULTIPLIER)
                call thistype(this*ALLOC_MULTIPLIER).non_perma_insert(this*ALLOC_MULTIPLIER)
              
                set thistype(this*ALLOC_MULTIPLIER).head = this*ALLOC_MULTIPLIER
                set thistype(this*ALLOC_MULTIPLIER).non_perma_head = this*ALLOC_MULTIPLIER
                set thistype(this*ALLOC_MULTIPLIER).recCount = this*ALLOC_MULTIPLIER
              
                set thistype(this*ALLOC_MULTIPLIER).timerChecker = CreateTimer()
                set thistype(this*ALLOC_MULTIPLIER).instanceData = this*ALLOC_MULTIPLIER
                set thistype(this*ALLOC_MULTIPLIER).instancePaused = true
              
                static if LIBRARY_Table then
                    set timerData.integer[GetHandleId(thistype(this*ALLOC_MULTIPLIER).timerChecker)] = this*ALLOC_MULTIPLIER
                endif
              
                set this = this + 1
            endloop
        endmethod  
    endmodule
  
    struct TextTag extends array
        private texttag texttag
    
        private widget wid_targ
        private unit uni_targ
    
        private boolean isPermanent
        private real dur
    
        private string s
        private real size
    
        private real fadepoint
      
        private integer r
        private integer g
        private integer b
        private integer a
      
        private real d_xvel
        private real d_yvel
      
        readonly real tag_x
        readonly real tag_y
        readonly real tag_z
      
        implement DoubleLink
        implement TextTag_Mod
      
        //  This method simply gets the player id through division.
        private method get_player_id takes nothing returns integer
            if this - this/ALLOC_MULTIPLIER*ALLOC_MULTIPLIER == 0 then
                //  The instance is a head node.
                return -1
            endif
            return this/ALLOC_MULTIPLIER
        endmethod
      
        method destroy takes nothing returns nothing
            local thistype that = this.local_next
            local integer pIndex = get_player_id()
          
            if pIndex >= bj_MAX_PLAYER_SLOTS then
                call DestroyTextTag(texttag)
                set texttag = null
            else
                if GetLocalPlayer() == Player(pIndex) then
                    call DestroyTextTag(texttag)
                    set texttag = null
                endif
            endif
          
            if that != 0 then
                loop
                    exitwhen that == this
                    call that.local_pop()
                  
                    call that.deallocate()
                  
                    set that = that.local_next
                endloop
            endif
          
            if non_perma_head != 0 then
                call non_perma_pop()
                set non_perma_head = 0
            endif
          
            set wid_targ = null
            set uni_targ = null
        
            set isPermanent = false
            set dur = 0.
        
            set r = 0
            set g = 0
            set b = 0
            set a = 0
          
            set d_xvel = 0
            set d_yvel = 0
          
            set tag_x = 0
            set tag_y = 0
          
            set s = ""
          
            set head = 0
          
            call pop()
            call deallocate()
          
            set thistype(pIndex*ALLOC_MULTIPLIER).tag_count = thistype(pIndex*ALLOC_MULTIPLIER).tag_count - 1
        endmethod
    
        //============================================================//
        //          Methods                                           //
        //============================================================//
      
        method setPosPrime takes real x, real y, real heightOffset, boolean break returns nothing
            //  pIndex is configured to each player
            //  Example:
            //      If this is 99, then the instance belongs to the list of Player 1.
            //      or Player(0)
              
            //      If this is 102, then the instance belongs to the list of Player 2.
            //      or Player(1)
          
            //      If this is 202, then the
            local integer pIndex = get_player_id()
          
            if break then
                set uni_targ = null
                set wid_targ = null
            endif
          
            debug if pIndex < 0 then
                debug call BJDebugMsg("The parameter this is a head node.")
                debug return
            debug endif
          
            set tag_x = x
            set tag_y = y
          
            if uni_targ == null then
                set tag_z = heightOffset
            else
                set tag_z = heightOffset - GetUnitFlyHeight(uni_targ)
            endif
          
            if pIndex >= bj_MAX_PLAYER_SLOTS then
                if d_xvel != 0 or d_yvel != 0 then
                    call DestroyTextTag(texttag)
                    set texttag = CreateTextTag()
                    call SetTextTagPos(texttag, x, y, heightOffset)
                    call SetTextTagText(texttag, s, size * HEIGHT_OFFSET)
                    call SetTextTagColor(texttag, r, g, b, a)
                  
                    call SetTextTagPermanent(texttag, isPermanent)
                    if not isPermanent then
                        call SetTextTagLifespan(texttag, dur)
                        call SetTextTagFadepoint(texttag, RMaxBJ(fadepoint, 0))
                    endif
                else
                    call SetTextTagPos(texttag, x, y, heightOffset)
                endif
            else
                if d_xvel != 0 or d_yvel != 0 then
                    if GetLocalPlayer() == Player(pIndex) then
                        call DestroyTextTag(texttag)
                        set texttag = CreateTextTag()
                        call SetTextTagPos(texttag, x, y, heightOffset)
                        call SetTextTagText(texttag, s, size * HEIGHT_OFFSET)
                        call SetTextTagColor(texttag, r, g, b, a)
                      
                        call SetTextTagPermanent(texttag, isPermanent)
                        if not isPermanent then
                            call SetTextTagLifespan(texttag, dur)
                            call SetTextTagFadepoint(texttag, RMaxBJ(fadepoint, 0))
                        endif
                    endif
                else
                    if GetLocalPlayer() == Player(pIndex) then
                        call SetTextTagPos(texttag, x, y, heightOffset)
                    endif
                endif
            endif
        endmethod
      
        method setPos takes real x, real y, real heightOffset returns nothing
            call setPosPrime(x, y, heightOffset, false)
        endmethod
    
        method setVeloc takes real xvel, real yvel returns nothing
            local integer pIndex = get_player_id()

            debug if pIndex < 0 then
                debug call BJDebugMsg("The parameter this is a head node.")
                debug return
            debug endif
          
            if pIndex >= bj_MAX_PLAYER_SLOTS then
                if xvel != 0 or yvel != 0 then
                    call SetTextTagVelocity(texttag, xvel * VELOCITY_OFFSET, yvel * VELOCITY_OFFSET)
                else
                    call setPos(tag_x, tag_y, 0)
                endif
            else
                if xvel != 0 or yvel != 0 then
                    if GetLocalPlayer() == Player(pIndex) then
                        call SetTextTagVelocity(texttag, xvel * VELOCITY_OFFSET, yvel * VELOCITY_OFFSET)
                    endif
                else
                    call setPos(tag_x, tag_y, 0)
                endif
            endif
          
            set d_xvel = xvel
            set d_yvel = yvel
        endmethod
    
        method setVelocEx takes real speed, real angle returns nothing
            call setVeloc(Cos(angle * bj_DEGTORAD) * speed, Sin(angle * bj_DEGTORAD) * speed)
        endmethod
      
        //  Sets visibility of a global text tag to a certain flag towards a certain force.
        method visible_to_force takes force f, boolean b returns nothing
            local integer pIndex = get_player_id()
          
            if pIndex >= bj_MAX_PLAYER_SLOTS then
                if IsPlayerInForce(GetLocalPlayer(), f) then
                    call SetTextTagVisibility(texttag, b)
                endif
            debug else
                debug call BJDebugMsg("thistype( " + I2S(this) + ".setVelocEx: Cannot modify visibility of a local text tag.")
            endif
        endmethod
        //============================================================//
        //          Method operators                                  //
        //============================================================//
    
        method operator permanent takes nothing returns boolean
            return isPermanent
        endmethod
        method operator permanent= takes boolean b returns nothing
            local integer pIndex = get_player_id()

            debug if pIndex < 0 then
                debug call BJDebugMsg("The parameter this is a head node.")
                debug return
            debug endif
          
            if not b then
                if this.non_perma_head == 0 then
                    call this.non_perma_insert(this.head)
                    set this.non_perma_head = this.head
                endif
            else
                if this.non_perma_head != 0 then
                    call this.non_perma_pop()
                    set this.non_perma_head = 0
                endif
            endif
          
            set isPermanent = b
        
            if pIndex >= bj_MAX_PLAYER_SLOTS then
                call SetTextTagPermanent(texttag, isPermanent)
            else
                if GetLocalPlayer() == Player(pIndex) then
                    call SetTextTagPermanent(texttag, isPermanent)
                endif
            endif
        endmethod
    
        method operator duration takes nothing returns real
            if not isPermanent then
                return dur
            endif
        
            //  If permanent, the text tag will always endure. Thus, its' duration is -1
            return -1.
        endmethod
        method operator duration= takes real r returns nothing
            local integer pIndex = get_player_id()
          
            debug if pIndex < 0 then
                debug call BJDebugMsg("The parameter this is a head node.")
                debug return
            debug endif
        
            //  Can't set a duration to permanent text tags.
            if isPermanent then
                return
            endif
        
            set dur = r
        
            if pIndex >= bj_MAX_PLAYER_SLOTS then
                call SetTextTagLifespan(texttag, dur)
            else
                if GetLocalPlayer() == Player(pIndex) then
                    call SetTextTagLifespan(texttag, dur)
                endif
            endif
        endmethod
      
        method operator msg takes nothing returns string
            return s
        endmethod
        method operator msg= takes string str returns nothing
            local integer pIndex = get_player_id()
          
            debug if pIndex < 0 then
                debug call BJDebugMsg("The parameter this is a head node.")
                debug return
            debug endif
        
            set s = str
        
            if pIndex >= bj_MAX_PLAYER_SLOTS then
                call SetTextTagText(texttag, s, size * HEIGHT_OFFSET)
            else
                if GetLocalPlayer() == Player(pIndex) then
                    call SetTextTagText(texttag, s, size * HEIGHT_OFFSET)
                endif
            endif
        endmethod
    
        //  These methods are wrappers...
        method operator message takes nothing returns string
            return s
        endmethod
        method operator message= takes string str returns nothing
            set msg = str
        endmethod
      
        //  A newer method operator that actually returns the height of the text tag.
        method operator height2 takes nothing returns real
            return tag_z
        endmethod
        method operator height2= takes real new returns nothing
            local integer pIndex = get_player_id()
          
            debug if pIndex < 0 then
                debug call BJDebugMsg("The parameter this is a head node.")
                debug return
            debug endif
          
            if wid_targ != null then
                call setPos(GetWidgetX(wid_targ), GetWidgetY(wid_targ), new)
            elseif uni_targ != null then
                call setPos(GetUnitX(uni_targ), GetUnitY(uni_targ), new)
            else
                call setPos(tag_x, tag_y, new)
            endif
        endmethod
      
        //  Cannot deal away with backwards compatibility here..
        method operator height takes nothing returns real
            return size
        endmethod
        method operator height= takes real h returns nothing
            local integer pIndex = get_player_id()
        
            debug if pIndex < 0 then
                debug call BJDebugMsg("The parameter this is a head node.")
                debug return
            debug endif
        
            set size = h
        
            if pIndex >= bj_MAX_PLAYER_SLOTS then
                call SetTextTagText(texttag, s, size * HEIGHT_OFFSET)
            else
                if GetLocalPlayer() == Player(pIndex) then
                    call SetTextTagText(texttag, s, size * HEIGHT_OFFSET)
                endif
            endif
        endmethod
    
        method operator fade takes nothing returns real
            return fadepoint
        endmethod
        method operator fade= takes real fader returns nothing
            local integer pIndex = get_player_id()
          
            debug if pIndex < 0 then
                debug call BJDebugMsg("The parameter this is a head node.")
                debug return
            debug endif
        
            if not isPermanent then
                set fadepoint = RMinBJ(RAbsBJ(fader), dur)
            else
                return
            endif
        
            if pIndex >= bj_MAX_PLAYER_SLOTS then
                call SetTextTagFadepoint(texttag, fadepoint)
            else
                if GetLocalPlayer() == Player(pIndex) then
                    call SetTextTagFadepoint(texttag, fadepoint)
                endif
            endif
        endmethod
    
        method operator Red takes nothing returns integer
            return r
        endmethod
        method operator Red= takes integer r returns nothing
            local integer pIndex = get_player_id()
          
            debug if pIndex < 0 then
                debug call BJDebugMsg("The parameter this is a head node.")
                debug return
            debug endif
        
            set this.r = r
        
            if pIndex >= bj_MAX_PLAYER_SLOTS then
                call SetTextTagColor(texttag, this.r, g, b, 255 - a)
            else
                if GetLocalPlayer() == Player(pIndex) then
                    call SetTextTagColor(texttag, this.r, g, b, 255 - a)
                endif
            endif
        endmethod
      
        //  Wrappers for wrappers
        method operator red takes nothing returns integer
            return r
        endmethod
        method operator red= takes integer r returns nothing
            set Red = r
        endmethod
      
        method operator Green takes nothing returns integer
            return g
        endmethod
        method operator Green= takes integer g returns nothing
            local integer pIndex = get_player_id()
          
            debug if pIndex < 0 then
                debug call BJDebugMsg("The parameter this is a head node.")
                debug return
            debug endif
        
            set this.g = g
        
            if pIndex >= bj_MAX_PLAYER_SLOTS then
                call SetTextTagColor(texttag, r, this.g, b, 255 - a)
            else
                if GetLocalPlayer() == Player(pIndex) then
                    call SetTextTagColor(texttag, r, this.g, b, 255 - a)
                endif
            endif
        endmethod
      
        method operator green takes nothing returns integer
            return g
        endmethod
        method operator green= takes integer g returns nothing
            set Green = g
        endmethod
      
        method operator Blue takes nothing returns integer
            return b
        endmethod
        method operator Blue= takes integer b returns nothing
            local integer pIndex = get_player_id()
          
            debug if pIndex < 0 then
                debug call BJDebugMsg("The parameter this is a head node.")
                debug return
            debug endif
        
            set this.b = b
        
            if pIndex >= bj_MAX_PLAYER_SLOTS then
                call SetTextTagColor(texttag, r, g, this.b, 255 - a)
            else
                if GetLocalPlayer() == Player(pIndex) then
                    call SetTextTagColor(texttag, r, g, this.b, 255 - a)
                endif
            endif
        endmethod
      
        method operator blue takes nothing returns integer
            return b
        endmethod
        method operator blue= takes integer b returns nothing
            set Blue = b
        endmethod
      
        method operator Alpha takes nothing returns integer
            return a
        endmethod
        method operator Alpha= takes integer a returns nothing
            local integer pIndex = get_player_id()
          
            debug if pIndex < 0 then
                debug call BJDebugMsg("The parameter this is a head node.")
                debug return
            debug endif
        
            set this.b = b
        
            if pIndex >= bj_MAX_PLAYER_SLOTS then
                call SetTextTagColor(texttag, r, g, b, 255 - this.a)
            else
                if GetLocalPlayer() == Player(pIndex) then
                    call SetTextTagColor(texttag, r, g, b, 255 - this.a)
                endif
            endif
        endmethod
      
        method operator alpha takes nothing returns integer
            return a
        endmethod
        method operator alpha= takes integer a returns nothing
            set Alpha = a
        endmethod
      
        method operator widget takes nothing returns widget
            return wid_targ
        endmethod
        method operator widget= takes widget wid returns nothing
            if wid == null then
                debug call BJDebugMsg("Warning: the parameter wid is null.")
                //  Must not attach to null instance...
                return
            endif
          
            set uni_targ = null
            set wid_targ = wid
          
            call setVeloc(0,0)
            call setPos(GetWidgetX(wid_targ), GetWidgetY(wid_targ), tag_z)
        endmethod
    
        method operator unit takes nothing returns unit
            return uni_targ
        endmethod
        method operator unit= takes unit uni returns nothing
            if uni == null then
                debug call BJDebugMsg("Warning: the parameter uni is null.")
                //  Must not attach to null instance...
                return
            endif
        
            set wid_targ = null
            set uni_targ = uni
          
            call setVeloc(0,0)
            call setPos(GetUnitX(uni_targ), GetUnitY(uni_targ), GetUnitFlyHeight(uni_targ))
        endmethod
    
        //  =================================   //
        //  Create and onTick methods           //
        //  =================================   //
      
        //  A linear search specifically for the onTick static method
        private static method timer_search takes timer t returns thistype
            static if LIBRARY_Table then
                return TextTag.timerData.integer[GetHandleId(t)]
            else
                local thistype this = 0
                loop
                    exitwhen timerChecker == t
                    if integer(this) > bj_MAX_PLAYER_SLOTS*ALLOC_MULTIPLIER then
                        set this = -1
                        exitwhen true
                    endif
                    set this = this + ALLOC_MULTIPLIER
                endloop
                return this
            endif
        endmethod
      
        private static method onTick takes nothing returns nothing
            local timer tick = GetExpiredTimer()
            local integer pIndex
            local thistype head
            local thistype this

            //  Gets the head node
            set pIndex = timer_search(tick)
          
            set head = pIndex
            set this = head.next
          
            static if LIBRARY_Table then
                if pIndex == 0 then
                    call BJDebugMsg("thistype.onTick: pIndex is invalid!")
                endif
            endif
          
            if this == head then
                set instancePaused = true
                call PauseTimer(tick)
            endif
          
            //  Loop over all instances related to the timer
            //  Each timer has a list for each player.
            loop
                exitwhen this == head
              
                if this.uni_targ != null or this.wid_targ != null then
                  
                    if this.wid_targ != null then
                        set this.widget = wid_targ
                    else
                        set this.unit = uni_targ
                    endif
                else
                    set this.tag_x = this.tag_x + d_xvel * INTERVAL
                    set this.tag_y = this.tag_y + d_yvel * INTERVAL
                endif
              
                if not this.isPermanent then
                    set this.dur = this.dur - INTERVAL
                    set this.fade = this.fade - INTERVAL
                  
                    if this.dur <= 0. then
                        call this.destroy()
                    endif
                endif
                set this = this.next
            endloop
        endmethod
      
        //  A return-safe way to search for non-permanent text tags.
        private static method non_perma_search takes thistype pIndex returns thistype
            local thistype this = thistype(pIndex*ALLOC_MULTIPLIER).non_perma_next
            if this == thistype(pIndex*ALLOC_MULTIPLIER) then
                set this = -1
            endif
            return this
        endmethod
      
        static method create takes integer pIndex returns thistype
            local thistype this = 0
            local integer i = 0
            local thistype array that
          
            if pIndex > bj_MAX_PLAYER_SLOTS then
                set pIndex = bj_MAX_PLAYER_SLOTS
            elseif pIndex < 0 then
                //  We make sure that the instance falls under the list
                set pIndex = ModuloInteger(pIndex, bj_MAX_PLAYER_SLOTS)
            endif
        
            if pIndex == bj_MAX_PLAYER_SLOTS then
                if thistype(pIndex*ALLOC_MULTIPLIER).tag_count == ALLOC_MULTIPLIER - 1 then
                    //  A very unlikely case where all 1717 instances are all occupied at the same time.
                    set this = non_perma_search(pIndex)
                  
                    debug if this == -1  then
                        debug call BJDebugMsg("No temporary text tag instances remaining!")
                        debug set this = 1/0
                    debug endif
                  
                    //  We move the instance to the end of the list.
                    call this.pop()
                    call this.insert(pIndex*ALLOC_MULTIPLIER)
                  
                    //  We move the non-permanent instance to the end of the list.
                    call this.non_perma_pop()
                    call this.non_perma_insert(pIndex*ALLOC_MULTIPLIER)
                else
                    //  We check if all local players can afford to have another text tag.
                    loop
                        //  We stop when variable i is greater than or equal to bj_MAX_PLAYER_SLOTS
                        //  When that happens, our variable can be globally generated.
                        exitwhen i >= bj_MAX_PLAYER_SLOTS
                        set that[i] = allocate(i)
                    
                        //  If any instance returns -1, then we stop looping and no longer proceed with creating
                        //  a text tag
                        if that[i] == -1 then
                            debug call BJDebugMsg("Breakpoint found: Player (" + I2S(i + 1) + ") cannot have any more text tags.")
                            exitwhen true
                        endif
                        set i = i + 1
                    endloop
                  
                    if i < bj_MAX_PLAYER_SLOTS then
                        //  Creation failed, sadly...
                        set this = -1
                        loop
                            exitwhen i < 0
                            //  We deallocate them as they were temporarily requested...
                            call that[i].deallocate()
                            set i = i - 1
                        endloop
                    else
                        //  Creation success..
                        set this = allocate(pIndex)
                      
                        call this.insert(pIndex*ALLOC_MULTIPLIER)
                        set this.head = pIndex*ALLOC_MULTIPLIER
                      
                        call this.local_insert(this)
                        set this.local_head = this
                      
                        set this.texttag = CreateTextTag()
                      
                        set thistype(pIndex*ALLOC_MULTIPLIER).tag_count = thistype(pIndex*ALLOC_MULTIPLIER).tag_count + 1
                      
                        set i = i - 1
                        loop
                            exitwhen i < 0
                            //  We link the local instances to the global instance
                            call that[i].local_insert(this)
                            set that[i].local_head = this
                          
                            set i = i - 1
                        endloop
                        if thistype(pIndex*ALLOC_MULTIPLIER).instancePaused then
                            set thistype(pIndex*ALLOC_MULTIPLIER).instancePaused = false
                            call TimerStart(thistype(pIndex*ALLOC_MULTIPLIER).timerChecker, INTERVAL, true, function thistype.onTick)
                        endif
                      
                        //  Assume that the text tag is not permanent.
                        call this.non_perma_insert(pIndex*ALLOC_MULTIPLIER)
                      
                        //  This is done so as to not cause any logical errors when looping through the non_permanent list
                        //  By setting it to the next head node, we avoid any false positive checks with player 1.
                        set this.non_perma_head = (pIndex+1)*ALLOC_MULTIPLIER
                    endif
                endif
            else
                //  How it is done:
                //      Every player knows that a text tag exists.
                //      However, only one player can actually see what the text tag really is.
              
                //  AS requested by Xonok...
                //  If the tag count of the head node is 100, then search for a non-permanent instance
                //  Override if return success!
                if thistype(pIndex*ALLOC_MULTIPLIER).tag_count == ALLOC_MULTIPLIER - 1 then
                    set this = non_perma_search(pIndex)
                  
                    debug if this == -1  then
                        debug call BJDebugMsg("No temporary text tag instances remaining!")
                        debug set this = 1/0
                    debug endif
                  
                    //  We move the instance to the end of the list.
                    call this.pop()
                    call this.insert(pIndex*ALLOC_MULTIPLIER)
                  
                    //  We move the non-permanent instance to the end of the list.
                    call this.non_perma_pop()
                    call this.non_perma_insert(pIndex*ALLOC_MULTIPLIER)
                else
                    set this = allocate(pIndex)
                  
                    if this != -1 then
                        //  Allocation Successful..
                        //  Update the number of text tags
                        set thistype(pIndex*ALLOC_MULTIPLIER).tag_count = thistype(pIndex*ALLOC_MULTIPLIER).tag_count + 1
                      
                        //  Create a link to the iterator list for checking of expiration of text tag.
                        call this.insert(pIndex*ALLOC_MULTIPLIER)
                        set this.head = pIndex*ALLOC_MULTIPLIER
                      
                        if GetLocalPlayer() == Player(pIndex) then
                            set this.texttag = CreateTextTag()
                        endif
                      
                        if thistype(pIndex*ALLOC_MULTIPLIER).instancePaused then
                            set thistype(pIndex*ALLOC_MULTIPLIER).instancePaused = false
                            call TimerStart(thistype(pIndex*ALLOC_MULTIPLIER).timerChecker, INTERVAL, true, function thistype.onTick)
                        endif
                      
                        call this.non_perma_insert(pIndex*ALLOC_MULTIPLIER)
                        set this.non_perma_head = (pIndex+1)*ALLOC_MULTIPLIER
                    endif
                endif
            endif
            return this
        endmethod
      
        //  Copies a Text Tag instance to another player
        method generate takes integer playerId returns thistype
            local thistype new = 0
            local integer pIndex = get_player_id()
          
            if pIndex == playerId then
                debug call BJDebugMsg("|cffffcc00(method generate)|r; |cffff0000Error:|r playerId instance provided is the same as pIndex")
                debug call BJDebugMsg("|cffffcc00(method generate)|r; value of pIndex: " + I2S(pIndex))
                return this
            endif
          
            set new = create(playerId)
            if new == -1 then
                debug call BJDebugMsg("|cffffcc00(method generate)|r; |cffffcc00Exception:|r instance count exceeded. (Too many permanent text tags.)")
                return new
            endif
          
            set new.duration = duration
            set new.fade = fade
          
            call new.setPos(tag_x, tag_y, 0)
            call new.setVeloc(tag_x, tag_y)
        
            set new.permanent = permanent
        
            set new.height = height
    
            set new.Red = red
            set new.Green = green
            set new.Blue = blue
            set new.Alpha = alpha
          
            set new.msg = msg      
            return new
        endmethod
      
        //  Same as the method generate but destroys the base instance
        method transfer takes integer playerId returns thistype
            local thistype that = generate(playerId)
          
            if that == -1 then
                debug call BJDebugMsg("|cffffcc00(method transfer)|r; |cffff0000Exception:|r delegate method called failed.")
                return this
            endif
          
            call destroy()
            return that
        endmethod
    endstruct
  
    globals
        TextTag vj_lastCreatedTextTag = 0
    endglobals
  
    function CreateTextTagBJ takes player p, real x, real y, real offset, integer red, integer green, integer blue, string msg returns TextTag
        local TextTag this
    
        if p == null then
            set this = TextTag.create(bj_MAX_PLAYER_SLOTS)
        else
            set this = TextTag.create(GetPlayerId(p))
        endif
    
        if this == -1 then
            return this
        endif
      
        set this.duration = TextTag.DEFAULT_DURATION
        set this.fade = TextTag.DEFAULT_DURATION - 0.5

        call this.setPos(x, y, offset)
        call this.setVelocEx(TextTag.DEFAULT_VELOC_SPEED, TextTag.DEFAULT_VELOC_ANGLE)
    
        set this.permanent = false
    
        set this.height = TextTag.DEFAULT_HEIGHT

        set this.Red = red
        set this.Green = green
        set this.Blue = blue
        set this.Alpha = 0
    
        set this.msg = msg
      
        set vj_lastCreatedTextTag = this
        return vj_lastCreatedTextTag
    endfunction
  
endlibrary
 
Based on what is being debugged, vj_lastCreatedTextTag returns 0, because the text tag was created directly through the TextTag class, instead of being created via CreateTextTagBJ.

Also, I suspect that the get_player_id method may be at fault here, and have since removed in the new version. Kinda wrote the new version hastily due to working on my contest entry.

Still, the TextTag library I wrote is really faulty for requiring a lot of things. I've released an update that will likely break some backwards compatibility with certain operators (Red, Green, Blue, Alpha), but will no longer require anything.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Do you intend to update this at any point? The ModuloInteger on the timer handle ID is creative, but it can potentially cause overlapping issues with the index (outside of a vaccuum test map of course). When you say you've released an update, did you submit that as a separate resource? If so, this one should be graveyarded.
 
When you say you've released an update, did you submit that as a separate resource?
I don't think I released a later version of this as another resource. However, I did forget to mention that the current version of the library has been changed to 1.4.00 at some point (my mistake there).

Do you intend to update this at any point? The ModuloInteger on the timer handle ID is creative, but it can potentially cause overlapping issues with the index (outside of a vaccuum test map of course).
Hmm, after looking at the library now, I forgot that handle IDs will not always be contiguous, so the possibility of a collision of indices may occur. I'll refactor the hashing function I used for the internal timers to minimize the possibility of collisions.

EDIT:
Updated to v.1.4.10:
  • API description and style has been revised to focus on the modus operandi of the system and marketing to the target users.
  • The hashing function GetHashID now generates wholly unique indices for the internal timers used by the system.
  • Method visible_to_force has been marked for deprecation. Use applyForceVisibility instead.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
The GetHashId construct is really over-engineered, imo. I am not sure why it is being done over a hashtable + struct index allocated via vJass native struct functionality, or Alloc. Nevertheless, it is functional and there might be a benefit in not requiring a hashtable/Table library for those who prefer systems with no requirements.

Approved
 
Top