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

Evolving/Devolving Unit

Level 31
Joined
Jul 10, 2007
Messages
6,306
updating this to use abilities as soon as I finish the rest of the necessary lua scripts
Until the lua scripts are done and this is updated to run on chaos, do not use this! This is horribly inefficient compared to the Chaos ability and causes permanent memory leaks! It also has many issues with handle pointers. You have been warned ; D.


From Demo 1 of Stacked Fields as that demo was just so awesome ; D.

Requires- http://www.thehelper.net/forums/showthread.php?t=151978 (2.1.0.0)

Notes-
I am not making a plain evolution system as they are unsafe with unit indexing systems. Evolution/Devolution systems like this one are safe.


Introduction
Wouldn't it be cool if you had like a peasant and he trained really hard and became a footman? Then he trained hard some more by killing guys or w/e and became a swordsman? Eventually a captain? A knight? A full fledged hero?

What if he screws up or gets lazy and goes from hero back to knight? Maybe he lost too many battles, lost some skills, or w/e else?

This system let's you do that with ease. When a unit changes (up a rank), it's called an evolution. When a unit goes down a rank, it's called a devolution. This system can do both =D.

Try the test map out for yourself. Move the unit around, order it to attack, and etc.

Test Map Command-
pop [num]
Devolves a unit num times.

you can type pop by itself, pop0, pop 1, or even pop 335939 and it'll all work ; D. You can even have a typo like adadsa a apopa 2 and it'll still work >.<. Just need to have the pop somewhere in there ; O.

This is safe with index systems

If you attach a struct to a unit and evolve that unit, the struct will still be on it.

If you evolve a unit, triggers that were registered specifically with it will not run anymore.

If a unit decays/is removed, the struct will still get destroyed, so don't worry =).

Safe Usage
If you have a trigger that runs and you use get(handle) to retrieve the EvolvingUnit index, it is always a good idea to make sure that the index is not 0.

If it is 0, the struct you retrieved is null, meaning your evolving unit no longer exists.

If you don't use get, then make sure the evolving unit (unit) is not null.

Code
JASS:
library EvoDevoUnit

globals
    //only use hash if you plan on having a crap load of units with a crap
    //load of evolutions, like 100 units that each evolve 100 times.
    private constant boolean USE_HASH = false
endglobals

