• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[System]Udn- Unit Decaying

[System]Corpse Manager

being rewritten to increase efficiency

JASS:
native UnitAlive takes unit id returns boolean

library CorpseManager initializer Initialization uses PlayerTracker, Recycle, GenericData
/*Information
//===================================================================
Name: Unit Decaying
Version: 7.2
Author: Nestharus

Settings:
*///===================================================================
globals
    private constant real UNIT_DECAY = 90
endglobals

//! textmacro CorpseManager_EXTRA_PLAYERS
    //call TriggerRegisterPlayerUnitEvent(decay, GetPlayer(15), EVENT_PLAYER_UNIT_DEATH, startDecay)
//! endtextmacro

//! textmacro CorpseManager_DECAY
    return not IsUnitType(u, UNIT_TYPE_HERO)
//! endtextmacro
/*//===================================================================

Description:
    What does it do-
        This allows you to do things when a unit decays. The normal
        UnitSuspendDecay function does not work properly as it is
        capped out at 8 seconds. This one works to any amount you want.
        It can also change whether or not units and unit types do or don't
        decay in real time.

Requirements: PlayerTracker- hiveworkshop.com/forums/jass-functions-413/system-pt-player-tracking-142554/
              Recycle- thehelper.net/forums/showthread.php?t=136087
              GenericData- thehelper.net/forums/showthread.php?t=141695

Installation: Advanced -> Gameplay Constants -> Corpse Decay Times -> 9999999

Settings:
------------------------------------------------------------------
UNIT_DECAY
    Refers to the global decay time for all units. This is only used
    if a unit does not have a set decay time.

CorpseManager_EXTRA_PLAYERS-
    Refers to other players that this system runs for. Does not go
    through a loop. Players 0-11 are automatically registered.

CorpseManager_DECAY-
    Will check this for decay. Heroes do not decay in a normal game,
    so they are automatically included.

API:
------------------------------------------------------------------
All Decay Events Evaluate The Triggers. Use TriggerAddCondition() instead
of TriggerAddAction() if you want your stuff to run.

-SetUnitDecay(unit, time)
    Will set the decay time of a specific unit. Setting the time to
    0 will make it default to the unitType time.

-SetUnitTypeDecay(unitType, time)
    Will set the decay time of a unit type. Setting the time to 0
    will make it default to the global decay time.

-SetUnitDecayFlag(unit, doesDecay)
    This will make it so that the unit's flag will override the unit
    type flag if enabled is true.

-SetUnitTypeDecayFlag(unit, doesDecay)
    This will change the decay flag for all units of a type

-TriggerRegisterDecayEvent(trigger) returns integer
    Will fire whenever a unit decays. The integer returned is
    an id that can be used to unregister the trigger when desired.

-TriggerRegisterUnitDecayEvent(trigger, unit) returns integer
    Will fire when the specific unit decays. The integer returned
    is an id that can be used to unregister the trigger when desired.
    After the unit decays, all triggers registered with the unit are
    automatically unregistered, so there are no leaks.

-TriggerRegisterPlayerDecayEvent(trigger, playerId) returns integer
    Will fire whenever a unit owned by player decays. The integer returned is
    an id that can be used to unregister the trigger when desired.

-TriggerRegisterUnitTypeDecayEvent(trigger, unitType) returns integer
    Will fire whenever a unit of unitType decays. The integer returned is
    an id that can be used to unregister the trigger when desired.

-TriggerRegisterPlayerUnitTypeDecayEvent(trigger, playerId, unitType) returns integer
    Will fire whenever a unit of unitType owned by player decays. The integer returned
    is an id that can be used to unregister the trigger when desired.

-TriggerRemoveDecayEvent(id)
    Will unregister the trigger associated with the id from the system.

-TriggerRemoveUnitDecayEvent(id, unit)
    Will unregister the trigger associated with the id from the system.
    Requires unit as the trigger was registered only for a specific
    unit.

-TriggerRemovePlayerDecayEvent(id, playerId)
    Will unregister the trigger associated with the id from the system.
    Requires playerId as the trigger was registered only for a specific
    player.

-TriggerRemovePlayerUnitTypeDecayEvent(id, playerId, unitTypeId)
    Will unregister the trigger associated with the id from the system.
    Requires playerId and unitTypeId.

-TriggerRemoveUnitTypeDecayEvent(id, unitTypeId)
    Will unregister the trigger associated with the id from the system.
    Reguires a unit type id as the trigger was registered only for a
    specific unit type.

-GetLastDecayedUnit()
    Will return the last decaying unit

-GetUnitRemainingDecayTime(unit)
    Returns the remaining decay time of a unit. Will return 0
    if that unit is alive. Only works on units that do decay.

-GetUnitElapsedDecayTime(unit)
    Returns the elapsed decay time of a unit. Will return 0 if that
    unit is alive. Only works on units that do decay.

-GetUnitDecayMaxTime(unit)
    Returns the decay time of a unit. If the decay time is 0, will
    return the decay time of the unit's type id. If that is 0, it
    will return UNIT_DECAY.

-SetUnitRemainingDecay(unit, time)
    Will set the remaining decay of a unit. A time of 0 will work here.

-CreateUnitCorpse(player, unitTypeId, x, y, facing, time)
    Will create a decaying corpse. If the time is 0, it will default
    to unitTypeId values. If those are 0, it will default to UNIT_DECAY.
------------------------------------------------------------------*/
    private keyword CorpseUnit

    private struct CorpseTimer extends array
        implement PermanentId
        public CorpseUnit decayUnit
        public timer decayTimer
    endstruct

    private struct CorpseUnitType extends array
        implement PermanentId
        public real decay
        public boolean flag
    endstruct

    private struct CorpseUnit extends array
        implement DynamicId
        public CorpseTimer decayTimer
        public unit decayUnit
        public real decay
        public boolean flag
        public integer decayHandleId
    endstruct

    private struct EventPlayerUnit
        public static integer count = 0
        public static hashtable decayEvent = InitHashtable()
    endstruct

    private struct EventPlayerType
        public static integer array count[16]
        public static hashtable decayEvent = InitHashtable()
    endstruct

    private struct EventUnit
        public static integer count = 0
        public static hashtable decayEvent = InitHashtable()
    endstruct

    private struct EventUnitType
        public static integer count = 0
        public static hashtable decayEvent = InitHashtable()
    endstruct

    private struct EventDecay
        public static integer count = 0
        public static trigger array decayEvent
    endstruct

    globals
        private trigger decayTrig = CreateTrigger()

        //Event Vars
        private unit lastDecayedUnit = null

        private boolexpr boolFunc
        private CorpseTimer curTimer
        private CorpseUnitType curUnitType
        private CorpseUnit curUnit
        private integer handleId
        private timer runTimer
        private unit runUnit
        private real decayTime
        private integer runUnitType
        private integer runIndex
        private integer runPlayerId
    endglobals

    private function DoesDecay takes unit u returns boolean
        //! runtextmacro CorpseManager_DECAY()
    endfunction

    function SetUnitDecayFlag takes unit u, boolean flag returns nothing
        set CorpseUnit(CorpseUnit[GetHandleId(u)]).flag = not flag
    endfunction
    
    function SetUnitTypeDecayFlag takes integer unitTypeId, boolean flag returns nothing
        set CorpseUnitType(CorpseUnitType[unitTypeId]).flag = not flag
    endfunction

    function GetUnitRemainingDecayTime takes unit u returns real
        if DoesDecay(u) and not UnitAlive(u) then
            return TimerGetRemaining(CorpseUnit(CorpseUnit[GetHandleId(u)]).decayTimer.decayTimer)
        endif
        return 0.
    endfunction

    function GetUnitElapsedDecayTime takes unit u returns real
        if DoesDecay(u) and not UnitAlive(u) then
            return TimerGetElapsed(CorpseUnit(CorpseUnit[GetHandleId(u)]).decayTimer.decayTimer)
        endif
        return 0.
    endfunction

    function GetUnitDecayMaxTime takes unit u returns real
        if DoesDecay(u) then
            set curUnit = CorpseUnit[GetHandleId(u)]
            set curUnitType = CorpseUnitType[GetUnitTypeId(u)]
            if not (curUnit.flag and curUnitType.flag) then
                if curUnit.decay > 0 then
                    return curUnit.decay
                elseif curUnitType.decay > 0 then
                    return curUnitType.decay
                endif
                return UNIT_DECAY
            endif
        endif
        return 0.
    endfunction

    function SetUnitDecay takes unit u, real decayTime returns nothing
        set handleId = GetHandleId(u)
        set curUnit = CorpseUnit[handleId]
        set curUnit.decayHandleId = handleId
        set curUnit.decay = decayTime
    endfunction

    function SetUnitTypeDecay takes integer unitTypeId, real decayTime returns nothing
        set CorpseUnitType(CorpseUnitType[unitTypeId]).decay = decayTime
    endfunction

    constant function GetLastDecayedUnit takes nothing returns unit
        return lastDecayedUnit
    endfunction

    function TriggerRegisterUnitDecayEvent takes trigger t, unit u returns integer
        call SaveTriggerHandle(EventUnit.decayEvent, GetHandleId(u), EventUnit.count, t)
        set EventUnit.count = EventUnit.count + 1
        return EventUnit.count
    endfunction

    function TriggerRegisterDecayEvent takes trigger t returns integer
        set EventDecay.decayEvent[EventDecay.count] = t
        set EventDecay.count = EventDecay.count + 1
        return EventDecay.count
    endfunction

    function TriggerRegisterPlayerDecayEvent takes trigger t, integer playerId returns integer
        call SaveTriggerHandle(EventPlayerUnit.decayEvent, playerId, EventPlayerUnit.count, t)
        set EventPlayerUnit.count = EventPlayerUnit.count + 1
        return EventPlayerUnit.count
    endfunction

    function TriggerRegisterUnitTypeDecayEvent takes trigger t, integer unitTypeId returns integer
        call SaveTriggerHandle(EventUnitType.decayEvent, unitTypeId, EventUnitType.count, t)
        set EventUnitType.count = EventUnitType.count + 1
        return EventUnitType.count
    endfunction

    function TriggerRegisterPlayerUnitTypeDecayEvent takes trigger t, integer playerId, integer unitTypeId returns integer
        call SaveTriggerHandle(EventPlayerType.decayEvent, unitTypeId, playerId+16*EventPlayerType.count[playerId], t)
        set EventPlayerType.count[playerId] = EventPlayerType.count[playerId] + 1
        return EventUnitType.count
    endfunction

    function TriggerRemoveUnitDecayEvent takes integer id, unit u returns nothing
        set EventUnit.count = EventUnit.count - 1
        set handleId = GetHandleId(u)
        call SaveTriggerHandle(EventUnit.decayEvent, handleId, id, LoadTriggerHandle(EventUnit.decayEvent, handleId, EventUnit.count))
    endfunction

    function TriggerRemoveDecayEvent takes integer id returns nothing
        set EventDecay.count = EventDecay.count + -1
        set EventDecay.decayEvent[id] = EventDecay.decayEvent[EventDecay.count]
    endfunction

    function TriggerRemovePlayerDecayEvent takes integer id, integer playerId returns nothing
        set EventPlayerUnit.count = EventPlayerUnit.count - 1
        call SaveTriggerHandle(EventPlayerUnit.decayEvent, playerId, id, LoadTriggerHandle(EventPlayerUnit.decayEvent, playerId, EventPlayerUnit.count))
    endfunction

    function TriggerRemoveUnitTypeDecayEvent takes integer id, integer unitTypeId returns nothing
        set EventUnitType.count = EventUnitType.count - 1
        call SaveTriggerHandle(EventUnitType.decayEvent, unitTypeId, id, LoadTriggerHandle(EventUnitType.decayEvent, unitTypeId, EventUnitType.count))
    endfunction

    function TriggerRemovePlayerUnitTypeDecayEvent takes integer id, integer playerId, integer unitTypeId returns nothing
        set EventPlayerType.count[playerId] = EventPlayerType.count[playerId] - 1
        call SaveTriggerHandle(EventPlayerType.decayEvent, unitTypeId, playerId+16*id, LoadTriggerHandle(EventPlayerType.decayEvent, unitTypeId, playerId+16*EventPlayerType.count[playerId]))
    endfunction

    private function EndDecay takes nothing returns nothing
        set runTimer = GetExpiredTimer()
        set curTimer = CorpseTimer[GetHandleId(runTimer)]
        set curUnit = curTimer.decayUnit
        set runUnitType = GetUnitTypeId(curUnit.decayUnit)
        if runUnitType > 0 and not UnitAlive(curUnit.decayUnit) and not (curUnit.flag and curUnitType.flag) then
            set lastDecayedUnit = curUnit.decayUnit
            //all
            set runIndex = EventDecay.count
            loop
                exitwhen runIndex == 0
                set runIndex = runIndex - 1
                call TriggerEvaluate(EventDecay.decayEvent[runIndex])
            endloop
            //unit
            set runIndex = EventUnit.count
            set handleId = GetHandleId(curUnit.decayUnit)
            loop
                exitwhen runIndex == 0
                set runIndex = runIndex - 1
                call TriggerEvaluate(LoadTriggerHandle(EventUnit.decayEvent, handleId, runIndex))
            endloop
            call FlushChildHashtable(EventUnit.decayEvent, handleId)
            set EventUnit.count = 0
            //unit type
            set runIndex = EventUnitType.count
            loop
                exitwhen runIndex == 0
                set runIndex = runIndex - 1
                call TriggerEvaluate(LoadTriggerHandle(EventUnitType.decayEvent, runUnitType, runIndex))
            endloop
            //player unit
            set runPlayerId = GetPlayerId(GetOwningPlayer(curUnit.decayUnit))
            set runIndex = EventPlayerUnit.count
            loop
                exitwhen runIndex == 0
                set runIndex = runIndex - 1
                call TriggerEvaluate(LoadTriggerHandle(EventPlayerUnit.decayEvent, runPlayerId, runIndex))
            endloop
            //player unit type
            set runIndex = EventPlayerType.count[runPlayerId]
            loop
                exitwhen runIndex == 0
                set runIndex = runIndex - 1
                call TriggerEvaluate(LoadTriggerHandle(EventPlayerType.decayEvent, runPlayerId+16*runIndex, runIndex))
            endloop
            call RemoveUnit(lastDecayedUnit)
        endif
        set curUnit.decayTimer = 0
        set curUnit.decay = 0
        set curUnit.flag = false
        call CorpseUnit.release(curUnit.decayHandleId)
    endfunction

    function SetUnitRemainingDecay takes unit u, real time returns nothing
        if DoesDecay(u) and not UnitAlive(u) then
            call TimerStart(CorpseUnit(CorpseUnit[GetHandleId(u)]).decayTimer.decayTimer, time, false, function EndDecay)
        endif
    endfunction

    function CreateUnitCorpse takes player p, integer unitTypeId, real x, real y, real facing, real time returns unit
        set runTimer = Timer.get()
        set curTimer = CorpseTimer[GetHandleId(runTimer)]
        set curTimer.decayTimer = runTimer
        set runUnit = CreateCorpse(p, unitTypeId, x, y, facing)
        set handleId = GetHandleId(runUnit)
        set curUnit = CorpseUnit[handleId]
        set curUnit.decayUnit = runUnit
        set curTimer.decayUnit = curUnit
        set curUnit.decayTimer = curTimer
        set curUnit.decayHandleId = handleId
        if time > 0 then
            set curUnit.decay = time
            call TimerStart(runTimer, curUnit.decay, false, function EndDecay)
        elseif not curUnitType.flag then
            set curUnitType = CorpseUnitType[GetUnitTypeId(runUnit)]
            if curUnitType.decay > 0 then
                call TimerStart(runTimer, curUnitType.decay, false, function EndDecay)
            else
                call TimerStart(runTimer, UNIT_DECAY, false, function EndDecay)
            endif
        debug else
            debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Made a unit that does not decay!")
        endif
        return runUnit
    endfunction

    private function StartDecay takes nothing returns boolean
        set runUnit = GetTriggerUnit()
        if DoesDecay(runUnit) then
            set handleId = GetHandleId(runUnit)
            set curUnit = CorpseUnit[handleId]
            set curUnit.decayHandleId = handleId
            set curUnitType = CorpseUnitType[GetUnitTypeId(runUnit)]
            if not (curUnit.flag and curUnitType.flag) then
                if curUnit.decay > 0 then
                    set decayTime = curUnit.decay
                elseif curUnitType.decay > 0 then
                    set decayTime = curUnitType.decay
                else
                    set decayTime = UNIT_DECAY
                endif
                if curUnit.decayTimer == 0 then
                    set runTimer = Timer.get()
                    set curTimer = CorpseTimer[GetHandleId(runTimer)]
                    set curTimer.decayUnit = curUnit
                    set curTimer.decayTimer = runTimer
                    set curUnit.decayTimer = curTimer
                    set curUnit.decayUnit = runUnit
                endif
                call TimerStart(curUnit.decayTimer.decayTimer, decayTime, false, function EndDecay)
            endif
        endif
        return false
    endfunction

    private function Initialization takes nothing returns nothing
        local integer x = 11
        set boolFunc = Condition(function StartDecay)
        call TriggerAddCondition(decayTrig, boolFunc)
        loop
            set x = x - 1
            call TriggerRegisterPlayerUnitEvent(decayTrig, GetPlayer(x), EVENT_PLAYER_UNIT_DEATH, null)
            exitwhen x == 0
        endloop
        //! runtextmacro CorpseManager_EXTRA_PLAYERS()
    endfunction
