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

TimedUnitScale v2.0

Info

Allows you smoothly to change a unit's scale over time.

Code
JASS:
library TimedUnitScale /* v2.0

    Information
    ¯¯¯¯¯¯¯¯¯¯¯

        Allows you smoothly to change a unit's scale over time.

        It is required you to read the GetUnitScale library,
        if you modified default scale values in object editor.
       
    Credits:
    ¯¯¯¯¯¯¯¯
       
        Resources:
       
            - TriggerHappy
            - Nestharus


 */ requires /*

        */ GetUnitScale   /* hiveworkshop.com/forums/submissions-414/snippet-getunitscale-262274/
        */ UnitDex        /* hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
        */ List           /* attached in map and in thread, because nestharus removed it I can't find original destinaion anymore

       
       
    struct UnitScale
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
   
   
        static method operator enableAll= takes boolean flag returns nothing
            - when set to "false" it will pause all scale changes for all units
            - when set to "true" it will continue again
   
        static method operator [] takes unit who returns thistype
            - That's the only interface for the user to get an instance.
            - Use this to call methods or to set/read other members:
           
           
            method apply takes real newScale, real duration returns UnitScaleData
                - use this to apply a new scale change
                - you also can use GetUnitScale function to mimic add & substract behaviour.
           
            method stopScale takes UnitScaleData sd returns nothing
                - you can use the return value from the "apply" method to manualy destroy a scale instance.
                - do this at your own risk
               
            method destroy
                - "destroy" will destroy all scale instances.
               
               
            boolean enabled
                - set this to "false" if you want to disable, but not destroy a unit's scale changes
                - when set to "true" it will continue again
               
            readonly boolean exists
                returns the flag if there currently exists any scale instance
               
            readonly integer count
                returns amount of current scale instances for our unit
               
               
        Example:
            local UnitScale myInstance = UnitScale[GetTriggerUnit()]
                - we get the instance for our unit.
           
            local UnitScaleData data = myInstance.apply(5, 10)
                - this will call the apply method for myInstance.
                - it will try to set it's scale to "5" over "10" seconds.
               
            call instance.stopScale(data)
                - will destroy the applied scale instance
           
           
        Pay attention for the example. When the user will start multiple scale instances,
        it oboviously is not ensured that the unit will have exactly the scale of "5" after "10" seconds.
        If you want ensure accurate values you can check for already running instances, or use the .destroy() method before apply().
       
   
    struct UnitScaleData
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
   
        That's the struct returned by the apply method above.
        You have further access from it:
       
            method operator time takes nothing returns real
                - returns the remaining time of the instance
               
            method operator time= takes real r returns nothing
                - define a new remaining time
                - only positives will be allowed
               
            method operator scalePerSecond takes nothing returns real
                - return the current scale rate perSecond
           
            method operator scalePerSecond= takes real r returns nothing
                - define a new sclare rate perSecond.
                - only positives will be allowed.
       
       
**************************************************************************/

    globals
        private constant timer CLOCK    = CreateTimer()
        private constant real  TIMEOUT  = .031250000
    endglobals

    struct UnitScaleData extends array
        implement List
        real duration     // Remaining duration for scaling
        real amount       // scale-amount per interval
       
        method operator time takes nothing returns real
            return .duration
        endmethod
        method operator time= takes real r returns nothing
            set .duration = RAbsBJ(r)
        endmethod
       
        method operator scalePerSecond takes nothing returns real
            return .amount/TIMEOUT
        endmethod
        method operator scalePerSecond= takes real r returns nothing
            set .amount = RAbsBJ(r)*TIMEOUT
        endmethod
    endstruct
   
    struct UnitScale extends array
        private static boolean systemEnabled   = false
       
        boolean enabled
        readonly boolean exists     // Does a UnitScale currently exist?
        readonly integer count      // How often a unit is currently regstered.
       
        private unit u
        private UnitScaleData head
        private thistype prev
        private thistype next
        private static thistype first = 0
       
        static method operator [] takes unit who returns thistype
            return GetUnitId(who)
        endmethod
       
        private method destroy takes nothing returns nothing
       
            if not .exists then
                return
            endif
           
            call .head.destroy()
            set .count = 0
           
            if (this == thistype.first) then
                set thistype.first = this.next
            endif
            set this.next.prev = this.prev
            set this.prev.next = this.next
               
            if (thistype.first == 0) then
                call PauseTimer(CLOCK)
                set thistype.systemEnabled = false
            endif
            set .exists = false
            set .u = null
        endmethod
       
        method stopScale takes UnitScaleData sd returns nothing
            set .count = .count - 1
            if .count == 0 then
                call.destroy()
            else
                call sd.remove()
            endif
        endmethod
   
        private static method callback takes nothing returns nothing
            local thistype this = thistype.first
            local UnitScaleData element
            local UnitScaleData temp
            loop
                exitwhen (this == 0)
               
                if (GetUnitTypeId(.u) == 0) then
                    call .destroy()
               
                elseif (.enabled) then
                   
                    set element = .head.first
                    loop
                        exitwhen element == 0
                        set temp = element.next
                       
                        call SetUnitScale(.u, (GetUnitScale(.u) + element.amount), 1, 1)
                        if (element.duration <= TIMEOUT) then
                            call .stopScale(element)
                        else
                            set element.duration = element.duration - TIMEOUT
                        endif
                       
                        set element = temp
                    endloop
                   
                endif
                set this = this.next
            endloop
        endmethod
       
        method apply takes real newScale, real duration returns UnitScaleData
            local unit u = GetUnitById(this)
            local UnitScaleData element
           
            if (newScale == GetUnitScale(u)) then
                return 0
            endif
           
            if (duration < 0) then
                debug call BJDebugMsg("UnitScale: Durations smaller or equal 0 will lead to instant scaling.")
                set duration = 0
            endif
           
            if (duration == 0) then
                call SetUnitScale(u, newScale, 1, 1)
                return 0
            endif
           
            set this = GetUnitId(u)
            if .count == 0 then
                set .head = UnitScaleData.create()
                set .enabled = true
                set .exists = true
                set .u = u
               
                if (thistype.first == 0) then
                    call TimerStart(CLOCK, TIMEOUT, true, function thistype.callback)
                    set thistype.systemEnabled = true
                endif
                set this.next = thistype.first
                set thistype.first.prev = this
                set thistype.first = this
                set this.prev = 0
            endif
           
            set element = .head.enqueue()
            set element.duration = duration
            set element.amount   = (newScale - GetUnitScale(u)) / (duration/TIMEOUT)
            set .count = .count + 1
           
            return element
        endmethod
       
        static method operator enableAll= takes boolean flag returns nothing
            if flag and not thistype.systemEnabled and thistype.first != 0 then
                call TimerStart(CLOCK, TIMEOUT, true, function thistype.callback)
                set thistype.systemEnabled = flag
            elseif not flag and not thistype.systemEnabled then
                call PauseTimer(CLOCK)
                set thistype.systemEnabled = flag
            endif
        endmethod
    endstruct
