• 🏆 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] Spell Channel Bar Not In Sync (Progress Bar System & Model)

Status
Not open for further replies.
Level 1
Joined
Feb 11, 2019
Messages
5
Hello all,
I am trying to utilize the below two resources in my map but i am unable to determine why the channel bar progress is not in sync with the timer. A demo map of the problem is attached.

Expected Result:
When casting Tranquility (100 sec duration) a channel bar appears above the druid's head that should fill up 1% every second. A periodic timer is going off every 1 sec to progress the channel bar by 1%.

Actual Result:
The channel bar is filled up to 100% before the end of the 100 second duration spell.


Resources being utilized:
Modified version of Progress Bars 2.1 by TriggerHappy: ProgressBars v2.0.1
Progress Bars v2.0.1 model: Progressbar V2


Thank you in advance for reviewing and attempting to help.
 

Attachments

  • ProgressBar_ChannelSpell_Problem.w3x
    33.8 KB · Views: 34
Level 1
Joined
Feb 11, 2019
Messages
5
Thank you for the suggestion Pyrogasm. I have included the modified Progress Bar library, the Tranquility spell code, and a video of the issue occurring in game. Thanks again to anyone willing to take the time to take a peak.

JASS:
//TESH.scrollpos=91
//TESH.alwaysfold=0
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     = 'e000' // 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
    method destroyEx takes nothing returns nothing
           
        call RemoveUnit(this.bar)
                   
            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
        call this.deallocate()
        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

JASS:
scope Tranq initializer Init

globals
    private boolean array TRANQ_IS_CHANNELING[11]
endglobals

private struct ChannelBar
    real castTime
    ProgressBar pbar
    unit caster
    static method create takes unit caster, real castTime returns ChannelBar
        local ChannelBar this = ChannelBar.allocate()
        set this.caster = caster
        set this.castTime = 0
        //set this.cbar = CastBar.create(caster, 1, 350, GetPlayerColor(Player(1)), 15 )
        set this.pbar = ProgressBar.createEx('e000')
        set this.pbar.xOffset    = -15
            set this.pbar.zOffset    = 250
              set this.pbar.size       = 1
                set this.pbar.color = GetPlayerColor(Player(1))
                set this.pbar.targetUnit = caster
        return this
    endmethod

    method destroy takes nothing returns nothing
        set caster = null
        call this.deallocate()
    endmethod
   
    static method Tick takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local ChannelBar c = GetTimerData(t)
       
        set c.castTime = c.castTime +1.00
        if TRANQ_IS_CHANNELING[GetPlayerId(GetOwningPlayer(c.caster))] then
            call BJDebugMsg(R2S(c.castTime))
            call c.pbar.setPercentage(c.castTime, .5)
        else
            call c.pbar.destroyEx()
            call ReleaseTimer(t)
        endif
        set t = null
    endmethod
endstruct

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A000'
endfunction
private function EndChannel takes nothing returns nothing
    set TRANQ_IS_CHANNELING[GetPlayerId(GetOwningPlayer(GetTriggerUnit()))] = false 
    call DestroyTrigger(GetTriggeringTrigger())
endfunction

private function Actions takes nothing returns nothing
    local unit caster = GetTriggerUnit()
    local trigger t = CreateTrigger()
    local timer clock = NewTimer() 
    local ChannelBar c = ChannelBar.create(caster, 30)
    set TRANQ_IS_CHANNELING[GetPlayerId(GetOwningPlayer(caster))] = true
    call TriggerRegisterUnitEvent( t, caster, EVENT_UNIT_SPELL_ENDCAST )
    call TriggerAddAction( t, function EndChannel )
    call SetTimerData(clock, c)
    call TimerStart(clock, 1.00, true, function ChannelBar.Tick)
    set t = null
    set clock = null
    set caster = null
endfunction

private function Init takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_CHANNEL )
    call TriggerAddCondition( t, Condition( function Conditions ) )
    call TriggerAddAction( t, function Actions )
    set t = null
endfunction
endscope

 
Level 39
Joined
Feb 27, 2007
Messages
5,023
I'm honestly a little baffled here too. Comments about dynamically created triggers aside, I distilled your test map down to as simple a bit of code as I could come up with and I'm still seeing the progress bars complete before they should. I think we need @TriggerHappy here to come in and explain the "speed" argument of the setPercentage method. The relevant code from ProgressBars and my test library are below:
JASS:
        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
//...
        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
JASS:
library test initializer init requires ProgressBars
globals
    private ProgressBar PB
endglobals

private function end takes nothing returns nothing
    call BJDebugMsg("time!")
endfunction

private function init takes nothing returns nothing
    local real dur = 10.00

    set PB = ProgressBar.createEx('e000')
    set PB.zOffset = 250
    set PB.color = GetPlayerColor(Player(1))

    call PB.setPercentage(100, 1.00/dur)
    call TimerStart(CreateTimer(), dur, false, function end)
endfunction
endlibrary
My understanding is that 'speed' is supposed to be how much the bar moves per bar update tick (which is 0.01 by default in the library). Thus with speed=1 the bar takes 1.0 seconds to go from 0% to 100%. If that's true then to get a bar to take N seconds to complete we should use setPercentage(100, 1.0/N). In the case of your 100-second bar speed=0.01, and it's possible that for speed << 1 the animation just doesn't play at the correct rate causing the error. However even with speed=0.1 as in my test library it completes in perhaps 8.5 seconds instead of 10 so idk.

Maybe the model has an error and the animation isn't actually exactly 1.00 seconds?

As a workaround instead of trying to only set the % once and let it go on its own, you can manually set the bar's progress every 1 second to the current % using speed=1, which should work just fine.
 
Level 1
Joined
Feb 11, 2019
Messages
5
Thank you all for your analysis and suggestions. I think I'm going to pin this issue on the model file as I've tried a few other solutions on my end for setting the animation speed. It appears the longer the 'channeling' time the 'earlier' the cast bar completes itself before expected. I'm not sure if there's issues/limits in what you can do with animation speed within wc3.

My next approach will be trying to learn how to edit the animation of this model file and then create multiple variations of the model for each cast time I require (of course I have no previous modeling experience kek.) Perhaps the Progress Bar system will work better with a more accurate model that has a base of 10 seconds as opposed to 1. I will update this thread later if I manage to get animation editing down and find Progress Bars works (or a similar method) in this instance with a 10 second animation model.
 
Level 1
Joined
Feb 11, 2019
Messages
5
Update already thanks to Matrix Eater which is an amazing tool.
It was a joke increasing the animation time of this model by opening the model in Matrix Eater->Tools->Edit Animations->Set the 'birth' animation speed to whatever you want it to be->Save.

Rinse and repeat for each cast time you need and now you can make very efficient cast bars with little coding necessary!
 
Status
Not open for further replies.
Top