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

Scale System

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
JASS:
library ScaleSystem /*

                                Scale System v1.00
                                    by Flux
                                   
            Scale System provides a scaling system for units gradually changing the
            scale of a unit instead of instantly. The system aims to replace
            SetUnitScale which conflicts with itself if used in different parts
            of the code.
           
        NOTE:
            Any SetUnitScale used before the system will be ignored.
       
        CREDITS:
            Bribe - Table
       
    */ requires /*
   
    */ optional Table /*
        If not found, the system will use a hashtable. Hashtables are limited to 255 per map.
   
    ******************************************************************
                                  API
    ******************************************************************
    */
    //! novjass
        struct Scale
           
            static method create takes unit u, real addedScale returns thistype
            //Create a Scale instance on a unit. Example:
                local Scale s = Scale.create(<yourUnit>, 0.5)
                //Will increase the scaling of <yourUnit> to 1.5 because 0.5 were added to it
           
            public real speed
            //Refers to how fast the Scale instance scales per second. Example:
                local Scale s = Scale.create(<yourUnit>, 0.5)
                set s.speed = 0.5
                //This means that after 1 second, <yourUnit> have gained 0.5 scale
            //Another example:
                local Scale s = Scale.create(<yourUnit>, 1.0)
                set s.speed = 0.5
                //This means that it will take 2 seconds for the Scaling animation to finish
                //because it can only scale 0.5 per second.
           
            public real duration
            //Refers to the duration of the scale instance. Example
                local Scale s = Scale.create(<yourUnit>, 0.5)
                set s.duration = 5.0
                //After 5 seconds, <yourUnit> will start to decrease in scale.
               
            static method get takes unit u returns real
            //Returns the current scale of a unit. Returns 1.0 if the unit is currently unaffected
            //by the Scale System.
               
            method destroy takes nothing returns nothing
            //Destroy a Scale instance. You mostly won't need this if the Scale instance
            //has a duration.
       
    //! endnovjass 

    globals
        //Scaling Speed per second used as default.
        private constant real DEFAULT_SPEED = 1.0
        private constant real TIMEOUT = 0.05
    endglobals
   
    struct Scale
       
        public real speed
        public real duration
        private unit u
        private real add
        private real subtract
        private integer id
       
        static if LIBRARY_Table then
            private static Table tb
        else
            private static hashtable hash = InitHashtable()
        endif
       
        private static timer t = CreateTimer()
       
        private thistype next
        private thistype prev
       
        method destroy takes nothing returns nothing
            set this.duration = 0
        endmethod
       
        static method get takes unit u returns real
            local integer id = GetHandleId(u)
            static if LIBRARY_Table then
                if thistype.tb.has(id) then
                    return thistype.tb.real[id]
                endif
            else
                if HaveSavedReal(thistype.hash, id, 0) then
                    return LoadReal(thistype.hash, id, 0)
                endif
            endif
            return 1.0
        endmethod
       
        private static method onPeriod takes nothing returns nothing
            local thistype this = thistype(0).next
            local real added
            local real new
            loop
                exitwhen this == 0
                set this.duration = this.duration - TIMEOUT
                if this.duration > 0 then
                    if this.add != 0 then
                        if this.add > 0 then
                            set added = RMinBJ(this.add, this.speed*TIMEOUT)
                        else
                            set added = -RMinBJ(RAbsBJ(this.add), this.speed*TIMEOUT)
                        endif
                        set this.add = this.add - added
                        set this.subtract = this.subtract + added
                        static if LIBRARY_Table then
                            set new = thistype.tb.real[this.id] + added
                            set thistype.tb.real[this.id] = new
                        else
                            set new = LoadReal(thistype.hash, this.id, 0) + added
                            call SaveReal(thistype.hash, this.id, 0, new)
                        endif
                        call SetUnitScale(this.u, new, 0, 0)
                    endif
                else
                    if this.subtract != 0 then
                        if this.subtract > 0 then
                            set added = RMinBJ(this.subtract, this.speed*TIMEOUT)
                        else
                            set added = -RMinBJ(RAbsBJ(this.subtract), this.speed*TIMEOUT)
                        endif
                        set this.subtract = this.subtract - added
                        static if LIBRARY_Table then
                            set new = thistype.tb.real[this.id] - added
                            set thistype.tb.real[this.id] = new
                        else
                            set new = LoadReal(thistype.hash, this.id, 0) - added
                            call SaveReal(thistype.hash, this.id, 0, new)
                        endif
                        call SetUnitScale(this.u, new, 0, 0)
                    else
                        static if LIBRARY_Table then
                            set thistype.tb[this.id] = thistype.tb[this.id] - 1
                            if thistype.tb[this.id] == 0 then
                                call thistype.tb.remove(this.id)
                                call thistype.tb.real.remove(this.id)
                            endif
                        else
                            call SaveInteger(thistype.hash, this.id, 0, LoadInteger(thistype.hash, this.id, 0) - 1)
                            if LoadInteger(thistype.hash, this.id, 0) == 0 then
                                call RemoveSavedInteger(thistype.hash, this.id, 0)
                                call RemoveSavedReal(thistype.hash, this.id, 0)
                            endif
                        endif
                        set this.next.prev = this.prev
                        set this.prev.next = this.next
                        if thistype(0).next == 0 then
                            call PauseTimer(thistype.t)
                        endif
                        set this.u = null
                        call this.deallocate()
                    endif
                endif
                set this = this.next
            endloop
        endmethod
       
        static method create takes unit u, real addedScale returns thistype
            local thistype this = thistype.allocate()
            set this.u = u
            set this.id = GetHandleId(u)
            set this.speed = DEFAULT_SPEED
            set this.add = addedScale
            set this.subtract = 0
            set this.duration = 0xFFFFFF
            static if LIBRARY_Table then
                if thistype.tb.real.has(this.id) then
                    set thistype.tb[this.id] = thistype.tb[this.id] + 1
                else
                    set thistype.tb.real[this.id] = 1.0
                    set thistype.tb[this.id] = 1
                endif
            else
                if HaveSavedReal(thistype.hash, this.id, 0) then
                    call SaveInteger(thistype.hash, this.id, 0, LoadInteger(thistype.hash, this.id, 0) + 1)
                else
                    call SaveReal(thistype.hash, this.id, 0, 1.0)
                    call SaveInteger(thistype.hash, this.id, 0, 1)
                endif
            endif
            set this.next = thistype(0)
            set this.prev = thistype(0).prev
            set this.prev.next = this
            set this.next.prev = this
            if this.prev == 0 then
                call TimerStart(thistype.t, TIMEOUT, true, function thistype.onPeriod)
            endif
            return this
        endmethod
       
        static if LIBRARY_Table then
            private static method onInit takes nothing returns nothing
                set thistype.tb = Table.create()
            endmethod
        endif
       
    endstruct
   