endlibrary

Changelog

v2.0
- Internaly the structure is a bit cleaner and uses an other struct.
- Reworked API syntax and added a bit more powers for user.​
v.1.6a
- Removed not directly needed requierements.
- Moved timer callback, to prevent trigger evaluation.​
v.1.6
- Updated GetUnitScale in demo.​
v.1.5.0a
- Removed stop-scale-onDeath config.​
v.1.5.0
- Seperated GetUnitScale from system.
- Only allocate if all conditions are true.​
v.1.4.2
- Renamed two API functions. Again :eek:...​
v1.4.1
- Forgot to rename 2 API functions. :D
v1.4.0
- Structure reduced. Double scaling is not possible anymore. Only single scale -> straight forward.​
v1.3.2
- Static if added for onDeath.​
v1.3.1
- Optimized method onDeindex.​
v1.3.0
- Cancel scaling onDeindex.
- Canceling scaling onDeath now possible.​
v1.2.1
- Note was outdated. Changed.
- API functions moved below struct.​
v1.2.0
- Now also 0 and negative values are allowed as parameter, and will be automatically be fixed, if needed.
- Fixed a bug with scale accuracy.​
v1.1.0
- function GetUnitDefaultScale added.
- function StopGrows added.
- function ResetScale added.
- Division with 0 bug fixed.
- Tiny code optimizations.​
v1.0
- release​

Keywords:
Timed, Unit, Scale, System, Grow, Shrink, Effect, IcemanBo, null
Contents

TimedUnitScale (Map)

Reviews
11:23, 22th May 2015 BPower: Looks good to me. Approved. Please remove not directly needed requirements. 22:22, 8th Jan 2015 Orcnet: Set to Need Fix, for now

