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

[Snippet] CreepRespawn (Unit Recycling)

Allows you to automatically respawn units from where they originated at (as opposed to where they died).

Requires
  1. TimerUtils
  2. UnitDex
  3. (Optional) ReviveUnit

System Code

JASS:
library CreepRespawn initializer onInit uses TimerUtils, UnitDex, optional ReviveUnit
/***************************************************************
*
*   v1.0.4, by TriggerHappy
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*
*   CreepRespawn revives units once they die, at the place they originated
*   at. It also supports reviving the units which prevents a leak that would 
*   otherwise be irremovable.
*
*   ReviveUnit: -http://www.hiveworkshop.com/forums/jass-resources-412/snippet-reviveunit-186696/
*
*   Configuration
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 
*/
    globals
        // You may revive the unit instead of creating a new handle
        // because units cause an irremovable leak. If we ressurect the
        // unit it can prevent this leak
        private constant boolean REVIVE_CREEP  = true // (Requires the ReviveUnit library)
        
        // Removes the corpse upon re-creating the creep.
        private constant boolean REMOVE_CORPSE = true
    endglobals
    
    private constant function REVIVE_TIME takes unit u returns real
        return 30.
    endfunction
    
    private constant function UNIT_FILTER takes unit u returns boolean
        return (not IsUnitType(u, UNIT_TYPE_HERO))/*
        */ and (not IsUnitType(u, UNIT_TYPE_STRUCTURE))/*
        */ and (GetOwningPlayer(u) == Player(PLAYER_NEUTRAL_AGGRESSIVE))
    endfunction
/*
*   Don't edit below this line
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 
*/
    globals
        private real array X
        private real array Y
        private real array FACING
        private player array PLAYER
        private integer array ID
    endglobals
    
    private function OnRevive takes nothing returns nothing
        local timer   t = GetExpiredTimer()
        local integer i = GetTimerData(t)
        
        // Check if we should revive the unit to prevent a leak
        static if (REVIVE_CREEP and LIBRARY_ReviveUnit) then
            // if it fails, the function will continue
            if (ReviveUnit(GetUnitById(i))) then
                call SetUnitX(GetUnitById(i), X[i])
                call SetUnitY(GetUnitById(i), Y[i])
                return
            endif
        endif
        
        static if (REMOVE_CORPSE) then
            call RemoveUnit(GetUnitById(i))
        endif
            
        call CreateUnit(PLAYER[i], ID[i], X[i], Y[i], FACING[i])

        call ReleaseTimer(t)
        
        set t = null
    endfunction
    
    private function OnDeath takes nothing returns boolean
        local unit u = GetFilterUnit()
        local integer i
        
        if (IsUnitIndexed(u) and UNIT_FILTER(u)) then
            set i = GetUnitId(u)
            
            static if (LIBRARY_ReviveUnit) then
                call SetUnitFacing(u, FACING[i])
            endif
            
            call TimerStart(NewTimerEx(i), REVIVE_TIME(u), false, function OnRevive)
        endif
        
        set u = null
        return false
    endfunction
    
    private function OnIndex takes nothing returns boolean
        local player owner = GetOwningPlayer(GetIndexedUnit())
        
        // Check if the dying unit passes the filters
        if (UNIT_FILTER(GetIndexedUnit())) then
        
            // Assign the unit data
            set X[GetIndexedUnitId()]       = GetUnitX(GetIndexedUnit())
            set Y[GetIndexedUnitId()]       = GetUnitY(GetIndexedUnit())
            set FACING[GetIndexedUnitId()]  = GetUnitFacing(GetIndexedUnit())
            set ID[GetIndexedUnitId()]      = GetUnitTypeId(GetIndexedUnit())
            set PLAYER[GetIndexedUnitId()]  = owner
        endif
        
        return false
    endfunction

    private function onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
        call TriggerAddCondition(t, Filter(function OnDeath))
        
        call RegisterUnitIndexEvent(Filter(function OnIndex), EVENT_UNIT_INDEX)
    endfunction

endlibrary
Changelog

Code:
v1.0.4
- UNIT_FILTER checked on death.
- Shortened code.

v1.0.3
- Units position now resets if the unit is revived.
- REMOVE_CORPSE now functions properly.
- Updated to comply with the newest version of ReviveUnit.

v1.0.2
- PlayerFilter removed.
- All constant functions have been renamed to all caps.
- REMOVE_CORPSE will remove the old unit before after it's replacement.
- REVIVE_CREEP will revive the unit to prevent memory leaking. This also means the ReviveUnit library is required for this to be enabled.

v1.0.1
- UnitFilter implemented.
- Filtered structures and heroes by default.
 
Last edited:
You might add similar feature which I was using within some maps - allow pushing different revive time for different creep types.

This is why the constant function exists.

private constant function GetReviveTime takes unit u returns real

So you can do things like this:

JASS:
private constant function GetReviveTime takes unit u returns real
    local integer id = GetUnitTypeId(u)

    if (id == 'hfoo') then
        return 15.
    elseif (id == 'hpea') then
        return 30.
    endif

    return 5.
endfunction
or you could make it more manageable by using a hashtable (or Table).

JASS:
// elsewhere
hashtable Hashtable=InitHashtable()
key UnitReviveTime

// load from hash
private constant function GetReviveTime takes unit u returns real
    return LoadReal(Hashtable, UnitReviveTime, GetHandleId(u))
endfunction
 
Last edited:
idk why do people keep re-creating new units to re-spawn 'em while they can use resurrection ability easily?

I initially had Purge's ReviveUnit as an optional requirement, however it didn't make sense to me to revive the creeps.

I might reconsider it because of the irreversible leaks that units make.
 
It might be neat to have an optional boolean to clean-up the corpse (a.k.a. remove the old unit) once the new one respawns. I've played some games like Imagica where the respawn is really fast, and then I just end up with a huge pile of corpses (which doesn't make sense if I'm killing just 3 or so units that keep respawning). I guess it is also a religious issue (does the creep keep his soul once he is respawned?), but it would be neat to have the option.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
however it didn't make sense to me to revive the creeps.
I'm not sure if that re-creating units is causing trouble, but by reviving using resurrection you have some advantages,
1. perhaps it certain situation, the creep has an ability that you wont remove until certain condition. but you wont remove that ability when the creep dies. so on revive, users do not need to re-add that ability (in certain situation, this "re-add" could be troublesome, so there is the advantage of using resurrection).
2. it saves memory (speculation)
3. useful for certain other systems like unitindexer (don't need to recycle the index on each dying unit), or other systems that affect whole global unit.
4. perhaps many more
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
To be honest i think reviving units is more prone to causing trouble than recreating. The first thing is that reviving units doesnt fire the usual events, this means other systems might not function correctly. Another thing to keep in mind is that all references to the old unit will point to the new one. This can be handy if used consciously, but it also means that if the user forgot to remove any effects from the old unit they will stay on the new one. Recreating a unit kind of resets it in many ways, which is a helpful mechanism to suppress malfunction.
 
To be honest i think reviving units is more prone to causing trouble than recreating. The first thing is that reviving units doesnt fire the usual events, this means other systems might not function correctly. Another thing to keep in mind is that all references to the old unit will point to the new one. This can be handy if used consciously, but it also means that if the user forgot to remove any effects from the old unit they will stay on the new one. Recreating a unit kind of resets it in many ways, which is a helpful mechanism to suppress malfunction.

REVIVE_CREEP=false

I may consider removing it, however like I said units leak 4(k?)b so this is a nice way to prevent it considering the creeps will probably be re-spawning all-game.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
The returned boolean of the ReviveUnit library is set by this function.
set success = IssueImmediateOrderById(reviver, 852094)

Now resurrection is not a target order and should return true no matter what number of units are revived. It's just an assumption of mine. I haven't tested it ingame.
 
The returned boolean of the ReviveUnit library is set by this function.
set success = IssueImmediateOrderById(reviver, 852094)

Now resurrection is not a target order and should return true no matter what number of units are revived. It's just an assumption of mine. I haven't tested it ingame.

AFAIK Resurrection us unable to cast if there are no units to revive, thus failing.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Stays very map specific, as revived ( respawned ) units do not fire desired onIndex events.
Does what it is made for, is simple and readable. Code looks clean and good to me.

Sure turns out to be useful in maps with high consumtion of repetitively used unit types.

Personally I would prefer SetUnitPosition over SetUnitX/Y ... just for eycandy.
SetUnitX/Y might stack units over each other and create an ugly moving effect once they
get their first move order.

UnitDex is not approved thus I can't approve a resource based on it.
Apart from that, I think CreepRespawn is ready to be part of the jass section resources.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
at this point, almost noone uses the new version. The code is complete bitch to get hands at, cause he migrated it to github, it has like 15 dependencies, which is laughful, and the resulting code is in thousands of lines, so all speed you get by the added complexity is burried in the fact that Jass VM is not the fastest of things.

All this to index few hundred units, which you can do a lot simpler, probably even with hooks(I will trade thousands of lines of code with trigger evaluations any day).

Even Iceman in his GetUnitScale system uses his old unit indexer, which goes to show something
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I think that from the 50% using a unit indexing system at all, 99% of these just want their
units to have a unique id, which can be used with arrays ( structs ).
onIndex and deindex are mostly important for creating/destroying unit event related
triggers or proper cleanup.

The new version of UnitIndexer has so much functionallity, which only few ( one person ) will every use.
 
The unit owner might change in between the unit being indexed and it's death.
It might be saver to assign the value directly onDeath.
The code shares actually the same goal as this: [vJASS] - Unit Recycler
I'm not sure if both is needed. How ever I personaly prefer to have a stack over a forced timed reviving. What do you, or other members think?
 
Top