library ProperBoneDecaySuspension initializer PBDSInit /* v1.2.1
********************************************************************************
* *
* This system allows users to properly suspend the bone decay of units, *
* and resume it at will. More specific info can be found further down. *
* *
* Author: J2Krauser *
* *
* v1.0.0: 06.09.2020 *
* v1.1.0: 08.11.2020 *
* v1.2.0: 04.12.2020 *
* v1.2.1: 29.04.2021 *
* *
********************************************************************************
* *
* [] REQUIREMENTS *
* *
* 1. As I scripted (and saved) it using the latest version of *
* Warcraft, and editor at the time (1.32.10 - 1.32k), I recommend *
* using the system on at least these. However, it should work on *
* older patches just as well as long as one doesn't go too far back, *
* therefore this is more of an advice than a real requirement. *
* 2. Nothing. No unit indexer dependency, or anything of the sort. *
* I found this important to point out. *
* *
********************************************************************************
* *
* [] HOW TO IMPLEMENT *
* *
* 1. Copy the folder called "PBDS". *
* 2. Paste it into your map. *
* 3. Read the HOW TO USE section. *
* 4. Read the VARIABLES section. *
* 5. Read the FUNCTIONS section. *
* 6. Read the NOTES section. *
* *
********************************************************************************
* *
* [] HOW TO USE *
* *
* After having copied the system into your map, perform the quick *
* setup below. Only two things are necessary if you have modified *
* the gameplays constants in your map for flesh and/or bone decay: *
* 1. Set FLESH_DECAY to "Decay Time (sec) - Flesh" in your map. *
* 2. Set BONE_DECAY to "Decay Time (sec) - Bones" in your map. *
* 3. Set BLIZZARD_STYLE_CARGO to "false" if you want fully accurate *
* decay timers for units dropped from cargo holders. *
* *
* Additionally, you could fiddle with PERIOD, but I recommend not *
* to touch it. At this point everything should just work, the system *
* should just do its thing. *
* *
* There is only one public function you have to use called *
* PBDSUnitSuspendDecay. It's fully explained further down, but the *
* gist of it is: *
* If you want to suspend a unit's bone decay: *
* call PBDSUnitSuspendDecay(unit, true) *
* If you want to resume it: *
* call PBDSUnitSuspendDecay(unit, false) *
* *
* *
********************************************************************************
* *
* [] SETUP *
* */
globals
private constant real PERIOD = 0.5
private constant real FLESH_DECAY = 2.00
private constant real BONE_DECAY = 88.00
private constant boolean BLIZZARD_STYLE_CARGO = true
endglobals
/*******************************************************************************
* *
* [] VARIABLES *
* *
* REAL *
* 1. PERIOD: How often the the periodic trigger runs which does the *
* heavy lifting of this system. Can be changed if someone really *
* wants to, but I recommend it stays at its default 0.5 set by me. *
* *
* REAL *
* 2. FLESH_DECAY: In the editor: Advanced -> Gameplay Constants... *
* Here, find "Decay Time (sec) - Flesh", and this variable should *
* hold the same value one finds there. *
* *
* REAL *
* 3. BONE_DECAY: In the editor: Advanced -> Gameplay Constants... *
* Here, find "Decay Time (sec) - Bones", and this variable should *
* hold the same value one finds there. *
* *
* BOOLEAN *
* 4. BLIZZARD_STYLE_CARGO: Corpses picked up by a cargo holder, then *
* put back down restart their decay timer if this is set to "true". *
* This is the default WC3 behaviour. *
* *
* <<[**]>> *
* Starting here, the rest are set up by my script, and do not have *
* to be touched manually. *
* <<[**]>> *
* *
* *
* HASHTABLE *
* 5. pbdsDecayingUnitsHash: The main data structure the system uses *
* to function. *
* It's structured in the following way: *
* [KEY]: UNIT HANDLE INTEGER *
* [0]: BONE DECAY REAL *
* [1]: SUSPENDED BOOLEAN *
* [2]: IS CARGO BOOLEAN *
* *
* In other words, the key is a unit. *
* 0 holds its current bone decay timer, counting down. Basically the *
* amount of seconds left till it's removed from the game, and *
* disappears. 1 is a boolean that's true if the bone decay is *
* suspended at the moment, which means the bone decay timer at 0 is *
* not progressing; it's false if the unit is decaying, and will *
* eventually get removed. 2 signals if the unit is being held as *
* cargo. *
* *
* UNIT GROUP *
* 6. pbdsDecayingUnitsUG: Contains every unit that's in the *
* hashtable. The bottom line is, these are units which are in the *
* process of bone decay. They are eligible to be suspended. *
* *
* TRIGGER *
* 7. trgUnitDeath: The trigger that fires for every unit death. If *
* the unit is decayable, it'll be eligible for bone decay *
* suspension, therefore it gets stored in pbdsDecayingUnitsHash and *
* pbdsDecayingUnitsUG. *
* *
* TRIGGER *
* 8. trgPeriodic: Periodically executed as long as the unit group is *
* not empty. Counts down the bone decay timers, removes decayed *
* units. *
* *
********************************************************************************
* *
* [] FUNCTIONS *
* *
* function PBDSUnitSuspendDecay *
* takes unit u, boolean suspend *
* returns nothing *
* u: The unit to suspend or resume bone decay for. *
* suspend: If true, suspends the decay. If false, resumes it. *
* HOW IT WORKS: *
* 1. Check if the unit is in pbdsDecayingUnitsUG. If not, do *
* nothing. *
* 2. If "suspend" is true: *
* 1. If the boolean at index 1 for this unit in *
* pbdsDecayingUnitsHash is false, set it to true, then call *
* native UnitSuspendDecay. *
* 2. Call native SetUnitTimeScale. This native is used to *
* to set the unit's animation speed to 0% (0.00) which *
* prevents its corpse from disappearing over time. *
* 3. If "suspend" is false: *
* 1. If the boolean at index 1 for this unit in *
* pbdsDecayingUnitsHash is true, set it to false. *
* 2. native UnitSuspendDecay isn't necessary to be called *
* again here with false to resume the decay, as the system *
* will just remove the unit anyway once it decayed. *
* *
********************************************************************************
* *
* [] NOTES *
* *
* 1. I tried using SetUnitTimeScale to speed up the bone decay *
* animation instead of manually removing the corpse once the timer *
* is up. This doesn't work. I don't know the exact functionality of *
* the game in the background, but it seems to me as if it's fully *
* hardcoded once the decay is resumed how long it will take for the *
* corpse to disappear, and changing animation speed has no effect on *
* it whatsoever. Why is this important to mention? Because corpses *
* will immediately disappear instead of quickly fading away. This is *
* normal with this system. *
* *
* 2. Why is the system necessary? Wouldn't it work to just call *
* the natives UnitSuspendDecay, and SetUnitTimeScale appropriately, *
* then call it a day? No, it would not work. Let me explain how *
* UnitSuspendDecay works to show why this system is needed. *
* Basically, once the decay is suspended, the corpse will still *
* disappear over time. This is why SetUnitTimeScale to 0.00 is used *
* so that the animation is halted. Bone decay animations also have *
* a fixed length much like every other, therefore just suspending *
* the decay would keep the unit around (I know this is a bit hard *
* to grasp for people who aren't that into these things.), but its *
* corpse would be gone. You could still raise the unit, or resurrect *
* it, the corpse would just not be there. Halting the animation *
* using SetUnitTimeScale is what keeps the corpse around. *
* *
* So? What's the issue with just calling UnitSuspendDecay again with *
* false, then SetUnitTimeScale with 1.00? Well, this is where the *
* way it works comes into the picture. You see, if you do this, it *
* does NOT resume anything. You might think that would be the *
* expected behaviour, but it actually starts the unit's bone decay *
* all over again. You notice this once you have a few units which *
* didn't all die exactly at the same moment, then you suspend their *
* bone decay just to resume it again. Their corpses will all fade *
* away at the exact same moment, exactly after the amount of seconds *
* you have "Decay Time (sec) - Bones" set to in gameplay constants. *
* *
* That's why this system is needed if you want to actually resume *
* bone decay instead of restarting it at its full duration. *
* *
********************************************************************************/
globals
private hashtable pbdsDecayingUnitsHash = InitHashtable()
private group pbdsDecayingUnitsUG = CreateGroup()
private trigger trgUnitDeath = CreateTrigger()
private trigger trgPeriodic = CreateTrigger()
endglobals
private function TrgUnitDeathActions takes nothing returns nothing // On any unit's death.
local unit thisUnit = GetTriggerUnit()
local integer unitHandleId
if (BlzGetUnitBooleanField(thisUnit, UNIT_BF_DECAYABLE)) then // If the unit's death type has "Does decay" in there,
set unitHandleId = GetHandleId(thisUnit)
call TriggerSleepAction(BlzGetUnitRealField(thisUnit, UNIT_RF_DEATH_TIME) + FLESH_DECAY + 0.03) // wait out its "Art - Death Time (seconds)" + FLESH_DECAY + a tiny amount to make sure it's in bone decay,
call SaveReal(pbdsDecayingUnitsHash, unitHandleId, 0, BONE_DECAY) // save it in the hashtable
call SaveBoolean(pbdsDecayingUnitsHash, unitHandleId, 1, false) // with the boolean at index 1 set to false by default as its decay is not suspended automatically,
call GroupAddUnit(pbdsDecayingUnitsUG, thisUnit) // then add it to pbdsDecayingUnitsUG.
call EnableTrigger(trgPeriodic) // Finally, start the periodic trigger in case the unit group was empty beforehand.
endif // Note: Death Time is the amount of seconds from the unit's death till it starts decaying.
// Note: Decaying starts with flesh decay, after which bone decay sets in.
set thisUnit = null
endfunction
private function TrgPeriodicActions takes nothing returns nothing // Runs every PERIOD seconds if pbdsDecayingUnitsUG is not empty.
local integer i = 0
local group tempUG = CreateGroup() // A temporary group used to get rid of phantom units in pbdsDecayingUnitsUG.
local unit enumUnit
local integer enumUnitHandleId
local real remaining = 0.00
call BlzGroupAddGroupFast(pbdsDecayingUnitsUG, tempUG) // Copy the contents of the original group into the temporary one,
call GroupClear(pbdsDecayingUnitsUG) // then clear out the original. The temporary will be worked with.
set enumUnit = BlzGroupUnitAt(tempUG, i) // enumUnit is the variable used to cycle through the group's units.
loop
exitwhen i >= BlzGroupGetSize(tempUG)
set enumUnitHandleId = GetHandleId(enumUnit)
if ((not IsUnitType(enumUnit, UNIT_TYPE_DEAD)) and (GetUnitTypeId(enumUnit) != 0)) then // If the unit is alive, for example because it got revived in some way,
call SetUnitTimeScale(enumUnit, 1.00) // simply remove it from the hashtable, and proceed to the next iteration of the loop.
call FlushChildHashtable(pbdsDecayingUnitsHash, enumUnitHandleId)
else
if (IsUnitLoaded(enumUnit)) then // If the corpse is currently held as cargo,
if (not LoadBoolean(pbdsDecayingUnitsHash, enumUnitHandleId, 2)) then // but during the previous iteration, it wasn't,
call SaveBoolean(pbdsDecayingUnitsHash, enumUnitHandleId, 2, true) // mark it as cargo in the hashtable.
endif
else // If it's not held as cargo,
if (LoadBoolean(pbdsDecayingUnitsHash, enumUnitHandleId, 2)) then // but during the previous iteration, it was (which means it was just dropped out of the cargo holder),
call SaveBoolean(pbdsDecayingUnitsHash, enumUnitHandleId, 2, false) // unmark it as cargo in the hashtable.
if (BLIZZARD_STYLE_CARGO) then // If we're utilizing default WC3 cargo behaviour,
call SaveReal(pbdsDecayingUnitsHash, enumUnitHandleId, 0, BONE_DECAY) // reset its decay timer.
endif
if (LoadBoolean(pbdsDecayingUnitsHash, enumUnitHandleId, 1)) then // If its decay is currently suspended,
call UnitSuspendDecay(enumUnit, true) // suspend it, and stop its animation again, since WC3 automatically restarts it when this occurs.
call SetUnitTimeScale(enumUnit, 0.00)
endif
endif
if (not LoadBoolean(pbdsDecayingUnitsHash, enumUnitHandleId, 1)) then // If the boolean at index 1 for this unit is false, its decay is not suspended,
set remaining = LoadReal(pbdsDecayingUnitsHash, enumUnitHandleId, 0) - PERIOD // therefore keep the countdown going.
if (remaining > 0.00) then // If the the timer hasn't expired yet,
call SaveReal(pbdsDecayingUnitsHash, enumUnitHandleId, 0, remaining) // keep going.
else
call FlushChildHashtable(pbdsDecayingUnitsHash, enumUnitHandleId) // Otherwise, remove the unit from the hashtable, then remove the unit itself.
call RemoveUnit(enumUnit) // This is where the corpse disappears.
endif
endif
endif
if (GetUnitTypeId(enumUnit) != 0) then // If the unit is not removed yet, its unit-type is not zero. If it's removed, it's a phantom unit.
call GroupAddUnit(pbdsDecayingUnitsUG, enumUnit) // Only existing units (technically, corpses) are copied back into the original group.
endif
endif
set i = i + 1
set enumUnit = BlzGroupUnitAt(tempUG, i)
endloop
call DestroyGroup(tempUG)
set tempUG = null
if (BlzGroupGetSize(pbdsDecayingUnitsUG) < 1) then // Disable this trigger if there are no decaying corpses currently.
call DisableTrigger(GetTriggeringTrigger())
return
endif
endfunction
private function PBDSInit takes nothing returns nothing
call TriggerRegisterAnyUnitEventBJ(trgUnitDeath, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddAction(trgUnitDeath, function TrgUnitDeathActions)
call DisableTrigger(trgPeriodic)
call TriggerRegisterTimerEvent(trgPeriodic, PERIOD, true)
call TriggerAddAction(trgPeriodic, function TrgPeriodicActions)
endfunction
/*******************************************************************************
* *
* [] PUBLIC *
* *
********************************************************************************/
function PBDSUnitSuspendDecay takes unit u, boolean suspend returns nothing // Suspends bone decay of "u" if "suspend" is true, resumes it if false.
local integer unitHandleId
if (IsUnitInGroup(u, pbdsDecayingUnitsUG)) then
set unitHandleId = GetHandleId(u)
if (suspend) then
if (not LoadBoolean(pbdsDecayingUnitsHash, unitHandleId, 1)) then
call SaveBoolean(pbdsDecayingUnitsHash, unitHandleId, 1, true)
call UnitSuspendDecay(u, true)
call SetUnitTimeScale(u, 0.00)
endif
else
if (LoadBoolean(pbdsDecayingUnitsHash, unitHandleId, 1)) then
call SaveBoolean(pbdsDecayingUnitsHash, unitHandleId, 1, false)
endif
endif
endif
endfunction
endlibrary