endlibrary

JASS:
scope Test initializer Initialization
    globals
        private constant integer unitTypeId = 'hpea'
        private constant real unitTypeDecayTime = 1

        private trigger tStart = CreateTrigger()
        private event e = TriggerRegisterTimerEvent(tStart, 0, false)
        private trigger t = CreateTrigger()
        private real x
        private real y
    endglobals

    private function Test takes nothing returns boolean
        local unit u = CreateUnit(Player(0), unitTypeId, GetUnitX(GetLastDecayedUnit()), GetUnitY(GetLastDecayedUnit()), 0)
        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, GetUnitName(GetLastDecayedUnit()) + " has decayed")
        call KillUnit(u)
        set u = null
        return false
    endfunction
    
    private function Start takes nothing returns boolean
        local unit u 
        set x = GetRectCenterX(bj_mapInitialPlayableArea)
        set y = GetRectCenterY(bj_mapInitialPlayableArea)
        set u = CreateUnit(Player(0), unitTypeId, x, y, 0)
        set u = CreateUnit(Player(0), unitTypeId, x, y, 0)
        call TriggerAddCondition(t, Condition(function Test))
        call PanCameraTo(x, y)
        call SetUnitTypeDecay(unitTypeId, unitTypeDecayTime)
        call KillUnit(u)
        set u = null
        call TriggerRegisterUnitTypeDecayEvent(t, unitTypeId)
        return false
    endfunction

    private function Initialization takes nothing returns nothing
        call TriggerAddCondition(tStart, Condition(function Start))
    endfunction
