• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[System] TextTagUtils

Level 11
Joined
Apr 29, 2007
Messages
826
So I made this system to ease the use with texttags, which was pretty uncomfortable. I think everything is explained well enough in the documentation.

Requires:


TextTagUtils v2.0
by YourName

TextTagUtils as a system which should ease the use with texttags for you. Creating a new texttag takes only one texttagconfig struct which contains all the neccessary data. These structs can be saved to easily reuse certain styles, you just have to change the text and position.

API:

  • Predefined texttagconfigs
    JASS:
    TEXTTAG_GOLD //Gold texttag when workers return gold
    TEXTTAG_WOOD //Wood texttag when workers return wood
    TEXTTAG_BOUNTY //When you kill a neutral creep
    TEXTTAG_CRITICAL //Critical Strike-like
    TEXTTAG_DEFAULT //Just a texttag with default settings which can be modified for instantly creating new texttags
  • struct texttagconfig
    JASS:
    string text
    real size
    
    integer red
    integer green
    integer blue
    
    real lifespan
    real fadepoint
    
    real angle
    real speed
    
    real x
    real y
    real z
    
    boolean visible
    boolean permanent
  • NewTextTag
    JASS:
     function NewTextTag takes texttagconfig data returns texttag
  • struct TextBar
    JASS:
    string empty
    string full
    JASS:
    static method create takes integer tiles, integer tilesfilled, real size returns TextBar
    
    method getFilled takes nothing returns integer
    method getTiles takes nothing returns integer
    method getTextTag takes nothing returns texttag
    method getTile takes nothing returns string
    method getPercent takes nothing returns real
    
    method setFilled takes integer filled returns nothing
    method setPercent takes integer percent returns nothing
    method setTile takes string tile returns nothing
  • struct AttachedText
    JASS:
    static method create takes texttag which, unit to, real offset returns AttachedText
    
    method setTextTag takes texttag which returns nothing
    method setTarget takes unit to returns nothing
    method setOffset takes real z returns nothing
Sample Usage:
JASS:
//Attaching a textbar to a unit is this simple!
local TextBar tb = TextBar.create(10, 5, 9)
call AttachedText.create(tb.getTextTag(), SomeRandomUnit, 0)

//Creating a custom bounty texttag
set TEXTTAG_BOUNTY.text = I2S(GetRandomBounty)
set TEXTTAG_BOUNTY.x = GetUnitX(GetDyingUnit())
set TEXTTAG_BOUNTY.y = GetUnitY(GetDyingUnit())
call NewTextTag(TEXTTAG_BOUNTY)

JASS:
//! zinc

