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

ProgressBars v2.0.1

Now supports multiple progress bar models (via dummies).

JASS:
library ProgressBars requires TimerUtils optional BoundSentinel
/**************************************************************
*
*   ProgressBars v2.0.1 by TriggerHappy
*
*   This library allows you to easily create and modify progress bars.
*   It works by creating a dummy unit with a special model and changing
*   the animation speed to increase or reduce the bar speed. It is more than
*   just a wrapper as it recycles each progress bar, meaning it will avoid
*   costly CreateUnit calls whenever possible which also leak.
*
*   Options:
*       x            - set X coordinate
*       y            - set Y coordinate
*       xOffset      - offset of the target unit, if any.
*       yOffset      - offset of the target unit, if any.
*       zOffset      - how high the bar is from the ground.
*       color        - allows you to tint the bar or add transparency
*       targetUnit   - pick which unit the bar should hover over
*       size         - set model scale
*
*   Usage:
*       local ProgressBar bar = ProgressBar.create()
*       set bar.zOffset       = 150
*       set bar.color         = PLAYER_COLOR_RED
*       set bar.targetUnit    = CreateUnit(Player(0), 'hfoo', 0, 0, 0)
*       call bar.setPercentage(30)
*
*   Installation:
*       1. Copy the dummy unit over to your map
*       2. Change the DUMMY constant to fit the Raw code of the dummy.
*       3. Copy this and all required libraries over to your map.
*
*   Thanks to JesusHipster for the Progress Bar models
*   and to Vexorian for TimerUtils & BoundSentinel
*
**************************************************************/

    globals
        private constant integer PROGRESS_BAR_DUMMY     = 'pbar' // the default one
        private constant player  PROGRESS_BAR_OWNER     = Player(PLAYER_NEUTRAL_PASSIVE) // owner of the dummy
        private constant real    UPDATE_POSITION_PERIOD = 0.03 // the timer period used with .targetUnit
    endglobals
    
    struct ProgressBar
    
        unit bar
        unit target
        
        real xOffset = 0
        real yOffset = 0
        
        timer timer
        timer timer2
        
        private boolean t_enabled = false
        private real endVal
        private real curVal=0
        private real pspeed=0
        private boolean reverse
        private boolean done
        private boolean recycle
        
        readonly static unit array dummy
        readonly static integer lastDummyIndex = -1

        method operator x= takes real x returns nothing
            call SetUnitX(this.bar, x)
        endmethod
        
        method operator x takes nothing returns real
            return GetUnitX(this.bar)
        endmethod
        
        method operator y= takes real y returns nothing
            call SetUnitY(this.bar, y)
        endmethod
        
        method operator y takes nothing returns real
            return GetUnitY(this.bar)
        endmethod
        
        method operator zOffset= takes real offset returns nothing
            call SetUnitFlyHeight(this.bar, offset, 0)
        endmethod
        
        method operator zOffset takes nothing returns real
            return GetUnitFlyHeight(this.bar)
        endmethod
        
        method operator size= takes real size returns nothing
            call SetUnitScale(this.bar, size, size, size)
        endmethod
        
        method operator color= takes playercolor color returns nothing
            call SetUnitColor(this.bar, color)
        endmethod
        
        method show takes boolean flag returns nothing
            call UnitRemoveAbility(this.bar, 'Aloc')
            call ShowUnit(this.bar, flag)
            call UnitAddAbility(this.bar, 'Aloc')
        endmethod
        
        method reset takes nothing returns nothing
            call SetUnitAnimationByIndex(this.bar, 1)
        endmethod

        method RGB takes integer red, integer green, integer blue, integer alpha returns nothing
            call SetUnitVertexColor(this.bar, red, green, blue, alpha)
        endmethod
        
        method destroy takes nothing returns nothing
            if (recycle) then
                set lastDummyIndex = lastDummyIndex + 1
                set dummy[lastDummyIndex] = this.bar
                call SetUnitAnimationByIndex(this.bar, 0)
                call SetUnitTimeScale(this.bar, 1)
            endif
            
            set this.bar        = null
            set this.target     = null
            set this.t_enabled  = false
            set this.endVal     = 0
            set this.curVal     = 0
            
            if (this.timer != null) then
                call ReleaseTimer(this.timer)
                set this.timer = null
            endif
            
            if (this.timer2 != null) then
                call ReleaseTimer(this.timer2)
                set this.timer2 = null
            endif
        endmethod
        
        private static method updatePercentage takes nothing returns nothing
            local timer expired = GetExpiredTimer()
            local thistype this = GetTimerData(expired)
            
            if (this.reverse) then
            
                if (this.curVal > this.endVal) then
                    call SetUnitTimeScale(this.bar, -this.pspeed)
                    set this.curVal = (this.curVal - (this.pspeed))
                elseif (this.curVal <= this.endVal) then
                    call PauseTimer(this.timer2)
                    call SetUnitTimeScale(this.bar, 0)
                    set this.curVal = this.endVal
                    set this.done   = true
                endif
                
            else
            
                if (this.curVal < this.endVal) then
                    call SetUnitTimeScale(this.bar, this.pspeed)
                    set this.curVal = (this.curVal + (this.pspeed))
                elseif (this.curVal >= this.endVal) then
                    call PauseTimer(this.timer2)
                    call SetUnitTimeScale(this.bar, 0)
                    set this.curVal = this.endVal
                    set this.done   = true
                    
                endif
                
            endif
            
        endmethod
        
        private static method updatePosition takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            if (this.target != null) then
                call SetUnitX(this.bar, GetUnitX(this.target) + xOffset)
                call SetUnitY(this.bar, GetUnitY(this.target) + yOffset)
            else
                call ReleaseTimer(GetExpiredTimer())
            endif
        endmethod
        
        private static method getDummy takes nothing returns unit
            if (lastDummyIndex <= -1) then
                set bj_lastCreatedUnit = CreateUnit(PROGRESS_BAR_OWNER, PROGRESS_BAR_DUMMY, 0, 0, 270)
                call PauseUnit(bj_lastCreatedUnit, true)
                return bj_lastCreatedUnit
            endif
            call SetUnitAnimationByIndex(dummy[lastDummyIndex], 1)
            set lastDummyIndex = lastDummyIndex - 1
            return dummy[lastDummyIndex + 1]
        endmethod
        
        static method release takes integer count returns nothing
            if (count > thistype.lastDummyIndex) then
                set count = thistype.lastDummyIndex
            endif
                
            loop
                exitwhen count <= 0
                call RemoveUnit(dummy[count])
                set dummy[count] = null
                set count = count - 1
            endloop
                
            set thistype.lastDummyIndex = -1
        endmethod
        
        static method create takes nothing returns thistype
            local thistype this = thistype.allocate()
            
            set this.bar        = thistype.getDummy()
            set this.done       = true
            set this.recycle    = true
            
            call SetUnitAnimationByIndex(this.bar, 1)
            call SetUnitTimeScale(this.bar, 0)
            
            return this
        endmethod
        
        static method createEx takes integer unitId returns thistype
            local thistype this = thistype.allocate()
            
            set this.bar        = CreateUnit(PROGRESS_BAR_OWNER, unitId, 0, 0, 0)
            set this.done       = true
            set this.recycle    = false
            
            call SetUnitAnimationByIndex(this.bar, 1)
            call SetUnitTimeScale(this.bar, 0)
            
            return this
        endmethod
        
        method setPercentage takes real percent, real speed returns nothing
            set this.endVal = R2I(percent)
            set this.pspeed = speed
            
            set this.reverse = (curVal > endVal)
                
            if (this.done) then
                
                if (this.timer2 == null) then
                    set this.timer2 = NewTimerEx(this)
                endif
            
                call TimerStart(this.timer2, 0.01, true, function thistype.updatePercentage)
                set this.done=false
            endif
        endmethod
        
        method operator targetUnit= takes unit u returns nothing
            set this.target = u
            
            if (u != null) then
                if (this.timer == null) then
                    set this.timer = NewTimerEx(this)
                endif
                call TimerStart(this.timer, UPDATE_POSITION_PERIOD, true, function thistype.updatePosition)
                call SetUnitX(this.bar, GetUnitX(this.target) - xOffset)
                call SetUnitY(this.bar, GetUnitY(this.target) - yOffset)
                set this.t_enabled = true
            else
                if (this.timer != null) then
                    call ReleaseTimer(this.timer)
                endif
                set this.t_enabled = false
            endif
        endmethod
        
    endstruct
    