Moderator

M

Moderator

11:23, 22th May 2015
BPower: Looks good to me. Approved.
Please remove not directly needed requirements.

22:22, 8th Jan 2015
Orcnet: Set to Need Fix, for now
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449

  • First, I prefer apply and remove for the function names
  • Thread crash could occurs on create method if growUpDuration or growDownDuration is zero (!0 devided by 0 => thread crash). The solution is to skip some/whole actions: if growUpDuration is zero, then the unit scaled up/down instantly, if growDownDuration is zero, then the unit will be reset instantly either, if both of them is zero then don't start the timer at all. I believe you can solve this by yourself actually ;) But the point is 0 means instant
  • JASS:
                if eDuration < 0 then 
                    set growBack = false
                else
                    set growBack = true
                endif
    Usually, 0 is also included as permanent so it should be eDuration <= 0.
  • JASS:
                        if not pauseGrow then
                            set tmpScale = unitScale[id] + growRate
                            call SetUnitScale(grower, tmpScale, 1, 1)
                            set unitScale[id] = tmpScale
    I think tmpScale is unneeded (you are not using it enywhere else)
    JASS:
                        if not pauseGrow then
                            set unitScale[id] = unitScale[id] + growRate
                            call SetUnitScale(grower, unitScale[id], 1, 1)
  • JASS:
                set updatesUp = R2I((scaleGoal - scaleStart) /growRate)
                set updatesDown = -1   // Just a negative value. Mustn't be 0 or bigger at initialization.
    It's been quite hard to understand what those two variables do. But I have came to a conclusion that one boolean variable, named state or something, could covers both of their jobs.

    For example, when state is true that means unit is still growing up and vice versa. To check whether a unit has finished growing:
    JASS:
    if state then
        if (growRate < 0 and currentScale <= targetScale) or (growRate > 0 andcurrentScale >= targetScale) then
            // This means unit has finished growing
    
            set currentScale = targetScale // To makes sure the scale doesn't exceed the target
            call SetUnitScale(...)
        endif
    elseif not pause then
        // When unit is being reset-ed
    else
        // Effect duration
    endif

I will look further into it later, sudden busy :p

EDIT:
One more :p

JASS:
            if growCount[id] == 1 then
                set isGrower[id] = false
                set growCount[id] = 0
            else
                set growCount[id] = growCount[id] - 1
            endif
=>
JASS:
            if growCount[id] == 1 then
                set isGrower[id] = false
            endif
            set growCount[id] = growCount[id] - 1
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
JASS:
    struct DefaultScale
        private static key k
        private static Table table = k
        readonly integer unitType
        readonly real    defaultScale
        
        static method create takes integer uType, real scale returns thistype
            local thistype this = table[uType]
            if this == 0 then
                set this = allocate()
                set table[uType] = this
            endif
            set unitType = uType
            set defaultScale = scale
            return this
        endmethod
        
        static method operator [] takes integer i returns thistype
            return table[i]
        endmethod
        
    endstruct
And they are all should be privatized since I feel it's an awful way to get unit's default scale. I mean, we should have a variable DefaultScale for every unit type we register there? No way

You should add another function bloat called GetUnitDefaultScale(unit) :v
 
its pain in butt to get them tho, because analrus decided to migrade to github.
Hm, if it helps... my test map contains them. :D

This is extremely useful though. I hope that case is clarified.
Thanks. :)

set growBack = (eDuration > 0.) :thumbs_up:
Nice one.^^

You should add another function bloat called GetUnitDefaultScale(unit) :v
Indeed, that should be added! :csmile: And thanks for other suggestions, I will have a look at them.

This is better for Tiny's Grow

EDIT:
Not bad...
Thank you! Sorry, what is Tiny's Grow?
 
@Dalvengyr
You were right about the "zero durtion" thread crash, thanks! I do not allow now durations equal or smaller 0 anymore, because it anyway does not make sense.
People can use the SetUnitScale native for this.
JASS:
            set updatesUp = R2I((scaleGoal - scaleStart) /growRate)
            set updatesDown = -1   // Just a negative value. Mustn't be 0 or bigger at initialization.
It's been quite hard to understand what those two variables do. But I have came to a conclusion that one boolean variable, named state or something, could covers both of their jobs.