library TextTagUtils
{

    constant string DEFAULT_TEXT = "";

    constant real DEFAULT_SIZE = 10.;

    constant integer DEFAULT_RED = 255;
    constant integer DEFAULT_GREEN = 255;
    constant integer DEFAULT_BLUE = 255;
    constant integer DEFAULT_ALPHA = 255;

    constant real DEFAULT_LIFESPAN = 3.;
    constant real DEFAULT_FADEPOINT = 2.;

    constant real DEFAULT_SPEED = 64.;
    constant real DEFAULT_ANGLE = 90.;

    constant real DEFAULT_X = 0.;
    constant real DEFAULT_Y = 0.;
    constant real DEFAULT_Z = 0.;

    constant boolean DEFAULT_VISIBILITY = true;
    constant boolean DEFAULT_PERMANENT = false;


    public
    {
        texttagconfig TEXTTAG_GOLD;
        texttagconfig TEXTTAG_WOOD;

        texttagconfig TEXTTAG_BOUNTY;
        texttagconfig TEXTTAG_CRITICAL;

        texttagconfig TEXTTAG_DEFAULT;
    }


    //Tile used by textbars. Can be changed with structname.setTile(new tile) though.
    constant string TEXTBAR_TILE = "|";
    //Color which is used for an empty tile.
    constant string TEXTBAR_COLOR_EMPTY = "FFFFFF";
    //Color which is used for a full tile.
    constant string TEXTBAR_COLOR_FULL = "000000";


    //How often the attached texttag updates.
    constant real UPDATE_INTERVAL = 0.02;
    //If the texttag should be removed aswell when the struct gets destroyed.
    constant boolean AUTO_DESTROY = true;


    public struct texttagconfig
    {
        string text = DEFAULT_TEXT;

        real size = DEFAULT_SIZE;

        integer red = DEFAULT_RED;
        integer green = DEFAULT_GREEN;
        integer blue = DEFAULT_BLUE;
        integer alpha = DEFAULT_ALPHA;

        real lifespan = DEFAULT_LIFESPAN;
        real fadepoint = DEFAULT_FADEPOINT;

        real speed = DEFAULT_SPEED;
        real angle = DEFAULT_ANGLE;

        real x = DEFAULT_X;
        real y = DEFAULT_Y;
        real z = DEFAULT_Z;

        boolean visible = DEFAULT_VISIBILITY;
        boolean permanent = DEFAULT_PERMANENT;

        private static method onInit()
        {
            TEXTTAG_GOLD = thistype.allocate();
            TEXTTAG_GOLD.red = 255;
            TEXTTAG_GOLD.green = 220;
            TEXTTAG_GOLD.blue = 0;
            TEXTTAG_GOLD.lifespan = 2;
            TEXTTAG_GOLD.fadepoint = 1;

            TEXTTAG_WOOD = thistype.allocate();
            TEXTTAG_WOOD.red = 0;
            TEXTTAG_WOOD.green = 200;
            TEXTTAG_WOOD.blue = 80;
            TEXTTAG_WOOD.lifespan = 2;
            TEXTTAG_WOOD.fadepoint = 1;

            TEXTTAG_BOUNTY = thistype.allocate();
            TEXTTAG_BOUNTY.red = 255;
            TEXTTAG_BOUNTY.green = 220;
            TEXTTAG_BOUNTY.blue = 0;

            TEXTTAG_CRITICAL = thistype.allocate();
            TEXTTAG_CRITICAL.red = 255;
            TEXTTAG_CRITICAL.green = 0;
            TEXTTAG_CRITICAL.blue = 0;
            TEXTTAG_CRITICAL.lifespan = 5;
            TEXTTAG_CRITICAL.fadepoint = 2;

            TEXTTAG_DEFAULT = thistype.allocate();
        }
    }


    public function NewTextTag(texttagconfig data) -> texttag
    {
        texttag tt = CreateTextTag();
        real vel, xvel, yvel;

        SetTextTagText(tt, data.text, data.size*0.0023);
        SetTextTagPos(tt, data.x, data.y, data.z);

        SetTextTagColor(tt, data.red, data.green, data.blue, data.alpha);

        SetTextTagVisibility(tt, data.visible);
        SetTextTagPermanent(tt, data.permanent);

        vel = data.speed*0.071/128;

        xvel = vel*Cos(data.angle*bj_DEGTORAD);
        yvel = vel*Sin(data.angle*bj_DEGTORAD);

        SetTextTagVelocity(tt, xvel, yvel);

        SetTextTagLifespan(tt, data.lifespan);
        SetTextTagFadepoint(tt, data.fadepoint);

        return tt;
    }


    public struct TextBar
    {
        string empty = TEXTBAR_COLOR_EMPTY;
        string full = TEXTBAR_COLOR_FULL;

        private
        {
            string tile = TEXTBAR_TILE;

            texttag tt;

            integer filled;
            integer maxTiles;

            real size;
        }

        static method create(integer tiles, integer start, real siz) -> thistype
        {
            thistype this = thistype.allocate();
            integer i = 0;
            string text = "";

            this.tt = CreateTextTag();

            this.size = siz*0.0023;

            this.filled = 0;
            this.maxTiles = tiles;

            if (this.tile == "|") { this.tile = "||"; }

            if (start > 0)
            {
                while(this.filled < start)
                {
                    text = text + "|cff" + this.full + this.tile + "|r";
                    this.filled += 1;
                }
            }

            i = this.filled;
            if (i < this.maxTiles)
            {
                while(i < this.maxTiles)
                {
                    text = text + "|cff" + this.empty + this.tile + "|r";
                    i += 1;
                }
            }

            SetTextTagText(this.tt, text, this.size);

            return this;
        }

        method setFilled(integer fld)
        {
            string text = "";
            integer i = 0;

            this.filled = 0;

            if (fld > 0)
            {
                while(this.filled < fld)
                {
                    text = text + "|cff" + this.full + this.tile + "|r";
                    this.filled += 1;
                }
            }

            i = this.filled;
            if (i < this.maxTiles)
            {
                while(i < this.maxTiles)
                {
                    text = text + "|cff" + this.empty + this.tile + "|r";
                    i += 1;
                }
            }

            SetTextTagText(this.tt, text, this.size);
        }

        method getFilled() -> integer { return this.filled; }
        method getTiles() -> integer { return this.maxTiles; }
        method getTextTag() -> texttag { return this.tt; }
        method getTile() -> string { return this.tile; }

        method getPercent() -> real { return this.filled*100./this.maxTiles; }
        method setPercent(integer p)
        {
            if (p > 100) { p = 100; } else if (p < 0) { p = 0; }
            this.setFilled(p*this.maxTiles/100);
        }
        method setTile(string t)
        {
            if (t == "|") { t = "||"; }
            this.tile = t;
            this.setPercent(R2I(this.getPercent()));
        }
    }


    public struct AttachedText
    {
        private
        {
            texttag tt;
            unit u;
            real z;

            static thistype data[];
            static integer count = 0;
            static timer t = CreateTimer();
        }

        static method create(texttag which, unit to, real offset) -> thistype
        {
            thistype this = thistype.allocate();

            this.tt = which;
            this.u = to;
            this.z = offset;

            if (this.count == 0)
            {
                TimerStart(this.t, UPDATE_INTERVAL, true, static method thistype.update);
            }
            
            this.data[this.count] = this;
            this.count += 1;

            return this;
        }

        method setTextTag(texttag t) { this.tt = t; }
        method setTarget(unit to) { this.u = to; }
        method setOffset(real ofs) { this.z = ofs; }

        private static method update()
        {
            thistype this;
            integer i = 0;
            
            while(i < this.count)
            {
                this = this.data[i];

                SetTextTagPos(this.tt, GetUnitX(this.u), GetUnitY(this.u), this.z);
                
                i += 1;
            }
        }

        private method onDestroy()
        {
            this.data[this] = this.data[this.count-1];
            this.count -= 1;
            
            static if (AUTO_DESTROY) { DestroyTextTag(this.tt); }
        }
    }
}