endlibrary

Changelog

Code:
v2.0.1
- Added support for different types of progress bars (without recycling) via .createEx(integer unitId).
- Properly nulled the timer handles.
- Paused progress bar dummies to reduce CPU usage.

v2.0.0
- Default health bar hidden in demo.
- ProgressBars will now no longer fill up out of nowhere.
- Fixed a possible, rare leak associated with not nulling a timer.

v2.0.0 Beta
- beginProgression and everything associated with it has been removed. Use .setPercentage instead.
- zOffset getter included.
- xOffset and yOffset added.
- show method now adds and removes locust to prevent it removing.

v1.0.2
- Minor efficiency improvements
- freeDummies member renamed to lastDummyIndex

v1.0.1
- Fixed a bug where the bar would fade out really slow on long durations.
- Removed unused parameters from the getDummy method.
- Removed UnitAddAbility and gave the dummy the ability directly.
- targetUnit operator now sets the position of the bar directly (fixes cosmetic bug)
- Changed onDestroy to destroy.

Thanks to JesusHipster for the Progress Bar V2 model.


Keywords:
progress,bar,xp,exp,mana,hp,health,loot,mui,hp,mp,cast,bars
Contents

ProgressBars v2.0.1 (Map)

Reviews
18th Apr 2016 Your resource has been reviewed by BPower. In case of any questions or for reconfirming the moderator's rating, please make use of the Quick Reply function of this thread. Review: A wonderful system and definitly worth a 5/5...

