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

A text tag library (formerly a snippet) that allows you to create local text tags worry-free.

JASS:
library TextTag
/*------------------------------
|                               |
|  TextTag                      |
|      By MyPad                 |
|                               |
|  Version:                     |
|      v.1.4.10                 |
|--------------------------------------------------------------------------------
|
|   TextTag is a library that provides convenience for the creation of local
|   text tags. Knowing that there is a limit to the number of text tag handles
|   that can be created and used at any given time, this system optimizes
|   the usage of these limited text tag handles, minimizing their actual amount.
|   
|   If you want to create localized text tags, then this library is definitely
|   for you. If you want an object-oriented approach to handling text tags,
|   this library should suit your needs.
|
|---------------------------------------------------------------------------------
|           |
|  API      |
|           |
 --------------------------------------------------------------------------------
*/
//! 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 bj_MAX_PLAYER_SLOTS or higher
    local TextTag tag = TextTag.create(integer playerId) -> TextTag
        //  Creates a TextTag instance.
    ..
    
    tag.destroy()
        //  This destroys a TextTag instance. How it destroys an instance
        //  for a player is determined either manually
        //  or automatically
    
    method operator/s:
        //  Note: "==" operators indicate getters, while "=" operators indicate setters.
        tag.z       == real z
        //  Returns the member z
        tag.height2 == real z
        //  Returns z as well for BC
        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
        //  If set to true, this will lock the transparency of
        //  the TextTag. Be careful when using.
        // Documented in v.1.4.10
        tag.fade == real fadepoint
        //  Returns the member fadepoint
        tag.fade = real fadepoint
        //  Determines the amount of time before the TextTag
        //  starts fading. (Watch out for the permanence property!)
        
        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.green = integer int(0 - 255)
        tag.blue = integer int(0 - 255)
        tag.alpha = integer int(0 - 255)
        //  Returns the red, green, blue, and alpha properties of the text tag.
        
        //  Alpha levels determine the visibility of the locally created text tag.
        //  255 means the instance is fully visible while 0 means transparent.
            
        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.applyForceVisibility(force f, boolean visible)
        tag.visible_to_force(force f, boolean b) // @deprecated
            //  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}
function CreateTextTagVJ(player p, real x, real y, real offset, integer red, integer green, integer blue, string msg) -> {TextTag}
    //  Creates a TextTag instance with the default settings.
    //  If you're not planning on saving the instance immediately,
    //  you can still access it via vj_lastCreatedTextTag
    {vj_lastCreatedTextTag} // or return vj_lastCreatedTextTag
    
//! endnovjass
//! novjass
    Update History:
    
    v.1.4.1.0 - Slightly changed the hashing algorithm used in GetHashID.
                - Exchanged execution speed for guaranteed unique indices
                  for the internal timers.
              - Revised the API to describe the machinations of the system
                in more detail.
              - The method visible_to_force has been marked for deprecation.
                You are advised to use the method applyForceVisibility instead.
                - Author's note: Turns out I forgot to include applyForceVisibility
                  as one of the publicly accessible methods.
    v.1.4.0.0 - Removed all requirements
    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/16.
endglobals
private function print takes string msg returns nothing
    call BJDebugMsg(msg)