endscope
 
Last edited:
Aye, but this has its own internal stuff. It needed to attach data to unit type, unit, and timer, so I just did it for all since they are all very similar ^_^, meaning I don't need the data attachment in Timer Utilities, so the GetHandleId calls would slow it down, heh ^_^.

Just needed something simple, and everyone keeps telling me to use more complicated things that do a lot more than I need =O. Recycling even has more than I want, I don't care for the small extra safety check =).
 
Level 11
Joined
Nov 4, 2007
Messages
337
Why is this approved?
This system sucks!
It doesn't have a reason to exist.
I can change the time after units decay on my own.
And there is no reason anybdoy would like to do that.

@Nestharus: If people tell you, it's too simple, it should be more complicated, it means that the system has no point and not that you shall add useless features.
 
JASS:
private constant function StillDecays takes integer unitId, integer unitTypeId returns boolean
        if unitFlagEnabled[unitId] == true and unitFlag[unitId] or unitTypeFlag[unitTypeId] then
            return false
        endif
        return true
endfunction
should be:
JASS:
private constant function StillDecays takes integer unitId, integer unitTypeId returns boolean
       return not (unitFlagEnabled[unitId] and unitFlag[unitId] or unitTypeFlag[unitTypeId]) then
    endfunction
 
Last edited:
Why is this approved?
This system sucks!
It doesn't have a reason to exist.
I can change the time after units decay on my own.
And there is no reason anybdoy would like to do that.