//1.2.0.6
//Description
//  Evolves and Devolves units. Retains all unit data.
////////////////////////////////////////////////////
//EvolveUnit struct API
////////////////////////////////////////////////////
//Fields/Properties
//  readonly unit unit
//          the evolution unit handle
//  readonly integer level
//          current evolution level
//Methods
//  static get(handle h) returns EvolvingUnit
//          used with for handle attachment
//  evolve(integer unitType) returns nothing
//          evolve
//  devolve() returns nothing
//          devolve
//  devolveCount(integer count) returns nothing
//          devolve certain amount of times. If you want to devolve more than once but you don't want to clear, 
//          use this instead of devolve as it'll be much faster
//  base() returns nothing
//          devolve  to original
//  create(unit) returns EvolveUnit
//          create new
//  destroy() returns nothing
//          destroy
//  attach(handle) returns nothing
//          attaches instance to handle
    scope sdfidsjfosi
    endscope
    
    private keyword evoCodeDId
    private keyword hashref
    
    private keyword HASH_EvolvingDevolvingUnit
    private struct EvolvingDevolvingUnit extends array
        public unit unit

        static if HASH_EvolvingDevolvingUnit then
            private static key k_unit
            
            public method restore takes hashtable h, integer instance returns nothing
                set unit = LoadUnitHandle(h, instance, k_unit)
            endmethod

            public method archive takes hashtable h, integer instance returns nothing
                if (unit != null) then
                    call SaveUnitHandle(h, instance, k_unit, unit)
                endif
            endmethod
        endif

        public method allocate takes nothing returns nothing
            set unit = null
        endmethod

        public method deallocate takes nothing returns nothing
            //should pretty much just be used for cleaning up handles/structs
            if (unit != null) then
                call RemoveUnit(unit)
            endif
        endmethod
    endstruct
    
    //! runtextmacro MAKE_FIELD_STACK("EvolvingDevolvingUnit", "USE_HASH")
    
    private struct Order extends array
        public integer order
        public widget targetWidget
        public real targetX
        public real targetY
    endstruct
    
    private struct SpawnData extends array
        public static real x
        public static real y
        public static real lifeRatio
        public static real facing
        public static player p
        public static integer i
        public static integer count
        public static integer count2
        public static item array items
        public static real manaRatio
        public static boolean select
        public static real mana
        public static integer userData
        public static boolean useFood
        public static delegate Order orderX
        
        public static method allocate takes unit u, Order ordered returns nothing
            local integer unitId = GetUnitTypeId(u)
            set useFood = GetUnitFoodUsed(u) != 0
            set userData = GetUnitUserData(u)
            set orderX = ordered
            if (GetUnitCurrentOrder(u) != ordered.order) then
                set order = -1
            endif
            set select = IsUnitSelected(u, GetLocalPlayer())
            set x = GetUnitX(u)
            set y = GetUnitY(u)
            set facing = GetUnitFacing(u)
            set p = GetOwningPlayer(u)
            set mana = GetUnitState(u, UNIT_STATE_MANA)
            if (mana != 0) then
                set manaRatio = mana/GetUnitState(u, UNIT_STATE_MAX_MANA)
            else
                set manaRatio = 0
            endif
            set lifeRatio = GetWidgetLife(u)/GetUnitState(u, UNIT_STATE_MAX_LIFE)
            set count = UnitInventorySize(u)
            set i = 0
            loop
                exitwhen i == count
                set items[i] = UnitItemInSlot(u, i)
                if (items[i] != null) then
                    call UnitDropItemSlot(u, items[i], i)
                endif
                set i = i + 1
            endloop
        endmethod
        
        public static method copy takes unit u, boolean basics returns nothing
            call IssueImmediateOrder(u, "stop")
            if (basics) then
                call SetUnitX(u, x)
                call SetUnitY(u, y)
                call SetUnitFacingTimed(u, facing, 0)
                call SetUnitOwner(u, p, true)
            endif
            call SetUnitUserData(u, userData)
            call SetUnitUseFood(u, useFood)
            call SelectUnit(u, select)
            call SetWidgetLife(u, GetUnitState(u, UNIT_STATE_MAX_LIFE)*lifeRatio)
            set mana = GetUnitState(u, UNIT_STATE_MANA)
            if (mana != 0) then
                call SetUnitState(u, UNIT_STATE_MANA, mana*manaRatio)
            endif
            set i = 0
            loop
                exitwhen i == count
                if (items[i] != null) then
                    call UnitAddItem(u, items[i])
                    set items[i] = null
                endif
                set i = i + 1
            endloop
            
            if (order != -1) then
                if (targetWidget != null) then
                    call IssueTargetOrderById(u, order, targetWidget)
                else
                    call IssuePointOrderById(u, order, targetX, targetY)
                endif
            endif
        endmethod
    endstruct

    struct EvolvingUnit extends array
        implement EvolvingDevolvingUnitStack
        
        private static hashtable hashref = InitHashtable()
        private integer tCount
        
        private static boolexpr pointOrder
        private static boolexpr targetOrder
        private trigger trackPointOrder
        private trigger trackTargetOrder
        private integer trackPointOrderId
        private integer trackTargetOrderId
        
        private static real hideX
        private static real hideY
        
        //recycler
        private thistype next
        private thistype previous
        private static timer recycler = CreateTimer()
        private static boolean recycleRunning = false
        
        private static method operator head takes nothing returns thistype
            return 0
        endmethod
        
        public method attach takes handle h returns nothing
            local integer id = GetHandleId(h)
            call SaveInteger(hashref, id, 0, this)
            call SaveInteger(hashref, this, tCount, id)
            set tCount = tCount + 1
        endmethod
        
        public static method get takes handle h returns thistype
            return LoadInteger(hashref, GetHandleId(h), 0)
        endmethod
        
        //hide unit
        public method operator unit takes nothing returns unit
            return EvolvingDevolvingUnit.unit
        endmethod

        private method operator unit= takes unit u returns nothing
            set EvolvingDevolvingUnit.unit = u
        endmethod
        
        public method operator level takes nothing returns integer
            return EvolvingDevolvingUnit.size
        endmethod
        
        public method destroy takes nothing returns nothing
            if (trackPointOrder != null) then //make sure allocated
                call deallocateEvolvingDevolvingUnit() //all you have to do
                
                ////////////////////////////////////////////////////////
                call RemoveSavedInteger(hashref, trackPointOrderId, 0)
                call RemoveSavedInteger(hashref, trackTargetOrderId, 0)
                
                call DestroyTrigger(trackPointOrder)
                call DestroyTrigger(trackTargetOrder)
                
                set Order(this).targetWidget = null
                
                set trackPointOrder = null
                set trackTargetOrder = null
                
                set next.previous = previous
                set previous.next = next
                
                loop
                    exitwhen tCount == 0
                    set tCount = tCount - 1
                    call RemoveSavedInteger(hashref, LoadInteger(hashref, this, tCount), 0)
                endloop
                call FlushChildHashtable(hashref, this)
            endif
        endmethod
        
        private static method runRecycle takes nothing returns nothing
            local thistype this = head.next
            loop
                exitwhen this == 0
                if (unit == null) then
                    call destroy()
                endif
                set this = next
            endloop
            
            if (head.next == 0) then
                set recycleRunning = false
                call PauseTimer(recycler)
            endif
        endmethod
        
        private method recycle takes nothing returns nothing
            set head.next.previous = this
            set next = head.next
            set head.next = this
            set previous = head
            
            if (not recycleRunning) then
                set recycleRunning = true
                call TimerStart(recycler, 2, true, function thistype.runRecycle)
            endif
        endmethod
        
        private static method trackPointOrderX takes nothing returns boolean
            local Order this = get(GetTriggeringTrigger())
            set this.targetWidget = null
            set this.targetX = GetOrderPointX()
            set this.targetY = GetOrderPointY()
            set this.order = GetIssuedOrderId()
            return false
        endmethod
        
        private static method trackTargetOrderX takes nothing returns boolean
            local Order this = get(GetTriggeringTrigger())
            set this.targetWidget = GetOrderTarget()
            set this.order = GetIssuedOrderId()
            return false
        endmethod
        
        private method registerTrigs takes nothing returns nothing
            call TriggerRegisterUnitEvent(trackPointOrder, unit, EVENT_UNIT_ISSUED_POINT_ORDER)
            call TriggerRegisterUnitEvent(trackTargetOrder, unit, EVENT_UNIT_ISSUED_TARGET_ORDER)
        endmethod

        public static method create takes unit u returns thistype
            local thistype this = 0
            if (u != null) then
                set this = allocateEvolvingDevolvingUnit()
                set unit = u
                
                set trackPointOrder = CreateTrigger()
                set trackTargetOrder = CreateTrigger()
                
                set trackPointOrderId = GetHandleId(trackPointOrder)
                set trackTargetOrderId = GetHandleId(trackTargetOrder)
                call SaveInteger(hashref, trackPointOrderId, 0, this)
                call SaveInteger(hashref, trackTargetOrderId, 0, this)
                
                set Order(this).order = -1
                call TriggerAddCondition(trackPointOrder, pointOrder)
                call TriggerAddCondition(trackTargetOrder, targetOrder)
                
                call registerTrigs()
                call recycle()
            endif
            return this
        endmethod
        
        private method startCopy takes nothing returns nothing
            call DisableTrigger(trackPointOrder)
            call DisableTrigger(trackTargetOrder)
            call SpawnData.allocate(unit, this)
            call ShowUnit(unit, false)
            call PauseUnit(unit, true)
            call SetUnitX(unit, hideX)
            call SetUnitY(unit, hideY)
        endmethod
        
        private method finishCopy takes boolean basics returns nothing
            call ShowUnit(unit, true)
            call PauseUnit(unit, false)
            call SpawnData.copy(unit, basics)
            call EnableTrigger(trackPointOrder)
            call EnableTrigger(trackTargetOrder)
        endmethod

        public method devolve takes nothing returns nothing
            if (trackPointOrder != null) then
                if (unit != null) then
                    if (not EvolvingDevolvingUnit.empty) then
                        call startCopy()
                        
                        call EvolvingDevolvingUnit.pop()
                        
                        call finishCopy(true)
                    endif
                else
                    call destroy()
                endif
            endif
        endmethod
        
        public method devolveCount takes integer steps returns nothing
            if (trackPointOrder != null) then
                if (unit != null) then
                    if (steps != 0 and not EvolvingDevolvingUnit.empty) then
                        call startCopy()
                        
                        call EvolvingDevolvingUnit.popCount(steps)
                        
                        call finishCopy(true)
                    endif
                else
                    call destroy()
                endif
            endif
        endmethod
        
        public method evolve takes integer unitType returns nothing
            if (trackPointOrder != null) then
                if (unit != null) then
                    call startCopy()
                    
                    call EvolvingDevolvingUnit.push()
                    set unit = CreateUnit(SpawnData.p, unitType, SpawnData.x, SpawnData.y, SpawnData.facing)
                    
                    call finishCopy(false)
                    call registerTrigs()
                else
                    call destroy()
                endif
            endif
        endmethod

        public method base takes nothing returns nothing
            if (trackPointOrder != null) then
                if (unit != null) then
                    if (not EvolvingDevolvingUnit.empty) then
                        call startCopy()
                        
                        call EvolvingDevolvingUnit.clear()
                        
                        call finishCopy(true)
                    endif
                else
                    call destroy()
                endif
            endif
        endmethod
        
        private static method onInit takes nothing returns nothing
            local rect world = GetWorldBounds()
            set hideX = GetRectMaxX(world)
            set hideY = GetRectMaxY(world)
            call RemoveRect(world)
            set world = null
            
            set pointOrder = Condition(function thistype.trackPointOrderX)
            set targetOrder = Condition(function thistype.trackTargetOrderX)
        endmethod
    endstruct