//! endzinc
 
Last edited:
Level 8
Joined
Aug 6, 2008
Messages
451
I dont think this system useful at all.

Texttags work very differently from other handles, you can for example create them locally, and there is limit of 100 texttags per player, their handle indexes are pre allocated ( or something ).

Texttag recycling is not a good idea. It prevents you from creating local texttags, and doesnt really give you anything in return. ( But timers you should recycle instead of destroying them. Use TimerUtils for systems like these. )

We already have very good natives for handling texttags. How is some timer better than SetTextTagLifespan?

And I dont think that one function for creating texttags is good at all. Of course it is nice to have some own texttag creating functions for stuff like spell critical mesages and for that stuff units say in game, but Id rather use those natives for that kind of functions than some gigantic 12- arguments function.

In the other hand those attach texttag to unit thingies can be useful. You should also make some functions to change texttags size & color -overtime, instead of system like this.

This is really well coded and everything, but it just aint useful now. You should rather create some nice system for lightnings. They lack lifespan natives and need some over time color fading and locking to units in order to look good.
 
Level 11
Joined
Apr 29, 2007
Messages
826
I dont think this system useful at all.

Texttags work very differently from other handles, you can for example create them locally, and there is limit of 100 texttags per player, their handle indexes are pre allocated ( or something ).
And you can't create other handles locally?

Texttag recycling is not a good idea. It prevents you from creating local texttags, and doesnt really give you anything in return. ( But timers you should recycle instead of destroying them. Use TimerUtils for systems like these. )
Why would you need to create local texttags if you can use my function? It will also somewhat give you an unuqie, local texttag. And the created texttag get's returned?
JASS:
    local texttag tt = NewTextTag()
Will work pretty fine at all.

We already have very good natives for handling texttags. How is some timer better than SetTextTagLifespan?
The timer is only for recycling. The SetTextTagLifespan is still used.

And I dont think that one function for creating texttags is good at all. Of course it is nice to have some own texttag creating functions for stuff like spell critical mesages and for that stuff units say in game, but Id rather use those natives for that kind of functions than some gigantic 12- arguments function.
Well that's your oppinion and I accept that. But I, for one, like this 12+ argument function more than 12+ lines for every texttag I create.

In the other hand those attach texttag to unit thingies can be useful. You should also make some functions to change texttags size & color -overtime, instead of system like this.
Yeah that was the other purpose of the Utils. And the size and color over time is a good idea, I will implement it in the next version.