Moderator

M

Moderator

18th Apr 2016

General Info

Your resource has been reviewed by BPower.
In case of any questions or for reconfirming the moderator's rating,
please make use of the Quick Reply function of this thread.
Review:

A wonderful system and definitly worth a 5/5 rating.
Recycling bar dummies is a very welcome bonus.

Why is BoundSentinel in the demo map?

Troubleshooting:

  • The hardcoded timer timeout in method setPercentage could be replaced by a descriptive constant.
    ---
  • Each time you release a timer you should null that timer handle, because of code blocks like:
    JASS:
    if (this.timer == null) then
        set this.timer = NewTimerEx(this)
    endif
    It implies that the released timer is still reserved for your system, which is not true.
    TimerUtils probably gave the timer out for another code running.
    It's very likely because the timer stack always returns the last recycled timer.
    ---
  • if (this.target != null) then could preferably be if GetUnitTypeId(target) != 0 then

Review changelog:
  1. -
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
The progress bar model is just really nice :).

The dummy needs locust.

version 0.01 ?? rather 1.0.0

Edit:
timer timer Why ....

private static method getDummy takes real x, real y returns unit You don't use x and y at all.

I thought method onDestroy should be avoided. Can't argue about this, its just something I read, but I think you just should write method destroy on your own.

Also in onDestroy you do call SetUnitAnimationByIndex(this.bar, 0) which looks ugly in your demomap, in case you cancel the spell.
I would recommend using call SetUnitPosition(this.bar, 2147483647, 2147483647) before. ( or any really high x/y value)

You could use 0.031250000 instead of 0.03, it would be more precise.

I would create new dummies at the edge of the map, just use SetUnitPosition and a hillarious high x and y value, because if it is on 0,0 it will be displayed there or at its current position for a millisecond before it gets moved above the targetunit. (You can experience it in your demo map)
When recycling the dummy place him back to the edge of the map.

You could mention your demo spell leaks and should not be copied. ^^

If it gets approved, I'll use it as optional library for my TownPortalSystem.
 
Last edited:
Level 29
Joined
Oct 24, 2012
Messages
6,543
version 0.01 ?? rather 1.0.0

Edit:
timer timer Why ....

I thought method onDestroy should be avoided. Can't argue about this, its just something I read, but I think you just should write method destroy on your own.

Also in onDestroy you do call SetUnitAnimationByIndex(this.bar, 0) which looks ugly in your demomap, in case you cancel the spell.
I would recommend using call SetUnitPosition(this.bar, 2147483647, 2147483647) before. ( or any really high x/y value)

You could use 0.031250000 instead of 0.03, it would be more precise.

I would create new dummies at the edge of the map, just use SetUnitPosition and a hillarious high x and y value, because if it is on 0,0 it will be displayed there or at its current position for a millisecond before it gets moved above the targetunit. (You can experience it in your demo map)
When recycling the dummy place him back to the edge of the map.
Going off of the above post.

This should not have been submitted unless it is verion 1.0.0 or higher.

timer timer should be avoided.

onDestroy should never be used create your own destroy method.
Look at Magtheridon's Struct for Dummies tutorial i believe the exact reason is in there. Also don't use SetUnitPosition. It is slower and less efficient than the SetUnitX/Y counterparts.

0.031250000 should be used. It is used by almost all jassers / vJassers

SetUnitX/Y with a hide unit and move then show unit will fix this problem.
 