endlibrary



v1.00 - [27 October 2016]
- Initial Release
Contents

Scale System (Map)

Level 13
Joined
Nov 7, 2014
Messages
571
Instead of requiring Table why don't you require that a unit's GetUnitUserData() be in the range [1, 8190] and not bother with hashtables and static ifs? (I think that should be a thing!)

In my opinion it would be easier to use if the interface worked something like this:

add a scale of X in T-up seconds, after T-up seconds wait Dur seconds, then scale back of -X in T-down seconds, although letting T-up = T-down is probably a good idea

The speed parameter seems weird to me...

Edit: Currently there doesn't seem to be a way to destroy the instance instantly (e.g: dispelling bloodlust, instantly "resets" back the scale).

In order to apply a scale_delta of same 0.5 with a scaling_duration of 1.0 and a staying_scaled_duration of 2.0 one has to write this:

JASS:
    set scale_delta = 0.5
    set scaling_duration = 1.0
    set staying_scaled_duration = 2.0

    set sc = Scale.create(u, scale_delta)
    set sc.speed = scale_delta / scaling_duration
    set sc.duration = staying_scaled_duration + scaling_duration

which is not great, so that's what I meant when I said that the interface can be simplified.

Also imagine that an item when equipped/dropped increased/decreased the scale of the hero, we can't simply store a Scale instance and apply it/unapply it because it will get destroyed the first time the item gets dropped, so the item has to store (2 + 1) fields, 2 for creating the Scale instance and 1 for the Scale instance itself.

And "Scale" doesn't seem like a good name because other widgets have scale as well, not just units.
 
Last edited:
Level 22
Joined
Feb 6, 2014
Messages
2,466
In my opinion it would be easier to use if the interface worked something like this:
I don't know, the current interface looks fine in my opinion. Currently, when the instance expires, the unit will start shrinking.

Edit: Currently there doesn't seem to be a way to destroy the instance instantly (e.g: dispelling bloodlust, instantly "resets" back the scale).
set the speed to 99999, the scaling animation will surely be instant.

Also imagine that an item when equipped/dropped increased/decreased the scale of the hero, we can't simply store a Scale instance and apply it/unapply it because it will get destroyed the first time the item gets dropped, so the item has to store (2 + 1) fields, 2 for creating the Scale instance and 1 for the Scale instance itself.
I don't get this part. Store the Scale instance to a hashtable using the item's handle id so that when it is dropped, you can manually destroy the Scale instance.

And "Scale" doesn't seem like a good name because other widgets have scale as well, not just units.
How about UnitScale? But I hate the long name. Maybe I will make it applicable to all widgets just to maintain the name.
 
My system's API seemed a bit bloated because I provided also pure fucntions instead of struct-only API.
I made it now with structs usage only, and also allow to modify scale-members.

If you ask me, I'm not so strict in Spells Section of having similar resources if we only have 1 approved for now.
But on the other side, it also should not be totaly equal probably, but should provide probably some new aspect / uses other approah / new user interface / or something else.

When I look at my approved one now, I'm personaly happy with it's usage I think, but maybe you think it's reasonable to make something different.
We can discuss it I think.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
The only significant difference I can see is in this resource, duration refers to how long the scale instance last, not how long the scaling occurs (and this doesn't require Unit Indexer). I can't really think of new features not already existing from yours so I think we don't need this one. Feel free to Graveyard this.
 
I had it also back then to allow defining a duration for the scale-state iirc, though I removed it for modularity reasons.
It may be some handy, sure, but Nestharus critizized me that the next advanced step from "single scaling" should be "sequenced scaling", which means you allow a queue strucuture for the scales.

Scaling into one direction and then backwards again is basicly one hardcoded and limited sequence approach, which could be generalized actually.
For example maybe I want to scale up, and after 2 seconds I dont wanna scale back, but instead I wanna scale up again, but much faster.

So I decided to remove the scaling back thing, and focused just on a straight forward single scale approach.
The user have to use timers for now if they want custom sequences, but it somehow never motivated me enough to implement a sequence structure.
 
Top