For example, when state is true that means unit is still growing up and vice versa. To check whether a unit has finished growing:
JASS:
if state then
    if (growRate < 0 and currentScale <= targetScale) or (growRate > 0 andcurrentScale >= targetScale) then
        // This means unit has finished growing

        set currentScale = targetScale // To makes sure the scale doesn't exceed the target
        call SetUnitScale(...)
    endif
elseif not pause then
    // When unit is being reset-ed
else
    // Effect duration
endif
I was already messing around a bit with this, but came to conclusion better to use two variables.
The reason for this was easier handling with accuracy (near targetScale) when unit is affected by multiple scale changes at same time.
(at least how I tested it... if you are sure there is a better way that won't let accuracy suffer too much under multiple instances I will try harder :D)

JASS:
            if growCount[id] == 1 then
                set isGrower[id] = false
                set growCount[id] = 0
            else
                set growCount[id] = growCount[id] - 1
            endif
=>
JASS:
            if growCount[id] == 1 then
                set isGrower[id] = false
            endif
            set growCount[id] = growCount[id] - 1
In case I use set growCount[id] = 0 it's faster than set growCount[id] = growCount[id] - 1 and same result in end. :p

I only quoted things I wanted to answer to. Implemented almost all other suggestions, thanks!. :csmile:

Updated.

@edo
Thanks for the link.^^
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
** Note:
** The system does not allow take 0 as argument for durations.

erm...nope? :D

also, you have at least 10 candidates for generating trigger evaluations, lets play a Hide and seek shall we? :D

Edit: actually, only 6.

+ Indention is wrong inside function ResetScale takes unit u, real duration returns nothing
 
** Note:
** The system does not allow take 0 as argument for durations.

erm...nope? :D

also, you have at least 10 candidates for generating trigger evaluations, lets play a Hide and seek shall we? :D

Edit: actually, only 6.

+ Indention is wrong inside function ResetScale takes unit u, real duration returns nothing
The note was outdated, thanks. :D

And also moved the API! Edo-update done.
 

Deleted member 219079

D

Deleted member 219079

Could you do same kind of library for unitmovementspeed?
 

Deleted member 219079

D

Deleted member 219079

I dunno what elaborate means :/
if you mean like, explain, well like same kind of functionality, but for SetUnitMoveSpeed()
 

Deleted member 219079

D

Deleted member 219079

Ye.

Because at the moment, SetUnitScale normally would interfere with other possible systems using the native. You could be the god of vJass and create the standard system for SetUnitSpeed.

Currently I'm afraid of adding setunitspeed to my ability, cuz it might interfere with other spells. And using it via ability is slow and unnecessary.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
I took a look at this system and I honestly think it does too much. Not only does it do scaling, but it does timed scaling and it'll even upscale and then downscale. I wouldn't mash it all together like this.

For example, upscaling and downscaling are two different scales put together. It is convenient, sure, but should one resource really do this much? It seems like it could be split up to me.

I don't particularly like the idea of hooks in the resource. This means every time that you use the native, it will throw a trigger evaluation. If you are scaling a lot of units, that's a lot of trigger evaluations.


My suggestion:

Resource 1: Simple scale tracking
Resource 2: Managed single scale
Resource 3: Managed scale sequences

Rather than grow and then shrink back to normal, you could create a scale sequence. This means that you could grow, shrink, and then grow again.


In finality, I think that it can be improved a lot. It's a decent first shot though =).

edit
Considering things like DDS, I suppose the hooks are ok. Doesn't mean that I like them ;p.
 
I agree with you, that it should be more straight forward. I reduced structure and made it only single scale now instead of double scale. Thanks.

How ever, nothing done for sequences. Honestly I didn't even really think about it, if it's worth to code it because of usage and so on... so atm user can just use a timer or what ever.

Updated.
 
Hey I don't understand the fuss is about can't we just put units scale in loop to increase and decrease
What fuss? It is a simple system indeed. It works for you.
Yes, you can create your own system of couse.
But why to create your own if you can simply import a system and use the API functions instead? o.o

why such big code with Jass and all
This is not a big code. And I don't understand the critique with using JASS.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Code looks good to me. I'll test it after the weekend.

Concerning UnitIndexer:
I checked Nestharus Demo map and the old version is still included,
So if you wish to stick to that, that's ok.

I wrote about that in GetUnitScale, but same applies there.

I'm not a big fan of the new UnitIndexer, because it requires
a bunch of resources, which I don't use anywhere else.
( Alloc modules, Trigger, TableField and dependencies. )
 
Top