endfunction
private module TextTagModule
    private  static constant integer    HASH_DIVISOR        = 37
    readonly static constant integer    ALLOC_SPACE         = 101
    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.
    private static timer    array       decayWatcher
    private static integer  array       decayWatchMap
    private static thistype array       activeList
    private static integer  array       timerId
    private thistype                    allocId
    //  Flag members
    private boolean is_active
    private boolean is_permanent
    //  Real members
    private real    fadepoint
    private real    size
    private real    dur
    private real    cx
    private real    cy
    private real    cz
    private real    dx
    private real    dy
    //  Message
    private string  str
    //  Color members
    private integer r
    private integer g
    private integer b
    private integer a
    private static method GetHashID takes timer whichtimer returns integer
        local integer id        = GetHandleId(whichtimer)
        local integer baseIndex = ModuloInteger(ModuloInteger(id, JASS_MAX_ARRAY_SIZE), HASH_DIVISOR)
        local integer iterIndex = 0
        if (decayWatchMap[baseIndex] == 0) or (decayWatchMap[baseIndex] == id) then
            if (decayWatchMap[baseIndex] == 0) then
                set decayWatchMap[baseIndex] = id
            endif
            return baseIndex
        endif
        set iterIndex       = baseIndex
        loop
            set iterIndex   = ModuloInteger(iterIndex + 1, HASH_DIVISOR)
            if (decayWatchMap[iterIndex] == 0) or (decayWatchMap[iterIndex] == id) then
                if (decayWatchMap[iterIndex] == 0) then
                    set decayWatchMap[iterIndex] = id
                endif
                return iterIndex
            endif
            exitwhen (iterIndex == baseIndex)
        endloop
        return -1
    endmethod
    private static method SetTimerID takes timer whichtimer, integer id returns nothing
        set timerId[GetHashID(whichtimer)]   = id
    endmethod
    private static method GetTimerID takes timer whichtimer returns integer
        return timerId[GetHashID(whichtimer)]
    endmethod
    private static method ApplyPlayerCondition takes integer pIndex returns boolean
        local player p  = null
        if pIndex < bj_MAX_PLAYER_SLOTS then
            set p = Player(pIndex)
        endif
        return (p == null) or (GetLocalPlayer() == p)
    endmethod
    private static method allocate takes integer i returns thistype
        local integer  j    = 0
        local thistype head = thistype(0)
        local thistype this = thistype(0)
        set head    = thistype(i*ALLOC_SPACE)
        set this    = head.allocId
        if (this.allocId == head) or (this.allocId == thistype(0)) then
            set j               = integer(this) + 1
            if j - integer(head) >= ALLOC_SPACE then
                return thistype(-1)
            endif
            set this            = thistype(j)
            set head.allocId    = this
        else
            //  Obtaining a recycled instance
            set head.allocId    = this.allocId
            set this.allocId    = 0
        endif
        set this.is_active      = true
        return this
    endmethod
    private method deallocate takes nothing returns nothing
        local integer mod   = ModuloInteger(integer(this), ALLOC_SPACE)
        local thistype head
        if not this.is_active then
            return
        endif
        if mod == 0 then
            return
        endif
        if this.allocId != thistype(0) then
            return
        endif
        set head            = thistype(integer(this)/ALLOC_SPACE*ALLOC_SPACE)
        set this.allocId    = head.allocId
        set head.allocId    = this
    endmethod
    method destroy takes nothing returns nothing
        local integer   pIndex  = integer(this)/ALLOC_SPACE
        local thistype  head    = thistype(pIndex*ALLOC_SPACE)
        local thistype  that    = thistype(0)
        local integer   i       = activeList[integer(head)]
        local integer   max     = i
        if not this.is_active then
            return
        endif
        if ApplyPlayerCondition(pIndex) then
            call DestroyTextTag(this.tag)
            set this.tag    = null
        endif
        call this.deallocate()
        set this.target         = null
        set this.widgetTarg     = null
        set this.is_permanent   = false
        set this.fadepoint      = 0.
        set this.size           = 0.
        set this.dur            = 0.
        set this.dx             = 0.
        set this.dy             = 0.
        set this.cx             = 0.
        set this.cy             = 0.
        set this.cz             = 0.
        set this.r              = 0
        set this.g              = 0
        set this.b              = 0
        set this.a              = 0
        set this.str            = null
        loop
            exitwhen i < 1
            set that    = activeList[integer(head) + i]
            if that == this then
                set that                            = activeList[integer(head) + max]
                set activeList[integer(head) + i]   = that
                set activeList[integer(head) + max] = 0
                set activeList[integer(head)]       = max - 1
                exitwhen true
            endif
            set i       = i - 1
        endloop
    endmethod
    
    //============================================================//
    //          Methods                                           //
    //============================================================//
    
    method setPosPrime takes real x, real y, real heightOffset, boolean detach returns nothing
        local integer pIndex        = integer(this)/ALLOC_SPACE
        if detach then
            set this.target         = null
            set this.widgetTarg    = null
        endif
        if this.target != null then
            set heightOffset = heightOffset - GetUnitFlyHeight(this.target)
        endif
        set this.cx     = x
        set this.cy     = y
        set this.cz     = heightOffset
        if ApplyPlayerCondition(pIndex) then
            if (not (this.dx != 0)) and (not (this.dy != 0)) then
                call SetTextTagPos(this.tag, x, y, heightOffset)
                return
            endif
            call DestroyTextTag(this.tag)
            set this.tag = CreateTextTag()
            call SetTextTagPos(this.tag, x, y, heightOffset)
            call SetTextTagText(this.tag, this.str, this.size*HEIGHT_OFFSET)
            call SetTextTagColor(this.tag, this.r, this.g, this.b, this.a)
            
            call SetTextTagPermanent(this.tag, this.is_permanent)
            if not this.is_permanent then
                call SetTextTagLifespan(this.tag, this.dur)
                call SetTextTagFadepoint(this.tag, RMaxBJ(this.fadepoint, 0))
            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 = integer(this)/ALLOC_SPACE
        if ApplyPlayerCondition(pIndex) then
            if xvel != 0 or yvel != 0 then
                call SetTextTagVelocity(this.tag, xvel*VELOCITY_OFFSET, yvel*VELOCITY_OFFSET)
            else
                call this.setPos(this.cx, this.cy, this.cz)
            endif
        endif
        set this.dx     = xvel
        set this.dy     = yvel
    endmethod
    
    method setVelocEx takes real speed, real angle returns nothing
        call this.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 applyForceVisibility takes force f, boolean b returns nothing
        local integer pIndex = integer(this)/ALLOC_SPACE
        if pIndex < bj_MAX_PLAYER_SLOTS then
            return
        endif
        if IsPlayerInForce(GetLocalPlayer(), f) then
            call SetTextTagVisibility(this.tag, b)
        endif
    endmethod
    // @deprecated
    method visible_to_force takes force f, boolean b returns nothing
        call BJDebugMsg("TextTag:visible_to_force >> This will be deprecated in later versions. Use applyForceVisibility instead.")
        call this.applyForceVisibility(f, b)
    endmethod
    //============================================================//
    //          Method operators                                  //
    //============================================================//
    
    method operator permanent takes nothing returns boolean
        return this.is_permanent
    endmethod
    method operator permanent= takes boolean flag returns nothing
        local integer pIndex    = integer(this)/ALLOC_SPACE
        set this.is_permanent   = flag
        if not ApplyPlayerCondition(pIndex) then
            return
        endif
        call SetTextTagPermanent(this.tag, flag)
    endmethod
    
    method operator duration takes nothing returns real
        if this.is_permanent then
            return -1.
        endif
        return this.dur
    endmethod
    method operator duration= takes real r returns nothing
        local integer pIndex    = integer(this)/ALLOC_SPACE
        set this.dur            = r
        if not ApplyPlayerCondition(pIndex) then
            return
        endif
        call SetTextTagLifespan(this.tag, r)
    endmethod
    
    method operator msg takes nothing returns string
        return this.str
    endmethod
    method operator msg= takes string str returns nothing
        local integer pIndex    = integer(this)/ALLOC_SPACE
        set this.str            = str
        if not ApplyPlayerCondition(pIndex) then
            return
        endif
        call SetTextTagText(this.tag, this.str, this.size*HEIGHT_OFFSET)
    endmethod
    
    //  These methods are wrappers...
    method operator message takes nothing returns string
        return this.str
    endmethod
    method operator message= takes string str returns nothing
        set this.msg    = str
    endmethod
    
    //  A newer method operator that actually returns the height of the text tag.
    method operator z takes nothing returns real
        return this.cz
    endmethod
    method operator z= takes real new returns nothing
        local integer pIndex    = integer(this)/ALLOC_SPACE
        if this.widgetTarg != null then
            call this.setPos(GetWidgetX(this.widgetTarg), GetWidgetY(this.widgetTarg), new)
        elseif this.target != null then
            call this.setPos(GetUnitX(this.target), GetUnitY(this.target), new)
        else
            call this.setPos(cx, cy, new)
        endif
    endmethod
    
    method operator height2 takes nothing returns real
        return this.cz
    endmethod
    method operator height2= takes real new returns nothing
        set this.z = new
    endmethod
    
    //  Cannot deal away with backwards compatibility here..
    method operator height takes nothing returns real
        return this.size
    endmethod
    method operator height= takes real h returns nothing
        local integer pIndex    = integer(this)/ALLOC_SPACE
        set this.size           = h
        if not ApplyPlayerCondition(pIndex) then
            return
        endif
        call SetTextTagText(this.tag, this.str, this.size*HEIGHT_OFFSET)
    endmethod
    
    method operator fade takes nothing returns real
        return this.fadepoint
    endmethod
    method operator fade= takes real fader returns nothing
        local integer pIndex    = integer(this)/ALLOC_SPACE
        if this.is_permanent then
            return
        endif
        set this.fadepoint      = RMinBJ(RAbsBJ(fader), this.dur)
        if not ApplyPlayerCondition(pIndex) then
            return
        endif
        call SetTextTagFadepoint(this.tag, fadepoint)
    endmethod
    
    method operator red takes nothing returns integer
        return this.r
    endmethod
    method operator red= takes integer value returns nothing
        local integer pIndex    = integer(this)/ALLOC_SPACE
        set this.r              = value
        if not ApplyPlayerCondition(pIndex) then
            return
        endif
        call SetTextTagColor(this.tag, this.r, this.g, this.b, 255 - this.a)
    endmethod
    
    method operator green takes nothing returns integer
        return this.g
    endmethod
    method operator green= takes integer value returns nothing
        local integer pIndex    = integer(this)/ALLOC_SPACE
        set this.g              = value
        if not ApplyPlayerCondition(pIndex) then
            return
        endif
        call SetTextTagColor(this.tag, this.r, this.g, this.b, 255 - this.a)
    endmethod
    
    method operator blue takes nothing returns integer
        return this.b
    endmethod
    method operator blue= takes integer value returns nothing
        local integer pIndex    = integer(this)/ALLOC_SPACE
        set this.b              = value
        if not ApplyPlayerCondition(pIndex) then
            return
        endif
        call SetTextTagColor(this.tag, this.r, this.g, this.b, 255 - this.a)
    endmethod
    
    method operator alpha takes nothing returns integer
        return this.a
    endmethod
    method operator alpha= takes integer value returns nothing
        local integer pIndex    = integer(this)/ALLOC_SPACE
        set this.a              = value
        if not ApplyPlayerCondition(pIndex) then
            return
        endif
        call SetTextTagColor(this.tag, this.r, this.g, this.b, 255 - this.a)
    endmethod
    
    method operator widget takes nothing returns widget
        return this.widgetTarg
    endmethod
    method operator widget= takes widget wid returns nothing
        set this.target     = null
        set this.widgetTarg = wid
        if wid  != null then
            call this.setVeloc(0,0)
            call this.setPos(GetWidgetX(wid), GetWidgetY(wid), this.cz)
        endif
    endmethod
    
    method operator unit takes nothing returns unit
        return this.target
    endmethod
    method operator unit= takes unit u returns nothing
        set this.widgetTarg = null
        set this.target     = u
        if u != null then
            call this.setVeloc(0,0)
            call this.setPos(GetUnitX(u), GetUnitY(u), this.cz)
        endif
    endmethod
    
    method operator texttag takes nothing returns texttag
        return this.tag
    endmethod
    //  =================================   //
    //  Create and onTick methods           //
    //  =================================   //
    private method onTickUpdate takes nothing returns boolean
        if this.widgetTarg != null then
            call this.setPos(GetWidgetX(this.widgetTarg), GetWidgetY(this.widgetTarg), this.cz)
        elseif this.target != null then
            call this.setPos(GetUnitX(this.target), GetUnitY(this.target), this.cz)
        else
            set this.cx = this.cx + this.dx*INTERVAL
            set this.cy = this.cy + this.dy*INTERVAL
        endif
        
        if this.is_permanent then
            return true
        endif
        set this.dur        = this.dur - INTERVAL
        set this.fadepoint  = this.fadepoint - INTERVAL
        if this.dur <= 0. then
            call this.destroy()
            return false
        endif
        return true
    endmethod
    private static method onTick takes nothing returns nothing
        local integer   pIndex  = GetTimerID(GetExpiredTimer())
        local integer   i       = 1
        local thistype  head    = thistype(pIndex*ALLOC_SPACE)
        local thistype  this    = 0
        loop
            exitwhen i > integer(activeList[integer(head)])
            set this    = activeList[integer(head) + i]
            if not this.onTickUpdate() then
                set i   = i - 1
            endif
            set i       = i + 1
        endloop
        if activeList[integer(head)] <= 0 then
            call PauseTimer(decayWatcher[pIndex])
        endif
    endmethod
    
    static method create takes integer pIndex returns thistype
        local thistype this     = 0
        local thistype head     = 0
        local integer  index    = 0
        local player   p        = null
        if pIndex < 0 or pIndex > bj_MAX_PLAYER_SLOTS then
            set pIndex  = bj_MAX_PLAYER_SLOTS
        endif
        set head = thistype(pIndex*ALLOC_SPACE) 
        set this = thistype.allocate(pIndex)
        if this == thistype(-1) then
            return this
        endif
        set index = activeList[integer(head)] + 1           
        set activeList[integer(head)]          = index
        set activeList[integer(head) + index]  = this
        if ApplyPlayerCondition(pIndex) then
            set this.tag    = CreateTextTag()
        endif
        if activeList[integer(head)] == 1 then
            call TimerStart(decayWatcher[pIndex], INTERVAL, true, /*
                            */ function thistype.onTick)
        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    = integer(this)/ALLOC_SPACE
        
        if pIndex == playerId then
            return this
        endif
        
        set new = thistype.create(playerId)
        if new == thistype(-1) then
            return new
        endif
        
        set new.duration    = this.dur
        set new.fade        = this.fadepoint
        set new.permanent   = this.is_permanent
        set new.height      = this.size
        set new.red         = this.r
        set new.green       = this.g
        set new.blue        = this.b
        set new.alpha       = this.a
        set new.msg         = this.msg
        call new.setPos(this.cx, this.cy, this.cz)
        call new.setVeloc(this.dx, this.dy)
        return new
    endmethod
    //  Same as the method generate but destroys the base instance
    method transfer takes integer playerId returns thistype
        local thistype that = this.generate(playerId)
        if that == -1 then
            return this
        endif
        call this.destroy()
        return that
    endmethod

    private static method onInit takes nothing returns nothing
        local integer i     = 0
        local thistype head
        loop
            exitwhen i > bj_MAX_PLAYER_SLOTS
            set head                        = thistype(i*ALLOC_SPACE)
            set head.allocId                = head
            set activeList[integer(head)]   = 0
            set decayWatcher[i]             = CreateTimer()
            call SetTimerID(decayWatcher[i], i)
            set i   = i + 1
        endloop
    endmethod