endlibrary

Demo Code
JASS:
//Order the unit around the map so you can see that it is still moving even while evolving/devolving
//type pop to devolve the unit
//watch as its evolution takes longer and longer as the xp required increases

//Commands-
//pop [num]
//  devolve num times. If num isn't specified, pop once
scope EvoDevoDemonstration
    private scope EvoUnit
        private keyword allocate
        private keyword deallocate
        private keyword evolution
        private keyword super
        
        private module EvoUnit
            private delegate EvolvingUnit un
            private integer levelX
            private integer xpX
            
            public static integer array evolution
            
            public method operator super takes nothing returns EvolvingUnit
                return un
            endmethod
            
            public method operator evoLevel takes nothing returns integer
                return super.level
            endmethod
            
            public method operator canEvolve takes nothing returns boolean
                return evolution[evoLevel+1] != 0
            endmethod
            
            public method operator canDevolve takes nothing returns boolean
                return evoLevel != 0
            endmethod
            
            public method operator level takes nothing returns integer
                return levelX
            endmethod
            
            public method operator xp takes nothing returns integer
                return xpX
            endmethod
            
            public method evolve takes nothing returns nothing
                if (canEvolve) then
                    set levelX = 0
                    set xpX = 0
                    call super.evolve(evolution[evoLevel+1])
                endif
            endmethod
            
            public method evolveCount takes integer steps returns nothing
                set levelX = 0
                set xpX = 0
                loop
                    exitwhen steps == 0 or evolution[evoLevel+1] == 0
                    set steps = steps - 1
                    call super.evolve(evolution[evoLevel+1])
                endloop
            endmethod
            
            public method devolveCount takes integer steps returns nothing
                if (canDevolve) then
                    set levelX = 0
                    set xpX = 0
                    call super.devolveCount(steps)
                endif
            endmethod
            
            public method devolve takes nothing returns nothing
                if (canDevolve) then
                    set levelX = 0
                    set xpX = 0
                    call super.devolve()
                endif
            endmethod
            
            public method base takes nothing returns nothing
                if (canDevolve) then
                    set levelX = 0
                    set xpX = 0
                    call super.base()
                endif
            endmethod
            
            public method operator level= takes integer val returns nothing
                set levelX = val
                set xpX = 0
                if (levelX >= EVO_LEVEL) then
                    call evolve()
                elseif (levelX < 0) then
                    call devolve()
                endif
            endmethod
            
            public method operator xp= takes integer val returns nothing
                set xpX = val
                if (xpX >= evoLevel*EVO_LEVEL) then
                    set level = level + 1
                elseif (xpX < 0) then
                    set level = level - 1
                endif
            endmethod
            
            public static method allocate takes unit u returns thistype
                local thistype this = EvolvingUnit.create(u)
                set un = this
                return this
            endmethod
            
            public method deallocate takes nothing returns nothing
                call super.destroy()
            endmethod
        endmodule
        
        struct HumanSoldier extends array
            private static constant real LEVEL_MULTIPLIER = 2
            private static constant integer EVO_LEVEL = 1
            implement EvoUnit
            
            public static method create takes player p, real x, real y, real facing returns thistype
                return allocate(CreateUnit(p, thistype.evolution[0], x, y, facing))
            endmethod
            
            public method destroy takes nothing returns nothing
                call deallocate()
            endmethod
            
            private static method onInit takes nothing returns nothing
                set evolution[0] = 'hpea'
                set evolution[1] = 'hmil'
                set evolution[2] = 'hfoo'
                set evolution[3] = 'hcth'
                set evolution[4] = 'hkni'
            endmethod
        endstruct
    endscope
    
    private struct Demonstration extends array
        private static constant string POP = "pop"
        private static integer popSize
        
        private static constant player P = Player(0)
        private static constant real X = 2000
        private static constant real Y = 2000
        
        private static constant real TICK = 1
        private static constant integer XP_PER_TICK = 1
        
        private delegate HumanSoldier humanSoldier
        
        private static method addXp takes nothing returns nothing
            local thistype this = HumanSoldier.get(GetExpiredTimer())
            set xp = xp + 1
        endmethod
        
        private static method delevel takes nothing returns boolean
            local thistype this = HumanSoldier.get(GetTriggeringTrigger())
            
            local integer count
            local integer i
            local string char
            local string str = GetEventPlayerChatString()
            local integer length
            local boolean useCount = false
            
            if (str == POP) then
                call devolve()
            elseif (SubString(str, 0, popSize) == POP) then
                set str = SubString(str, popSize, StringLength(str))
                set i = 0
                set count = 0
                set length = StringLength(str)
                
                loop
                    set char = SubString(str, i, i+1)
                    if (char == "0" or char == "1" or char == "2" or char == "3" or char == "4" or char == "5" or char == "6" or char == "7" or char == "8" or char == "9") then
                        set count = count * 10 + S2I(char)
                        set useCount = true
                    endif
                    set i = i + 1
                    exitwhen i == length
                endloop
                
                if (not useCount) then
                    set count = 1
                endif
                
                if (count >= evoLevel) then
                    call base()
                else
                    call devolveCount(count)
                endif
            endif
            return false
        endmethod
        
        private static method onInit takes nothing returns nothing
            local trigger deTr = CreateTrigger()
            local timer xpT = CreateTimer()
            local thistype this = HumanSoldier.create(P, X, Y, 0)
            set humanSoldier = this
            set popSize = StringLength(POP)
            
            call TriggerRegisterPlayerChatEvent(deTr, Player(0), POP, false)
            call TriggerAddCondition(deTr, Condition(function thistype.delevel))
            
            call attach(deTr)
            call attach(xpT)
            
            call TimerStart(xpT, TICK, true, function thistype.addXp)
        endmethod
    endstruct
    