timer timerWhy ....

Becausethis.timerlooks nice (in TESH), leave me alone.

private static method getDummy takes real x, real y returns unit You don't use x and y at all.

Thanks for pointing that out, I left that there on accident.

I thought method onDestroy should be avoided. Can't argue about this, its just something I read, but I think you just should write method destroy on your own.

onDestroy is fine.

You could use 0.031250000 instead of 0.03, it would be more precise.

I personally use 0.01, I only keep it at 0.03 to get approved here.

Also in onDestroy you do call SetUnitAnimationByIndex(this.bar, 0) which looks ugly in your demomap, in case you cancel the spell.

What's wrong with the death animation? How does that look ugly. The only reason it looks ugly (has nothing to do with the line you pointed out) when you cancel the spell is because it doesn't follow the unit, but I'll work on that.

deathismyfriend said:
This should not have been submitted unless it is verion 1.0.0 or higher.

Jesus christ, the version number is really just cosmetic. The system works fine you're really saying I shouldn't submit this because I titled it 0.01? Get it together lol.

Anyway changed version number to 1.00 (will update doc and map in next release) and uploaded a video.

SetUnitX/Y is no option, because you don't know how large the map is. You could easily move the unit outside mapbounds and crash the game. That's why you have to use SetUnitPosition.

I refuse to use SetUnitPosition, or even path check for that matter (in this lightweight system). I will consider bound sentinel as an optional dependency.
 
Last edited:
Level 29
Joined
Oct 24, 2012
Messages
6,543
Jesus christ, the version number is really just cosmetic. The system works fine you're really saying I shouldn't submit this because I titled it 0.01? Get it together lol.

Anyway changed version number to 1.00 (will update doc and map in next release) and uploaded a video.

I refuse to use SetUnitPosition, or even path check for that matter (in this lightweight system). I will consider bound sentinel as an optional dependency.

It is in the rules for the spell submission section. Do not submit anything under version 1.0

Use the world bounds library to detect when unit is leaving map area.
 
First off: I like the idea of progress bars like this in game and like that there is finally a library for it.

My questions/ideas:
Why did you add + 0.1 to the time? Isn't 0.01 enough?
You should change the name of the timer to clock or something more appropriate.
You can add optional ARGB support. (Vexorians Version)
Why is t_enabled on default visibility? Shouldn't it be private?
Last but not least, the demo really sucks. Make it a spell with start delay, one over time (rain of fire / blizzard) and maybe some hp or mana bar for another unit.

Edit: The show method is funny :D

You can change this
JASS:
        method show takes boolean flag returns nothing
            if (flag == false) then
                set this.hidden = true
                call ShowUnit(this.bar, false)
            elseif (hidden) then
                set this.hidden = false
                call ShowUnit(this.bar, true)
            endif
        endmethod

to this

JASS:
        method show takes boolean flag returns nothing
            set this.hidden = not flag
            call ShowUnit(this.bar, flag)
        endmethod

It's okay if it's already shown, it won't flicker or anything.
 
deathismyfriend said:
Use the world bounds library to detect when unit is leaving map area.

Why would I constantly check for safe coordinates (every 0.03 seconds) when I could just use bound sentinel?

Anachron said:
Why is t_enabled on default visibility? Shouldn't it be private?

Nice catch, thanks.

Anachron said:
The show method is funny :D

That's what coding at 2am gets you;)

Thanks.

I'll be updating in a little, right now the progression formula is a bit wrong.

Updated, however there is still an issue with the AnimationSpeed formula in that over long a duration it becomes slightly more inaccurate.

Also please nobody complain about the bar not being destroyed when the demo spell is interrupted, I'll have the example working in the next release.
 
Last edited:
if (this.t_enabled == false) then
->
if not this.t_enabled then

Anyway that looks good I'll review better tomorrow if I got some time :)
+rep anyway !

EDIT : Need to spread :/

The not operator is slower than ==.

I would make these private:

JASS:
        boolean t_enabled = false
       
        real finishTime = -1
        real ticks = 0

And make a method operator (Getter) as "elapsed / remaining".

True
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
The show method can potentionally critically harm the system an make it malfunction. Hiding and then showing a unit removes some of the effects of Locust, for example it will be picked with GroupEnumUnitsInRange and it will make the unit drag selectable. The unit will show the selection circle when the drag box is over the unit.

A working solution is to remove/add Locust.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Edit: The required fix is mentioned in Maker's post above, not this post :).