endmodule
struct TextTag extends array
    readonly widget  widgetTarg
    readonly unit    target
    readonly texttag tag
    implement TextTagModule
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      = TextTag(0)
    local integer pIndex    = bj_MAX_PLAYER_SLOTS
    if p != null then
        set pIndex  = GetPlayerId(p)
    endif
    set this        = TextTag.create(pIndex)
    if this == TextTag(-1) then
        return this
    endif
    set this.duration       = TextTag.DEFAULT_DURATION
    set this.fade           = TextTag.DEFAULT_DURATION - 0.5
    set this.height         = TextTag.DEFAULT_HEIGHT
    set this.permanent      = false
    set this.red            = red
    set this.green          = green
    set this.blue           = blue
    set this.alpha          = 0
    set this.msg            = msg
    call this.setPos(x, y, offset)
    call this.setVelocEx(TextTag.DEFAULT_VELOC_SPEED, TextTag.DEFAULT_VELOC_ANGLE)
    set vj_lastCreatedTextTag = this
    return vj_lastCreatedTextTag
endfunction
function CreateTextTagVJ takes player p, real x, real y, real offset, integer red, integer green, integer blue, string msg returns TextTag
    return CreateTextTagBJ(p, x, y, offset, red, green, blue, msg)
endfunction
endlibrary
[/hidden]
 