@Nestharus: If people tell you, it's too simple, it should be more complicated, it means that the system has no point and not that you shall add useless features.

Uh... all I can say to that is read the description because you can't... lol...

better yet, I'll paste it
Description:
What does it do-
This allows you to do things when a unit decays. The normal
UnitSuspendDecay function does not work properly as it is
capped out at 8 seconds. This one works to any amount you want.
It can also change whether or not units and unit types do or don't
decay in real time.

And as for usefulness.. I've made quite a few abilities in the past that run when a corpse decays ^_^.

and diablo, I shall fix that ^_^.
 
Level 13
Joined
Nov 22, 2006
Messages
1,260
This isn't bad, but it would be really cool if you used TimerUtils...

Also, please put the documentation in a separate trigger/code. This way people who CnP this have to take the documentation out manually.

Also, instead of using your Recycle library, you can use Risik_Dusk's GroupUtils or Vexorian's TimerUtils, depends on what handle you're using it for.

I know it's not very attractive to suddenly move to another system, but once you do it, you'll feel better. Plus, it's standard.
 
I can def move the documentation out. I had it in there as optimizers remove all the comments anyways and if people ever need to reference a function they can look thru the documentation on the system : ). The system code doesn't need to be browsed with the documentation there : D. This is especially true with systems like Spawn with around 200 methods.