JASS:
            if (this.timer != null) then
                if (this.t_enabled) then
                    call PauseTimer(this.timer)
                endif
                call ReleaseTimer(this.timer)
            endif
ReleaseTimer will either pause or destroy the timer. You don't have to do it on your own.
Furthermore it will check if you pass in a null timer and throw a debug warning.

Edit: The only reason I could see, is that first pausing before destroying a timer reduces the chance of creating a bug. (If such a bug does exist at all)
 
Last edited:
Level 37
Joined
Mar 6, 2006
Messages
9,240
The show method can potentionally critically harm the system an make it malfunction. Hiding and then showing a unit removes some of the effects of Locust, for example it will be picked with GroupEnumUnitsInRange and it will make the unit drag selectable. The unit will show the selection circle when the drag box is over the unit.

A working solution is to remove/add Locust.

Could you apply a fix to this? Let's get the resource approved.
 
Level 5
Joined
Dec 21, 2012
Messages
89
I like your system but it doesn't seems working for summoning spells (casting time before something is summoned). When I use the spell progress bar is showing after the spell is finished but for spell like rain of fire is good
 
I've updated to 2.0.0.

It's in beta because the code can probably be simplified and I was having some issues with my HP/MP bars, however I don't think it was an issue directly with the script.

Basically you can set the progress bars % now via.setPercentage

Code:
v2.0.0
- beginProgression and everything associated with it has been removed. Use .setPercentage instead.
- zOffset getter included.
- xOffset and yOffset added.
- show method now adds and removes locust.
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
Yea the hp bar regenerates itself :p But I think I found another bug if the unit dies..
No no, the demo doesn't check for the units health condition. Everything works very well.

@TriggerHappy: Once you release a timer null the timer reference, otherwise you might re-start a timer which is currently running for a different library.
The timer will be pushed back into the TimerUtils timer stack and may be used everywhere.


That timer could also be destroyed (does this really happen? Ever?), as TimerUtils destroys timers once the max array size is reached.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Yesterday I've read the introduction in Missle Recycler from noones favorite Avenger Captain Bri.. America and apperently "paused units consume very few CPU resources".
You should consider adding PauseUnit(...) into getDummy. I think the best way is to set the created unit to bj_lastCreatedUnit and return bj_lastCreatedUnit to prevent a possible unit handle leak.
Users still can mess around with any visuals althought the unit is paused.


This makes no sense it should be the other way around. I just read this is the description and didn't download the map. Is the code in there outdated?
JASS:
            set this.timer  = null
            set this.timer2 = null
           
            if (this.timer != null) then
                call ReleaseTimer(this.timer)
            endif
           
            if (this.timer2 != null) then
                call ReleaseTimer(this.timer2)
            endif

Edit: The code inside the map file doesn't match with the desciption, both need an update.

Captain Bribe would be a big win for the Avengers
 
Last edited:
Level 3
Joined
Dec 8, 2013
Messages
51
Very cool system,but why not make the bar is automatically created for units,which will appear on the map only with time?I don't know vJass,and now faces that can't implement it myself.
And..bar does not want to disappear after the death of unit
 
How about RPG maps or times where there are over 30 units . It would cause lag for sure. Please make it optional for example only heros.

I just said two posts ago it should support hundreds of bars.

The next version will likely just be an optimization improvement which should allow even more bars than the current version.
 
Level 3
Joined
Jun 13, 2010
Messages
43
Nevertheless, Great system. But its need more samples documentation and option
For example : height of the dummy model as some models may be different in scale.
, specifically units option documentation or maybe in GUI custom script
 
Nevertheless, Great system. But its need more samples documentation and option
For example : height of the dummy model as some models may be different in scale.
, specifically units option documentation or maybe in GUI custom script

Yeah the documentation clearly includes all of that, except for GUI support which would be ridiculous to include in the doc.

JASS:
*   Options:
*       x            - set X coordinate
*       y            - set Y coordinate
*       xOffset      - offset of the target unit, if any.
*       yOffset      - offset of the target unit, if any.
*       zOffset      - how high the bar is from the ground.
*       color        - allows you to tint the bar or add transparency
*       targetUnit   - pick which unit the bar should hover over
*       size         - set model scale
or more specifically, you were complaining about not being able to set the height of the model

JASS:
local ProgressBar bar = ProgressBar.create()
set bar.zOffset = 100
I do agree that more samples could be included, but this is very straight forward to use and the API is simplistic.

More than enough information is embedded into the current examples.

Please stop putting negativity out there about my system when it's not even true.
 
Top