Last edited:
Also you do not need timers either, just use SetTextTagLifespan and will be destroyed automatically, can check it by using GetHandleId for each created texttag.

Alright, I'll try to change it a bit.

EDIT:

By using SetTextTagLifespan, I did destroy its' reference and its' reference only, which is to say that the text tag itself is not destroyed (aesthetically), so I went about it another way, using a TriggerSleepAction and then destroying the instance, recycling it for further use, which practically removes the need for the timers.

Going to add description once again.

P.S.

Nice spell for your signature
 
Last edited:
Level 9
Joined
Jun 21, 2012
Messages
432
By using SetTextTagLifespan, I did destroy its' reference and its' reference only, which is to say that the text tag itself is not destroyed (aesthetically), so I went about it another way, using a TriggerSleepAction and then destroying the instance, recycling it for further use, which practically removes the need for the timers.

No, when SetTextTagLifespan is used you don't need to use timers or sleep actions because it is automatically destroyed.

For texttags you can instanciate by using GetHandleId, see this example:

JASS:
    method operator lifespan= takes real lifespan returns nothing
        if(0<=lifespan)then
            call SetTextTagPermanent(.tag,false)
            call SetTextTagLifespan(.tag,lifespan)
            //if(0==lifespan)then
                set .tag=null
            //endif
        endif
    endmethod

    method destroy takes nothing returns nothing
        set .lifespan=0
        //set .tag=null
    endmethod

    static method create takes string text, real size, real x, real y, real z, real lifespan returns Texttag
         local texttag t=CreateTextTag()
         local thistype this=GetHandleId(t)
         set .tag=t
         call .setText(text,size)
         call SetTextTagPos(.tag,x,y,z)
         if(0<lifespan)then
             set .lifespan=lifespan
         endif
         set t=null
         return this
     endmethod
 