Also, instead of using your Recycle library, you can use Risik_Dusk's GroupUtils or Vexorian's TimerUtils, depends on what handle you're using it for.

I totally know about GroupUtils and TimerUtils. I believe they do too much. Sometimes I may want to start a timer without data and sometimes I may want to start a timer with data. I want to use the same stacks for both but I want them both to be as fast as possible, meaning the one without data won't call GetHandleId ever and the one with data will at the right times.

So I prefer to use a simple recycler over TimerUtils. The recycler might do less and have less checks, but it runs in more scenarios =). If TimerUtils only attached data when an attachment was called and only removed when removed was called, then I'd probably use it, but it doesn't, and so I don't ; ).
 
Level 11
Joined
Nov 4, 2007
Messages
337
I totally know about GroupUtils and TimerUtils. I believe they do too much. Sometimes I may want to start a timer without data and sometimes I may want to start a timer with data. I want to use the same stacks for both but I want them both to be as fast as possible, meaning the one without data won't call GetHandleId ever and the one with data will at the right times.


Have you understood TimerUtils?
If you don't want to, you don't have to attach data.
Just use the stack (at least)
What you wrote seems just so... Stupid!
TimerUtils is far better than all of your systems.
 
Level 13
Joined
Nov 22, 2006
Messages
1,260
Uhm, GetHandleId is pretty fast anyways... how should I put this.... the difference in the speed is so small that it really doesn't matter. Nobody seems to care about the extra GetHandleId call except you.

Btw, somehow I don't think that the reason why you won't use TimerUtils is because of the speed. But that's just a guess...
 
Top