endscope
 

Attachments

  • test map.zip
    23.3 KB · Views: 76
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
It'd be nice if the the requirement had some sort of library to require from this one. That way if you forget a requirement, you don't get a bunch of nigh incoherent syntax errors popping up at you.

actually, this can be better done with the Chaos ability, but the Lua script I made for it fails because of how vjass handles external scripts + importing. I'm still waiting on this update for vjass, but it's doubtful : (.
 
Level 8
Joined
Oct 3, 2008
Messages
367
I don't think any more Jasshelper updates are ever coming. If someone else stepped up to the plate, maybe there'd be a solution. But with current technology, it appears that it's not very possible.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Yea, you know what I want to do right? Using loops to generate unique object ids for objects, then generate a script file, import that file, and etc so that the object id can be used. From here, you can create lots of objects without worry of collision, which would allow this library to easily move to the Lua script for use with the Chaos ability ; ). It'd also help a lot of other libraries too : P.

As that's impossible atm and this library is ridiculously inefficient compared to Chaos, it's best to simply do it by hand right now /cry.

I would personally not use this library until the Lua scripting stuff could be done so that the Chaos ability could be used.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Hurry up and make this run on chaos.

I'm in:
Calculus
all 3 core humanity (each core humanity making me write a 5-15 page paper every week)
Anthropology (more papers and discussions)

I barely have enough time to do anything : O. When I wrote my String Parser tutorial, I fell way behind on all my homework and even had to ditch a day to catch up.

Sorry azlier, I don't know when I'm going to get to this : |. I was working on all of the necessary Lua scripts, but it really does require a mega load. I think I need to do 6 or 7 more scripts before the "complete rewrite" of this entire library can take place >.<.

It might not be worked on again until mid December.
 
Top