Last edited:
No, when SetTextTagLifespan is used you don't need to use timers or sleep actions because it is automatically destroyed.

For texttags you can instanciate by using GetHandleId, see this example:

JASS:
    method operator lifespan= takes real lifespan returns nothing
        if(0<=lifespan)then
            call SetTextTagPermanent(.tag,false)
            call SetTextTagLifespan(.tag,lifespan)
            if(0==lifespan)then
                set .tag=null
            endif
        endif
    endmethod

    method destroy takes nothing returns nothing
        set .lifespan=0
        //set .tag=null
    endmethod

    static method create takes string text, real size, real x, real y, real z, real lifespan returns Texttag
         local texttag t=CreateTextTag()
         local thistype this=GetHandleId(t)
         set .tag=t
         call .setText(text,size)
         call SetTextTagPos(.tag,x,y,z)
         if(0<lifespan)then
             set .lifespan=lifespan
         endif
         set t=null
         return this
     endmethod

Ah, I see. I misinterpreted your example before, so I assumed that the allocation would be by the struct itself. I'll test your snippet and add something... :grin:
 
Is this KILLCIDE still the case?
Maybe we can need a simple one for textags, I'm not 100% sure right now if we have one.

VTextTag seems to do lot more than needed for simple texttag handling (imo such movements should be handled outside a textag lib), and TextSplat2 is good, but it's an alternative, and doesn't work with actual texttags (but images).

One system with defining/changing all needed parameters like string/life/speed/position etc of a texttag and maybe with feature of "attaching" it to a unit would be maybe good.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
I think that effects should be handled locally. As such, there is no reason to not have a standalone texttag system with another system for applying effects to texttags. This can be done by having a timer that is always running, but different lists for each player for effects ; ).


Also, this lib even as a simple thing is bad ; P. I was thinking of the other one, which applies effects to tags globally.
 
Following JASS Submission Rules is also required.
One system with defining/changing all needed parameters like string/life/speed/position etc of a texttag and maybe with feature of "attaching" it to a unit would be maybe good.
Effects should be outsourced, but a unit update is maybe still good to have, as it's pretty basic and often used. It would only be with updating x/y/z periodicly.
MyPad, could you consider this?
For example with access to parameters a struct syntax might become handy.
Also some proper documentation, like description of API is needed and very helpful.

Attaching to a widget is nice approach, but I believe that it should also consider flying height in case it's a unit.
Maybe a specified unit-attach function would be an easy solution.
 