This is really well coded and everything, but it just aint useful now. You should rather create some nice system for lightnings. They lack lifespan natives and need some over time color fading and locking to units in order to look good.
It isn't useful in your opinion, in mine it is. I think everyone should ask himself if he prefers this over the natives. You mustn't use my system. You also mustn't use TimerUtils, even if they're good, right? :)

The lightnings might be a good idea, I'll maybe do it when I'm the mood for it after I finished this.

And thanks for your critism. That's what I wanted to see :)
 
Level 8
Joined
Aug 6, 2008
Messages
451
By "creating locally" I mean this for example:


JASS:
function luls takes nothing returns nothing
   local texttag t
   if GetLocalPlayer() == p then
      set t=CreateTextTag()
      // do something with t
   endif
endfunction

Your recycling, which btw serves no purpose, also fucks this up.

But I, for one, like this 12+ argument function more than 12+ lines for every texttag I create.

12 + lines for map specific texttag functions you will need anyways.
 
Level 11
Joined
Apr 29, 2007
Messages
826
By "creating locally" I mean this for example:


JASS:
function luls takes nothing returns nothing
   local texttag t
   if GetLocalPlayer() == p then
      set t=CreateTextTag()
      // do something with t
   endif
endfunction
So, a extra function to create a text tag for a specific player should solve this, shouldn't it?


12 + lines for map specific texttag functions you will need anyways.
Yeah, but only once. And if you use alot of texttags..
 
Level 11
Joined
Apr 29, 2007
Messages
826
It's just to ease the function of texttags. You don't have to setup all that shit alone each time. If you like 12 lines of configuration for each texttag you create more, than don't use my system.

But, the Attach texttag to unit function is still useful. If I'll add more stuff like that, it'll still be useful for people like you.
 
Level 8
Joined
Aug 6, 2008
Messages
451
Yea. Attaching texttags to units.

But what bugs me mostly is that recycling. Why you even want to recycle texttags? ( Recycling pwns for timers, groups and units, but texttags? )
And since it requires some timer to recycling even work properly, it just adds some useless extra lagg when you use many texttags.

Also a fucntion with 12 arguments is just something really terrible. I dont know if it worked better with some nice struct syntax, it just might.

But yea, attaching texttag to units is something that cant be done with these natives we have, so it is pretty useful. If you add those over time scaling and color change thingies, even better. Some nice texttag movement thingies would be cool too.
 
Level 11
Joined
Apr 29, 2007
Messages
826
Welll basically, you're right, but with the new GetTextTagSize, GetTextTagX, Y and Z I've added you have to recycle them, otherwise you'd just blow an hashtable up when you're using alot of texttags.

But what do you mean with
I dont know if it worked better with some nice struct syntax, it just might.
?

edit/
New version posted. With alot more functions like SetTextTagSizeOverTime, set TextTagColorOverTime, GetTextTagSize, X, Y and Z and SetTextTagTextNew, SetTextTagX, Y and Z. Aswell a function for gradient string making (ColorStringGradient).
 
Last edited:
Level 11
Joined
Apr 29, 2007
Messages
826
AHH, sorry, wrong map -_-
Ok yeah you're right, so I can switch from hashtables to arrays. Gonna implement that in the next version(which is already finished, I'll only have to switch to arrays now)

e/ new Version up. I'm still in need of hashtables when it comes to some specific things, but saving the data from the texttags now work with arrays. Also, MoveTextTagTimed and TextBars were added.
 
Last edited:
Level 11
Joined
Feb 22, 2006
Messages
752
Well, I tested it, and it works, at least the stuff I tested. So functionality here isn't really a problem. However, I think the API could use a cleanup (or maybe a major rework).

First of all, I dont want to type in 10 parameters every time I create a texttag. It's just annoying, and it's easy to forget what those parameters are, so I have to go back and look at the documentation every time. Instead of emulating BJs with their huge parameter lists, you could make a Template struct, for example:


JASS:
struct Template
    static constant integer DEFAULT_RED = 255
    static constant integer DEFAULT_GREEN = 255
    static constant integer DEFAULT_BLUE = 255
    static constant integer DEFAULT_ALPHA = 255
    static constant integer DEFAULT_SIZE = 12
    static constant boolean DEFAULT_VISIBILITY = true
    static constant boolean DEFAULT_PERMANENT = true
    static constant real DEFAULT_FADEPOINT = 0.0
    static constant real DEFAULT_LIFESPAN = 0.0
    static constant real DEFAULT_X_VELOCITY = 0.0
    static constant real DEFAULT_Y_VELOCITY = 0.0

    integer red
    integer green
    integer blue
    integer alpha
    integer size
    boolean visible
    boolean permanent
    real fadepoint
    real lifespan
    real xVelocity
    real yVelocity

    static method create takes nothing returns nothing
        local Template this = Template.allocate()
        set this.red = .DEFAULT_RED
        set this.green = .DEFAULT_GREEN
        set this.blue = .DEFAULT_BLUE
        set this.alpha = .DEFAULT_ALPHA
        set this.size = .DEFAULT_SIZE
        set this.visible = .DEFAULT_VISIBILITY
        set this.permanent = .DEFAULT_PERMANENT
        set this.fadepoint = .DEFAULT_FADEPOINT
        set this.lifespan = .DEFAULT_LIFESPAN
        set this.xVelocity = .DEFAULT_X_VELOCITY
        set this.yVelocity = .DEFAULT_Y_VELOCITY
    endmethod
endstruct

function NewTextTag takes real x, real y, real z, string text, Template template returns texttag
    local texttag tag = CreateTextTag()
    call SetTextTagPos(tag, x, y, z)
    call SetTextTagText(tag, text, template.size * 0.023 / 10)
    call SetTextTagColor(tag, template.red, template.green, template.blue, template.alpha)
    call SetTextTagVisibility(tag, template.visible)
    call SetTextTagPermanent(tag, template.permanent)
    call SetTextTagFadepoint(tag, template.fadepoint)
    call SetTextTagLifespan(tag, template.lifespan)
    call SetTextTagVelocity(tag, template.xVelocity, template.yVelocity)
    return tag
endfunction


Some other things:

Your textbar thing shouldn't really need to take in an existing texttag upon creation. The constructor should create its own texttag. Also, by convention the constructor of any struct is .create(), not .Create() (and more generally, all method identifiers should have their first word in all lowercase, and the rest with first letter capitalized: .create(), .destroy(), .doSomething()).

You should use size and not height when determining text size. It's just easier to work with. Size can be thought of as the same thing as font size in word processing programs, while height is just some random real that probably makes no sense to anybody except the people at blizzard who wrote the natives.

Your documentation should specify what it is that some of the parameters do. Some of them are obvious by their names; others are not. It's best to be clear.

You should rename all "transparency" parameters to "alpha", since the way you handle them you are treating them as alpha values (0=transparent, 255=opaque) and not transparency (0%=opaque, 100%=transparent).

I've probably forgotten some other things that I thought of while testing. But this is it for now.
 
Level 11
Joined
Apr 29, 2007
Messages
826
You should use size and not height when determining text size. It's just easier to work with. Size can be thought of as the same thing as font size in word processing programs, while height is just some random real that probably makes no sense to anybody except the people at blizzard who wrote the natives.

I don't see where I have done that?
Other than that, updated. Fixed alot of things you mentioned.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
JASS:
        static method create(texttag which, unit to, real offset) -> thistype
        {
            thistype this = thistype.allocate();

            this.tt = which;
            this.u = to;
            this.z = offset;

            this.t = NewTimer();
            SetTimerData(this.t, this);
            TimerStart(this.t, UPDATE_INTERVAL, true, static method thistype.update);

            return this;
        }

So every time anyone creates a text-tag it starts a new timer? Why not just have all of these in a stack and then run through the stack every interval (one timer).

JASS:
struct stackNode
    integer index

    static thistype array stack
    static integer n = 0

    method onDestroy takes nothing returns nothing
        set thistype.n=thistype.n-1
        set thistype.stack[this.index] = thistype.stack[thistype.n]
        set thistype.stack[this.index].index = this.index
    endmethod

    static method create takes ... returns thistype
        local thistype dat=thistype.allocate()
        
        set dat.index = thistype.n
        set thistype.stack[thistype.n] = dat
        set thistype.n=thistype.n+1
 
        return dat
    endmethod
endstruct

Its in vJass not Zinc, but you can probably understand it.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
But it doesn't slow anything down, so why would you deliberately use unnecessary resources? Especially in something which would be implementing into someone else' map, you would want it to be as perfected as it can be.
 
Top