MyPad, every other texttag resource uses GetLocalPlayer. Each individual player can have 100 texttags. By creating them only for local players, you support more texttags. This is a completely required feature =).

Okay. I'll try to see if I can add GetLocalPlayer support with global variables.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
static method create takes lots of parameters Thats not really helpful API description/documentation.

What about allowing texttags to have different sizes? What if I want some big ass text for zones, but small texts for hitsplats? Thats currently impossible using this system
 
Okay, then. Thanks for the method. I'll give credit to you for that.

static method create takes lots of parameters Thats not really helpful API description/documentation.

What about allowing texttags to have different sizes? What if I want some big ass text for zones, but small texts for hitsplats? Thats currently impossible using this system

Alright. I'll modify the create method to reduce the number of arguments required. As what you have said, it takes a lot of parameters indeed. They are:

X-abscissa, Y-ordinate, velocity in X, velocity in Y, text height, text z-offset, duration of tag, fadepoint of tag, red, green, blue, alpha, and string.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
I am not really a fan of crediting someone just cause they did it before you, if you had no prior knowledge of them doing it and built it yourself, but this is endless war.

I didnt mean to reduce the number of arguments per se, just document them better, if you name them in the description, and then maybe explain some of the weirder ones, users have no need to go scrolling through the code to see the API for themselves, like I had to to check it.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
My policy is that if you go to their code and rip and or improve upon what they wrote, you credit them : ). Not doing so is plagiarism.


kingkingy from TheHelper is actually the person that made Unit Indexer systems viable. They told me that the undefend ability gets removed from the unit on the 2nd fire. Before that, everyone had to use timers. I never did credit them and I should have. Every Unit Indexer system today is based upon what kingkingy discovered as well as the work of whatisface from wc3c ; ).
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
to best of my knowledge, Bribe's unit indexer doesnt use undefend.

Also there is no proof he went and ripped your code from you script, and there are two easy counter arguments to that claim:

1. Its hosted on github, most ppl dont even know your github page exists(noone will bother clicking the link, and even if they do they wont go around browsing the github page, its just inconventient)
2. Plagiarism on your code is basically impossible, noones gonna decrypt that shit
 
to best of my knowledge, Bribe's unit indexer doesnt use undefend.

Also there is no proof he went and ripped your code from you script, and there are two easy counter arguments to that claim:

1. Its hosted on github, most ppl dont even know your github page exists(noone will bother clicking the link, and even if they do they wont go around browsing the github page, its just inconventient)
2. Plagiarism on your code is basically impossible, noones gonna decrypt that shit

I'll try my wits on that.

I am not really a fan of crediting someone just cause they did it before you, if you had no prior knowledge of them doing it and built it yourself, but this is endless war.

I didnt mean to reduce the number of arguments per se, just document them better, if you name them in the description, and then maybe explain some of the weirder ones, users have no need to go scrolling through the code to see the API for themselves, like I had to to check it.

Ah, sorry about improper documentation. I'll make it clear and more concise next time.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Oh snap. VTextTag didn't use my allocation scheme for texttags =O. I stand corrected.

You have a local pointer and a global pointer. The local one is specific to each player. The global one is what everyone sees. Every player gets stuff like a timer for the texttag to prevent desyncs. The local pointer will contain the actual texttag, which will be a different handle id for every player.

Each player can support 100 local texttags. Many texttags are only visible to certain players. Rather than setting a flag, instead create it only for those players. This will increase the amount of texttags that can be active at one time.
 
Last edited:
Oh snap. VTextTag didn't use my allocation scheme for texttags =O. I stand corrected.

You have a local pointer and a global pointer. The local one is specific to each player. The global one is what everyone sees. Every player gets stuff like a timer for the texttag to prevent desyncs. The local pointer will contain the actual texttag, which will be a different handle id for every player.

Each player can support 100 local texttags. Many texttags are only visible to certain players. Rather than setting a flag, instead create it only for those players. This will increase the amount of texttags that can be active at one time.

Done with the local text tags. Global text tags, I'm not so sure...
 
Last edited:
Add docu, like description of API.

Functions like the B2S should be private only, if it's required for the system.

JASS:
private method getPlayerInstance takes nothing returns integer
            local integer i = this/100
            local integer mod = this - (this/100)*100
            if mod == 0 then
                set i = i - 1
            endif
            return i
        endmethod
Why needed?

The system should periodicly consider the unit's flying height, as it might change. Not only at registration.

There might be powers to handle attributes of a texttag, like colors, etc, dynamicly.
 
Add docu, like description of API.

Functions like the B2S should be private only, if it's required for the system.

The system should periodicly consider the unit's flying height, as it might change. Not only at registration.

There might be powers to handle attributes of a texttag, like colors, etc, dynamicly.

Sorry about the B2S function, it was suited for debugging. However, I could consider adding the dynamic nature of the flying height of the unit, if need be. I recoded this snippet from scratch so that I could think it through a bit more clearly.

JASS:
private method getPlayerInstance takes nothing returns integer
    local integer i = this/100
    local integer mod = this - (this/100)*100
    if mod == 0 then
        set i = i - 1
    endif
    return i
endmethod
Why needed?

I got into a lot of situations wherein I would have to manually type the contents inside the method. Since I saw them in most of my usable code, I just had to save myself from some sort of eyesore and make a private method for that.

Although I could've just used a textmacro for it, but it gives you a feeling of disdain towards it

EDIT:
Thinking about it, I will add those features that you've mentioned. I will update the library with API description. Thanks for the feedback!
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
Each texttag will need a timer for destroying the tag when its lifespan is out, right? You can use the timer as a unique signature for the text tag that external libraries that apply advanced animations to the tag can use to determine whether the tag they are referencing is still alive or not.


JASS:
public method operator signature takes nothing returns handle
    return timer
endmethod



if (tag.signature == myHeldSignature) then
    call applyEffect()
else
    call destroyEffect()
endif



You might also want to take a look at my Position library. It boxes up widgets and locations into a single struct. This would allow you to easily anchor your texttag to any type of location. Alternatively you could just take a widget instead of a unit and leave the complexity of item behavior to the user.
 
Last edited:
Np man, take your time. : )
Added documentation for the library.
Rearranged methods from private to public.

I'm going to add some more features later. :grin:
Thanks!

EDIT:

Added the ability to change the red, green, and blue colors of the text tag, its' lifespan and permanence.
 
Last edited:
B2S must be private.

Give the user one speed and one angle argument.

Some argument names could be better in API (description at least):
h = height
o = z
(where is the difference and what is "o" ?)

"Cloak" -> "alpha" in description, the wording is more clear and intuitive if precise words are used.

Fade could be defineable I think, it might be useful sometimes. I made a map where I wanted some texts to fade out slowly and smooth, and some a bit quicker.

also in method attachToUnit takes unit u, real o returns nothing
^I don't really can intuitivly say what "o" should define.

It should be mentioned that the unit attachment function takes flying height of unit into consideration, while at the widget function the height is static/user defined.

I'm not really sure in what case to use function TriggerRegisterTagEvent takes trigger t returns nothing from reading API.

JASS:
static method localPlayer takes integer id returns player
            if id > bj_MAX_PLAYER_SLOTS then
                return null
            endif
            return Player(id)
        endmethod
How is this useful?

JASS:
static method operator [] takes player id returns thistype
            if id != null then
                return GetPlayerId(id)
            endif
            return bj_MAX_PLAYER_SLOTS + 1
        endmethod[
this could just return always the PlayerId.

static method operator getExTimer takes nothing returns thistype
^when reading header I expect a timer to be returned, but the actual instance is, something like getTimerInstance takes timer t returns thistype would be better.

For binding timers to instances a table could be used instead of linear searching through all possible instances.

private static LocalTextTag current_timer = 0
the declaration names does not fit the type of variable, timer vs LocalTexttag

Name conventions differs here and there, sometimes you start with capical letters, sometimes with small letters, sometimes with underscores "_" are used.
JPAG - JASS Proper Application Guide are the usual conventions we use to follow. : )

Why are there some readonly members when there exist method operators for them?
Why is for example "alpha" private, but the RGB values readonly?

JASS:
private static method SyncIndex takes integer curr returns nothing
            set LocalTextTag(curr).Index = LocalTextTag(curr).Index - 1
        endmethod
I'm not really sure what is being synced.

What is for example "instanceCheck" there's no docu or so, and by it's name I expect it to return a boolean, it does not. Also some other cases..

Why do the set method operators not actually change the text/color etc, but "only" change the member values?

Also could you add a small demo map to the main post? and preferable a version number
 
B2S must be private.

Give the user one speed and one angle argument.

Some argument names could be better in API (description at least):
h = height
o = z
(where is the difference and what is "o" ?)

"Cloak" -> "alpha" in description, the wording is more clear and intuitive if precise words are used.

Fade could be defineable I think, it might be useful sometimes. I made a map where I wanted some texts to fade out slowly and smooth, and some a bit quicker.

also in method attachToUnit takes unit u, real o returns nothing
^I don't really can intuitivly say what "o" should define.

It should be mentioned that the unit attachment function takes flying height of unit into consideration, while at the widget function the height is static/user defined.

I'm not really sure in what case to use function TriggerRegisterTagEvent takes trigger t returns nothing from reading API.

JASS:
static method localPlayer takes integer id returns player
            if id > bj_MAX_PLAYER_SLOTS then
                return null
            endif
            return Player(id)
        endmethod
How is this useful?

JASS:
static method operator [] takes player id returns thistype
            if id != null then
                return GetPlayerId(id)
            endif
            return bj_MAX_PLAYER_SLOTS + 1
        endmethod[
this could just return always the PlayerId.

static method operator getExTimer takes nothing returns thistype
^when reading header I expect a timer to be returned, but the actual instance is, something like getTimerInstance takes timer t returns thistype would be better.

For binding timers to instances a table could be used instead of linear searching through all possible instances.

private static LocalTextTag current_timer = 0
the declaration names does not fit the type of variable, timer vs LocalTexttag

Name conventions differs here and there, sometimes you start with capical letters, sometimes with small letters, sometimes with underscores "_" are used.
JPAG - JASS Proper Application Guide are the usual conventions we use to follow. : )

Why are there some readonly members when there exist method operators for them?
Why is for example "alpha" private, but the RGB values readonly?

JASS:
private static method SyncIndex takes integer curr returns nothing
            set LocalTextTag(curr).Index = LocalTextTag(curr).Index - 1
        endmethod
I'm not really sure what is being synced.

What is for example "instanceCheck" there's no docu or so, and by it's name I expect it to return a boolean, it does not. Also some other cases..

Why do the set method operators not actually change the text/color etc, but "only" change the member values?

Also could you add a small demo map to the main post? and preferable a version number

Sorry for the late reply, and bad design as well. Due to the thread not flowing with content, I just left it as is. I'll update this later.

JASS:
static method operator [] takes player id returns thistype
    if id != null then
        return GetPlayerId(id)
    endif
    return bj_MAX_PLAYER_SLOTS + 1
endmethod

Just in case you would input a null value when creating a text tag, it will return something in the lines of 16. Going to test your implication later.
And about the LocalTextTag, I should have made it more clear that it is private.

I'll think about updating the demo code and demo map. I'm using a separate map to write my system, so I haven't really updated it, but I'll consider adding a version number.

What do you think should be its' version number? I'm thinking, "v.1.1.0"


EDIT:
Tested it when player parameter is null with the static method operator, returned 0.

With the operators, I just wanted to make sure that the system will not malfunction, and I've now updated certain parts of the code. Expect to see a flicker method for the text tag (works with non-permanent ones).
 
Last edited:
Bump!

With the operators, I just wanted to make sure that the system will not malfunction, and I've now updated certain parts of the code. Expect to see a flicker method for the text tag (works with non-permanent ones).

Lies! What you told us are lies!
Anyway, I'm now going to work on re-adding the features lost.

EDIT:

Re-added the widget and unit attachment functions.
The library now uses double-linked listing for iteration and checking.

static method create takes lots of parameters Thats not really helpful API description/documentation.

What about allowing texttags to have different sizes? What if I want some big ass text for zones, but small texts for hitsplats? Thats currently impossible using this system

Please ignore this quote, I have changed it now so that all you need to provide is the player id.
 
Last edited:
Level 1
Joined
Jun 29, 2017
Messages
1
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))"
 
I don't know why to repeat all things, always. No offense, but such little things slow down reviewing and makes things less fun.
Docu API needs to be complete. Some operators, for example are completly missing. For some, getters are missing. List everything that can/should be used.

No idea where to get the (optinal) library from. Once again, read rules: JASS Submission Rules

Also, the parameter type could be written:

call TextTag.create(playerId)
->
[/icode]call TextTag.create(integer playerId)[/icode]
^defining types makes API easier to understand.

JASS:
//  Apply modulo...
                set pIndex = pIndex - pIndex/(bj_MAX_PLAYER_SLOTS + 1)*(bj_MAX_PLAYER_SLOTS + 1) + (bj_MAX_PLAYER_SLOTS + 1)
^That's example for a comment that can be improved. When there are calculations, or parts in code which intuitivly make not much sense, then it's good to describe why something is done, and not only "modulo" - it doesn't help very good.

Why do I need a timer at allocation with tickrate? Isn't one only needed in case of position update / velocity?

It's nowhere explained what happens with an integer above max player slots.

Using a list library, or something similar to keep track of texttags and its amount would make code much simpler to read. All calculs with this/ALLOC_MULTIPLIER, and things alike makes code bigger and gives not required complexities.

It's nice that unit attaching, with ticks is supported, and also that the flying height is considered.

For member msg, I would personlay prefer to write it our message, or simply text

Not required, but could you attach a demo?
 
^^ System code will be commented. This time, I'm just going to use one library that exists.

About using other people's libraries, I will try my best to use them when submitting newer libraries, as this one is a slightly older library. =)

I've added method operators for message (wrappers). (I kept the original method operator)

As for when an integer exceeds the max player slots, the integer is reduced back to the max player slots, thereby creating a global instance.

The timer stuff is not actually touched by the TextTag instances. They are reserved for the head nodes.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
One feature that you could add is support for permanent text tags.
Normally when you run out of text tag slots, the oldest one is deleted.
However, if you kept track of them it would be possible to mark some text tags as "permanent", so in that case the system would first delete one of the non-permanent ones, to prevent WC3 from preventing any that are supposed to be permanent.
The only way that would break is if someone marks 100 text tags as permanent and tries to make a new one.
 
^^ I refer to height and size synonymously because of the z-coordinate being the actual height of the Text Tag. (sometimes I would prefer calling them as offset). However, manipulating the height (not the Text size of the Text tag) dynamically is somewhat not supported, while changing the size of a text tag is supported.

In case of confusion, you can suggest more intuitive names for the method operators.
 
Top