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

Baradé's Unit Respawn System 1.0

This bundle is marked as awaiting update. A staff member has requested changes to it before it can be approved.
JASS:
// Baradé's Unit Group Respawn System 1.0
//
// Allows killed or charmed units from a group or individually to respawn after some time.
// Respawning groups are automatically determined by default from preplaced creeps next to each other owned by player neutral aggressive.
//
// Usage:
// - Copy this code into your map script or a trigger converted into code.
// - Place some creeps on the map.
//
// You can change the default configuration by changing the code in the library UnitGroupRespawnSystemConfig.
//
// Design:
//
// The unit variables of the respawns are set to null as soon as the respawn starts, not when the unit dies/changes the owner.
// This is done on purpose, so the callbacks can still access the units by index etc.
// The system does not use vJass structs but simple JASS arrays. This allows you to go through all indices and check an index is used or not.
// The system would require some kind of global list if vJass structs were used whenever you want to get all existing instances.
// The limitation of maximum instances is the maximum array size possible in Warcraft III JASS_MAX_ARRAY_SIZE.
// Even with vJass structs you would have to specify a higher limit manually.
// The system is designed to provide a simple JASS API similar to the JASS functions provided by Blizzard.
// The callbacks work like regular trigger events.
//
// API:
//
// function TriggerRegisterUnitRespawnEvent takes trigger whichTrigger returns nothing
//
// Registers an unit respawn event for the specified trigger. This means that the trigger is evaluated and executed if
// the condition is true whenever any unit is respawned by this system. You can access the triggering respawning unit
// and index from the system via functions.
//
// function TriggerRegisterUnitRespawnStartsEvent takes trigger whichTrigger returns nothing
//
// Registers an unit respawn start event for the specified trigger. This means that the trigger is evaluated and executed if
// the condition is true whenever any unit respawn timer is started by this system. You can access the previously killed or charmed unit
// and index from the system via functions.
//
// function GetTriggerRespawnUnit takes nothing returns unit
//
// Returns the triggering respawned unit from a trigger which was evaluated or executed due to a respawn event.
// Returns the triggering killed or charmed previously respawned unit if the event is a respawn starts event.
//
// function GetTriggerRespawnUnitIndex takes nothing returns integer
//
// Returns the corresponding index of the unit respawn from the respawned unit or unit for which the respawn timer has been started of the
// evaluated or executed trigger.
//
// function PreventUnitRespawn takes unit whichUnit returns nothing
//
// Can be called in a trigger which is executed on the respawn event to prevent the actual respawn. It removes the respawned unit. The respawn has to be started manually.
//
// function GetUnitRespawnUnitIndex takes unit whichUnit returns integer
//
// Returns the corresponding index of the unit respawn matching the passed unit. If there is none, it returns -1.
//
// function GetUnitRespawnGroupIndex takes unit whichUnit returns integer
//
// function GetRespawnUnitCounter takes nothing returns integer
//
// Returns the maximum number of item respawn indices. Note that not every index in between has to be valid. This function might help integer
// loops to go through all existing item respawns. Please use IsRespawnItemValid to check if an index in between 0 and the return value of
// this function is actually used.
//
// function IsRespawnUnitValid takes integer index returns boolean
//
// function IsRespawnUnitGroupValid takes integer index returns boolean
//
// function RespawnUnit takes integer index returns boolean
//
// function RespawnAllUnits takes nothing returns nothing
//
// function StartUnitRespawn takes integer index returns nothing
//
// function StartAllUnitRespawnsNotRunning takes nothing returns nothing
//
// function AddRespawnUnit takes unit whichUnit returns integer
//
// function AddRespawnUnitPool takes unitpool whichUnitPool, real x, real y returns integer
//
// function AddRespawnUnitRandomCreep takes integer level, real x, real y returns integer
//
// function RemoveRespawnUnit takes integer index returns boolean
//
// function SetRespawnUnitEnabled takes integer index, boolean enabled returns nothing
//
// function IsRespawnUnitEnabled takes integer index returns boolean
//
// function GetRespawnUnitTimer takes integer index returns timer
//
// function GetRespawnUnitType takes integer index returns integer
//
// function SetRespawnUnitTimeout takes integer index, real timeout returns nothing
//
// function GetRespawnUnitTimeout takes integer index returns real
//
// function SetRespawnUnitOwner takes integer index, player owner returns nothing
//
// function GetRespawnUnitOwner takes integer index returns player
//
// function SetRespawnUnitFacing takes integer index, real facing returns nothing
//
// function GetRespawnUnitFacing takes integer index returns real
//
// function SetRespawnUnitX takes integer index, real x returns nothing
//
// function GetRespawnUnitX takes integer index returns real
//
// function SetRespawnUnitY takes integer index, real y returns nothing
//
// function GetRespawnUnitY takes integer index returns real
//
// function SetRespawnUnitUseDyingLoc takes integer index, boolean use returns nothing
//
// function GetRespawnUnitUseDyingLoc takes integer index returns boolean
//
// function SetRespawnUnit takes integer index, unit whichUnit returns nothing
//
// function GetRespawnUnit takes integer index returns unit
//
// function SetRespawnUnitPool takes integer index, unitpool whichUnitPool returns nothing
//
// function GetRespawnUnitPool takes integer index returns unitpool
//
// function SetRespawnUnitLevel takes integer index, integer level returns nothing
//
// function GetRespawnUnitLevel takes integer index returns integer
//
// function GetRespawnUnitGroupCounter takes nothing returns integer
//
// function SetRespawnUnitGroupIndex takes integer index, integer groupIndex returns nothing
//
// function GetRespawnUnitGroupIndex takes integer index returns integer
//
// function AddRespawnUnitGroup takes nothing returns integer
//
// function AddRespawnUnitGroupFromUnit takes unit whichUnit returns integer
//
// function AddRespawnUnitGroupFromUnitPool takes unitpool whichUnitPool, integer countMembers, real x, real y, real range returns integer
//
// function AddRespawnUnitGroupFromRandomCreepLevel takes integer minCreepLevel, integer maxCreepLevel, integer countMembers, real x, real y, real range returns integer
//
// function RemoveRespawnUnitGroup takes integer index returns boolean
//
// function SetRespawnUnitGroupTimeout takes integer index, real timeout returns nothing
//
// function GetRespawnUnitGroupTimeout takes integer index returns real
//
// function SetRespawnUnitGroupEnabled takes integer index, boolean enabled returns nothing
//
// function IsRespawnUnitGroupEnabled takes integer index returns boolean
//
// function GetRespawnUnitGroupUnits takes integer index returns group
//
// function RespawnUnitGroup takes integer index returns boolean
//
// function StartUnitGroupRespawn takes integer index returns nothing
//
library UnitGroupRespawnSystemConfig

    globals
        // The default delay until a unit will be respawned.
        public constant real DEFAULT_TIMEOUT = 30.0
        // All preplaced units owned by the CREEPS_OWNER player on the map will automatically respawn if this value is true. Otherwise, you will have to add them manually.
        public constant boolean AUTO_ADD_ALL_PREPLACED_CREEPS = true
        // Creates unit group respawns from preplaced creeps next to each other if this value is true. Otherwise, it will create separate unit respawns per creep.
        public constant boolean AUTO_ADDED_GROUPS = true
        // Defines the maximum distance between preplaced creep units to belong to the same respawn group if AUTO_ADDED_GROUPS is true.
        public constant real AUTO_ADDED_GROUP_MAX_DISTANCE = 800.0
        // All players who preplaced units are added as respawning groups for.
        public constant force AUTO_ADDED_GROUP_PLAYERS = CreateForce()
        // All auto added unit respawns and respawn groups will drop random items when the whole group has been killed or charmed when this value is true. The level of the dropped item will be the maximum unit level of the group.
        // Make sure that all item types have set the field "Stats - Include As Random Choice" to the correct value in the object editor.
        public constant boolean AUTO_ADDED_DROP_RANDOM_ITEMS = true
        // Determines a unit's level by its unit type rather than the actual unit. This helps to get a lower level if the unit levels are changed manually during the game.
        public constant boolean GET_UNIT_LEVEL_BY_TYPE = true
        // Shows the eyecandy on respawning hero revivals if set to true. Otherwise, the effect is not shown.
        public constant boolean HERO_RESPAWN_DO_EYECANDY = true
        // Avoids permanent removal if too many heroes from the same player died.
        public constant boolean SET_MAX_DEATH_TIME_TO_UNITS = true
    endglobals

static if (AUTO_ADD_ALL_PREPLACED_CREEPS) then
    private function IsUnitLivingNonStructure takes nothing returns boolean
        return IsUnitAliveBJ(GetFilterUnit()) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)
    endfunction

    // Redefine this function to add different preplaced creeps.
    public function AddAllUserSpecifiedPreplacedCreeps takes group allCreeps returns nothing
        call GroupEnumUnitsOfPlayer(allCreeps, Player(PLAYER_NEUTRAL_AGGRESSIVE), Filter(function IsUnitLivingNonStructure))
    endfunction

    private module Init

        private static method onInit takes nothing returns nothing
            call ForceAddPlayer(AUTO_ADDED_GROUP_PLAYERS, Player(PLAYER_NEUTRAL_AGGRESSIVE))
        endmethod
    endmodule

    private struct S
        implement Init
    endstruct
endif

endlibrary

library UnitGroupRespawnSystem requires UnitGroupRespawnSystemConfig

globals
    constant integer UNIT_RESPAWN_TYPE_UNIT = 0
    constant integer UNIT_RESPAWN_TYPE_UNITPOOL = 1
    constant integer UNIT_RESPAWN_TYPE_RANDOM_CREEP_LEVEL = 2

    private integer respawnUnitCounter = 0
    private integer respawnUnitFreeIndex = 0
    private boolean array respawnUnitIsValid
    private integer array respawnUnitType
    private unit array respawnUnitUnit
    private integer array respawnUnitHandleId
    private integer array respawnUnitUnitTypeId
    private unitpool array respawnUnitPool
    private integer array respawnUnitRandomCreepLevel
    private player array respawnUnitOwner
    private real array respawnUnitFacing
    private real array respawnUnitX
    private real array respawnUnitY
    private boolean array respawnUnitUseDyingLoc
    private timer array respawnUnitTimer
    private real array respawnUnitTimeout
    private boolean array respawnUnitEnabled
    private integer array respawnUnitGroupIndex
    private boolean array respawnUnitReadyForRespawn

    private integer callbackRespawnTriggersCounter = 0
    private trigger array callbackRespawnTriggers

    private integer callbackRespawnStartsTriggersCounter = 0
    private trigger array callbackRespawnStartsTriggers

    private unit callbackUnit = null
    private integer callbackIndex = -1

    private integer respawnUnitGroupCounter = 0
    private integer respawnUnitGroupFreeIndex = 0
    private boolean array respawnUnitGroupIsValid
    private group array respawnUnitGroup
    private timer array respawnUnitGroupTimer
    private real array respawnUnitGroupTimeout
    private boolean array respawnUnitGroupEnabled

    private trigger unitDeathOrCharmTrigger = CreateTrigger()
    private hashtable respawnUnitHashTable = InitHashtable()

    private unit filterUnit = null
    private player filterOwner = null
endglobals

function GetTriggerRespawnUnit takes nothing returns unit
    return callbackUnit
endfunction

function GetTriggerRespawnUnitIndex takes nothing returns integer
    return callbackIndex
endfunction

function TriggerRegisterUnitRespawnEvent takes trigger whichTrigger returns nothing
    local integer index = callbackRespawnTriggersCounter
    set callbackRespawnTriggers[index] = whichTrigger
    set callbackRespawnTriggersCounter = callbackRespawnTriggersCounter + 1
endfunction

private function EvaluateAndExecuteCallbackRespawnTriggers takes integer index returns nothing
    local integer i = 0
    loop
        exitwhen (i >= callbackRespawnTriggersCounter)
        set callbackUnit = respawnUnitUnit[index]
        set callbackIndex = index
        call ConditionalTriggerExecute(callbackRespawnTriggers[i])
        set i = i + 1
    endloop
endfunction

function TriggerRegisterUnitRespawnStartsEvent takes trigger whichTrigger returns nothing
    local integer index = callbackRespawnStartsTriggersCounter
    set callbackRespawnStartsTriggers[index] = whichTrigger
    set callbackRespawnStartsTriggersCounter = callbackRespawnStartsTriggersCounter + 1
endfunction

private function EvaluateAndExecuteCallbackRespawnStartsTriggers takes integer index returns nothing
    local integer i = 0
    loop
        exitwhen (i >= callbackRespawnStartsTriggersCounter)
        set callbackUnit = respawnUnitUnit[index]
        set callbackIndex = index
        call ConditionalTriggerExecute(callbackRespawnStartsTriggers[i])
        set i = i + 1
    endloop
endfunction

private function ClearRespawnUnitIndex takes integer handleID returns nothing
    if (HaveSavedInteger(respawnUnitHashTable, handleID, 0)) then
        call FlushChildHashtable(respawnUnitHashTable, handleID)
    endif
endfunction

private function GetRespawnUnitIndexByHandleID takes integer handleID returns integer
    if (HaveSavedInteger(respawnUnitHashTable, handleID, 0)) then
        return LoadInteger(respawnUnitHashTable, handleID, 0)
    endif

    return -1
endfunction

private function GetUnitRespawnGroupIndexByHandleID takes integer handleID returns integer
    if (HaveSavedInteger(respawnUnitHashTable, handleID, 1)) then
        return LoadInteger(respawnUnitHashTable, handleID, 1)
    endif

    return -1
endfunction

function GetUnitRespawnUnitIndex takes unit whichUnit returns integer
    return GetRespawnUnitIndexByHandleID(GetHandleId(whichUnit))
endfunction

function GetUnitRespawnGroupIndex takes unit whichUnit returns integer
    return GetUnitRespawnGroupIndexByHandleID(GetHandleId(whichUnit))
endfunction

function GetRespawnUnitCounter takes nothing returns integer
    return respawnUnitCounter
endfunction

function IsRespawnUnitValid takes integer index returns boolean
    if (index < 0) then
        return false
    endif
    return respawnUnitIsValid[index]
endfunction

function IsRespawnUnitGroupValid takes integer index returns boolean
    if (index < 0) then
        return false
    endif
    return respawnUnitGroupIsValid[index]
endfunction

function RespawnUnit takes integer index returns boolean
    local boolean add = false
    local integer groupIndex = respawnUnitGroupIndex[index]
    if (not IsRespawnUnitValid(index)) then
        return false
    endif
    if (respawnUnitType[index] == UNIT_RESPAWN_TYPE_UNIT) then
        if (IsUnitType(respawnUnitUnit[index], UNIT_TYPE_HERO) and respawnUnitUnit[index] != null) then
            call ReviveHero(respawnUnitUnit[index], respawnUnitX[index], respawnUnitY[index], UnitGroupRespawnSystemConfig_HERO_RESPAWN_DO_EYECANDY)
        else
            set respawnUnitUnit[index] = CreateUnit(respawnUnitOwner[index], respawnUnitUnitTypeId[index], respawnUnitX[index], respawnUnitY[index], respawnUnitFacing[index])
            set add = true
        endif
    elseif (respawnUnitType[index] == UNIT_RESPAWN_TYPE_UNITPOOL) then
        set respawnUnitUnit[index] = PlaceRandomUnit(respawnUnitPool[index], respawnUnitOwner[index], respawnUnitX[index], respawnUnitY[index], respawnUnitFacing[index])
        set add = true
    elseif (respawnUnitType[index] == UNIT_RESPAWN_TYPE_RANDOM_CREEP_LEVEL) then
        set respawnUnitUnit[index] = CreateUnit(respawnUnitOwner[index], ChooseRandomCreep(respawnUnitRandomCreepLevel[index]), respawnUnitX[index], respawnUnitY[index], respawnUnitFacing[index])
        set add = true
    endif
    set respawnUnitReadyForRespawn[index] = false
static if (UnitGroupRespawnSystemConfig_SET_MAX_DEATH_TIME_TO_UNITS) then
    if (IsUnitType(respawnUnitUnit[index], UNIT_TYPE_HERO)) then
        call BlzSetUnitRealField(respawnUnitUnit[index], UNIT_RF_DEATH_TIME, 99999999.0)
    endif
endif
    set respawnUnitHandleId[index] = GetHandleId(respawnUnitUnit[index])
    call SaveInteger(respawnUnitHashTable, respawnUnitHandleId[index], 0, index)

    if (add and IsRespawnUnitGroupValid(groupIndex)) then
        call GroupAddUnit(respawnUnitGroup[groupIndex], respawnUnitUnit[index])
    endif

    call EvaluateAndExecuteCallbackRespawnTriggers(index)
    return true
endfunction

function RespawnAllUnits takes nothing returns nothing
    local integer i = 0
    loop
        exitwhen (i >= GetRespawnUnitCounter())
        if (IsRespawnUnitValid(i) and respawnUnitUnit[i] == null) then
            call RespawnUnit(i)
        endif
        set i = i + 1
    endloop
endfunction

function PreventUnitRespawn takes unit whichUnit returns nothing
    local integer index = GetUnitRespawnUnitIndex(whichUnit)
    if (IsRespawnUnitValid(index)) then
        if (IsUnitType(respawnUnitUnit[index], UNIT_TYPE_HERO)) then
            call KillUnit(whichUnit)
        else
            call ClearRespawnUnitIndex(respawnUnitHandleId[index])
            set respawnUnitHandleId[index] = 0
            call RemoveUnit(whichUnit)
            set whichUnit = null
        endif
    endif
endfunction

private function TimerFunctionRespawnUnit takes nothing returns nothing
    local integer index = LoadInteger(respawnUnitHashTable, GetHandleId(GetExpiredTimer()), 0)
    call RespawnUnit(index)
endfunction

function StartUnitRespawn takes integer index returns nothing
    call EvaluateAndExecuteCallbackRespawnStartsTriggers(index)

    if (respawnUnitHandleId[index] != 0) then
        call ClearRespawnUnitIndex(respawnUnitHandleId[index])
    endif
    set respawnUnitUnit[index] = null
    set respawnUnitHandleId[index] = 0
    call TimerStart(respawnUnitTimer[index], respawnUnitTimeout[index], false, function TimerFunctionRespawnUnit)
    set respawnUnitReadyForRespawn[index] = false
endfunction

function StartAllUnitRespawnsNotRunning takes nothing returns nothing
    local integer i = 0
    loop
        exitwhen (i >= GetRespawnUnitCounter())
        if (IsRespawnUnitValid(i) and respawnUnitUnit[i] == null and TimerGetElapsed(respawnUnitTimer[i]) <= 0.0) then
            call StartUnitRespawn(i)
        endif
        set i = i + 1
    endloop
endfunction

private function AddRespawnUnitDefault takes integer index, real x, real y returns nothing
    set respawnUnitIsValid[index] = true
    set respawnUnitX[index] = x
    set respawnUnitY[index] = y
    set respawnUnitFacing[index] = GetRandomDirectionDeg()
    set respawnUnitUseDyingLoc[index] = false
    set respawnUnitOwner[index] = Player(PLAYER_NEUTRAL_AGGRESSIVE)
    set respawnUnitTimer[index] = CreateTimer()
    set respawnUnitTimeout[index] = UnitGroupRespawnSystemConfig_DEFAULT_TIMEOUT
    call SaveInteger(respawnUnitHashTable, GetHandleId(respawnUnitTimer[index]), 0, index)
    set respawnUnitEnabled[index] = true
    set respawnUnitGroupIndex[index] = -1
    set respawnUnitReadyForRespawn[index] = true

    loop
        set respawnUnitFreeIndex = respawnUnitFreeIndex + 1
        exitwhen (not IsRespawnUnitValid(respawnUnitFreeIndex))
    endloop

    if (respawnUnitFreeIndex >= JASS_MAX_ARRAY_SIZE) then
        call BJDebugMsg("Warning: Reached Warcraft maximum array size for respawn units: " + I2S(respawnUnitFreeIndex))
    endif

    if (index >= respawnUnitCounter) then
        set respawnUnitCounter = index + 1
    endif
endfunction

function AddRespawnUnit takes unit whichUnit returns integer
    local integer index = respawnUnitFreeIndex
    set respawnUnitType[index] = UNIT_RESPAWN_TYPE_UNIT
    set respawnUnitUnit[index] = whichUnit
static if (UnitGroupRespawnSystemConfig_SET_MAX_DEATH_TIME_TO_UNITS) then
    if (IsUnitType(respawnUnitUnit[index], UNIT_TYPE_HERO)) then
        call BlzSetUnitRealField(respawnUnitUnit[index], UNIT_RF_DEATH_TIME, 99999999.0)
    endif
endif
    set respawnUnitHandleId[index] = GetHandleId(whichUnit)
    set respawnUnitUnitTypeId[index] = GetUnitTypeId(whichUnit)
    set respawnUnitPool[index] = null
    call AddRespawnUnitDefault(index, GetUnitX(whichUnit), GetUnitY(whichUnit))
    set respawnUnitFacing[index] = GetUnitFacing(whichUnit)
    set respawnUnitOwner[index] = GetOwningPlayer(whichUnit)
    set respawnUnitReadyForRespawn[index] = false

    call SaveInteger(respawnUnitHashTable, respawnUnitHandleId[index], 0, index)

    return index
endfunction

function AddRespawnUnitPool takes unitpool whichUnitPool, real x, real y returns integer
    local integer index = respawnUnitFreeIndex
    set respawnUnitType[index] = UNIT_RESPAWN_TYPE_UNITPOOL
    set respawnUnitUnit[index] = null
    set respawnUnitHandleId[index] = 0
    set respawnUnitUnitTypeId[index] = 0
    set respawnUnitPool[index] = whichUnitPool
    call AddRespawnUnitDefault(index, x, y)

    call RespawnUnit(index)

    return index
endfunction

function AddRespawnUnitRandomCreep takes integer level, real x, real y returns integer
    local integer index = respawnUnitFreeIndex
    set respawnUnitType[index] = UNIT_RESPAWN_TYPE_RANDOM_CREEP_LEVEL
    set respawnUnitUnit[index] = null
    set respawnUnitHandleId[index] = 0
    set respawnUnitUnitTypeId[index] = 0
    set respawnUnitPool[index] = null
    set respawnUnitRandomCreepLevel[index] = level
    call AddRespawnUnitDefault(index, x, y)

    call RespawnUnit(index)

    return index
endfunction

function RemoveRespawnUnit takes integer index returns boolean
    if (IsRespawnUnitValid(index)) then
        set respawnUnitIsValid[index] = false

        if (respawnUnitUnit[index] != null) then
            call ClearRespawnUnitIndex(GetHandleId(respawnUnitUnit[index]))
        endif

        set respawnUnitTimeout[index] = 0
        set respawnUnitType[index] = 0
        set respawnUnitUnit[index] = null
        set respawnUnitHandleId[index] = 0
        set respawnUnitUnitTypeId[index] = 0
        set respawnUnitPool[index] = null
        set respawnUnitRandomCreepLevel[index] = 0
        set respawnUnitUseDyingLoc[index] = false

        call PauseTimer(respawnUnitTimer[index])
        call FlushChildHashtable(respawnUnitHashTable, GetHandleId(respawnUnitTimer[index]))
        call DestroyTimer(respawnUnitTimer[index])

        set respawnUnitFreeIndex = index

        if (index == respawnUnitCounter - 1) then
            set respawnUnitCounter = respawnUnitCounter - 1
        endif

        return true
    endif

    return false
endfunction

function SetRespawnUnitEnabled takes integer index, boolean enabled returns nothing
    set respawnUnitEnabled[index] = enabled
endfunction

function IsRespawnUnitEnabled takes integer index returns boolean
    return respawnUnitEnabled[index]
endfunction

function GetRespawnUnitTimer takes integer index returns timer
    return respawnUnitTimer[index]
endfunction

function GetRespawnUnitType takes integer index returns integer
    return respawnUnitType[index]
endfunction

function SetRespawnUnitTimeout takes integer index, real timeout returns nothing
    set respawnUnitTimeout[index] = timeout
endfunction

function GetRespawnUnitTimeout takes integer index returns real
    return respawnUnitTimeout[index]
endfunction

function SetRespawnUnitOwner takes integer index, player owner returns nothing
    set respawnUnitOwner[index] = owner
endfunction

function GetRespawnUnitOwner takes integer index returns player
    return respawnUnitOwner[index]
endfunction
function SetRespawnUnitFacing takes integer index, real facing returns nothing
    set respawnUnitFacing[index] = facing
endfunction

function GetRespawnUnitFacing takes integer index returns real
    return respawnUnitFacing[index]
endfunction

function SetRespawnUnitX takes integer index, real x returns nothing
    set respawnUnitX[index] = x
endfunction

function GetRespawnUnitX takes integer index returns real
    return respawnUnitX[index]
endfunction

function SetRespawnUnitY takes integer index, real y returns nothing
    set respawnUnitX[index] = y
endfunction

function GetRespawnUnitY takes integer index returns real
    return respawnUnitY[index]
endfunction

function SetRespawnUnitUseDyingLoc takes integer index, boolean use returns nothing
    set respawnUnitUseDyingLoc[index] = use
endfunction

function GetRespawnUnitUseDyingLoc takes integer index returns boolean
    return respawnUnitUseDyingLoc[index]
endfunction

function SetRespawnUnit takes integer index, unit whichUnit returns nothing
    local integer groupIndex = respawnUnitGroupIndex[index]
    local boolean validRespawnUnitGroup = IsRespawnUnitGroupValid(groupIndex)
    local integer handleId = GetHandleId(whichUnit)
    if (respawnUnitUnit[index] != null) then
        if (validRespawnUnitGroup) then
            call GroupRemoveUnit(respawnUnitGroup[groupIndex], respawnUnitUnit[index])
        endif

        call ClearRespawnUnitIndex(GetHandleId(respawnUnitUnit[index]))
    endif
    set respawnUnitUnit[index] = whichUnit
    set respawnUnitHandleId[index] = handleId
    call SaveInteger(respawnUnitHashTable, handleId, 0, index)
    set respawnUnitReadyForRespawn[index] = false
    if (validRespawnUnitGroup) then
        call GroupAddUnit(respawnUnitGroup[groupIndex], whichUnit)
    endif
endfunction

function GetRespawnUnit takes integer index returns unit
    return respawnUnitUnit[index]
endfunction

function SetRespawnUnitPool takes integer index, unitpool whichUnitPool returns nothing
    set respawnUnitPool[index] = whichUnitPool
endfunction

function GetRespawnUnitPool takes integer index returns unitpool
    return respawnUnitPool[index]
endfunction

function SetRespawnUnitLevel takes integer index, integer level returns nothing
    set respawnUnitRandomCreepLevel[index] = level
endfunction

function GetRespawnUnitLevel takes integer index returns integer
    return respawnUnitRandomCreepLevel[index]
endfunction

function GetRespawnUnitGroupCounter takes nothing returns integer
    return respawnUnitGroupCounter
endfunction

function SetRespawnUnitGroupIndex takes integer index, integer groupIndex returns nothing
    if (IsRespawnUnitGroupValid(groupIndex)) then
        if (IsRespawnUnitGroupValid(respawnUnitGroupIndex[index]) and respawnUnitUnit[index] != null) then
            call GroupRemoveUnit(respawnUnitGroup[respawnUnitGroupIndex[index]], respawnUnitUnit[index])
        endif
        set respawnUnitGroupIndex[index] = groupIndex
        call GroupAddUnit(respawnUnitGroup[groupIndex], respawnUnitUnit[index])
    endif
endfunction

function GetRespawnUnitGroupIndex takes integer index returns integer
    return respawnUnitGroupIndex[index]
endfunction

function AddRespawnUnitGroup takes nothing returns integer
    local integer index = respawnUnitGroupFreeIndex
    set respawnUnitGroupIsValid[index] = true
    set respawnUnitGroup[index] = CreateGroup()
    set respawnUnitGroupTimer[index] = CreateTimer()
    set respawnUnitGroupTimeout[index] = UnitGroupRespawnSystemConfig_DEFAULT_TIMEOUT
    call SaveInteger(respawnUnitHashTable, GetHandleId(respawnUnitGroupTimer[index]), 0, index)
    set respawnUnitGroupEnabled[index] = true

    loop
        set respawnUnitGroupFreeIndex = respawnUnitGroupFreeIndex + 1
        exitwhen (not IsRespawnUnitGroupValid(respawnUnitGroupFreeIndex))
    endloop

    if (respawnUnitGroupFreeIndex >= JASS_MAX_ARRAY_SIZE) then
        call BJDebugMsg("Warning: Reached Warcraft maximum array size for respawn units: " + I2S(respawnUnitGroupFreeIndex))
    endif

    if (index >= respawnUnitGroupCounter) then
        set respawnUnitGroupCounter = index + 1
    endif

    return index
endfunction

private function IsLivingUnitWithRespawn takes nothing returns boolean
    local unit currentUnit = GetFilterUnit()
    local player currentOwner = GetOwningPlayer(filterUnit)
    local integer respawnUnitIndex = GetUnitRespawnUnitIndex(currentUnit)
    local boolean result = currentUnit != filterUnit and IsUnitAliveBJ(currentUnit) and not IsUnitType(currentUnit, UNIT_TYPE_STRUCTURE) and (currentOwner == filterOwner or IsPlayerInForce(currentOwner, UnitGroupRespawnSystemConfig_AUTO_ADDED_GROUP_PLAYERS)) and IsRespawnUnitValid(respawnUnitIndex) and IsRespawnUnitGroupValid(GetRespawnUnitGroupIndex(respawnUnitIndex))
    set currentUnit = null
    set currentOwner = null
    return result
endfunction

function AddRespawnUnitGroupFromUnit takes unit whichUnit returns integer
    local group allNearbyUnitsFromTheSameOwner = CreateGroup()
    local unit first = null
    local integer groupIndex = -1
    local integer index = -1
    set filterUnit = whichUnit
    set filterOwner = GetOwningPlayer(whichUnit)
    call GroupEnumUnitsInRange(allNearbyUnitsFromTheSameOwner, GetUnitX(whichUnit), GetUnitY(whichUnit), UnitGroupRespawnSystemConfig_AUTO_ADDED_GROUP_MAX_DISTANCE, Filter(function IsLivingUnitWithRespawn))
    set filterUnit = null
    set filterOwner = null
    set first = FirstOfGroup(allNearbyUnitsFromTheSameOwner)
    call GroupClear(allNearbyUnitsFromTheSameOwner)
    call DestroyGroup(allNearbyUnitsFromTheSameOwner)
    set allNearbyUnitsFromTheSameOwner = null
    if (first != null) then
        set groupIndex = GetRespawnUnitGroupIndex(GetUnitRespawnUnitIndex(first))
        set first = null
    else
        set groupIndex = AddRespawnUnitGroup()
    endif
    set index = AddRespawnUnit(whichUnit)
    call SetRespawnUnitGroupIndex(index, groupIndex)
    return groupIndex
endfunction

private function PolarProjectionX takes real x, real dist, real angle returns real
    return x + dist * Cos(angle * bj_DEGTORAD)
endfunction

private function PolarProjectionY takes real y, real dist, real angle returns real
    return y + dist * Sin(angle * bj_DEGTORAD)
endfunction

private function GetRandomLocInRange takes real x, real y, real range returns location
    local real dist = GetRandomReal(0.0, range)
    local real angle = GetRandomDirectionDeg()
    return Location(PolarProjectionX(x, dist, angle), PolarProjectionY(y, dist, angle))
endfunction

function AddRespawnUnitGroupFromUnitPool takes unitpool whichUnitPool, integer countMembers, real x, real y, real range returns integer
    local integer groupIndex = AddRespawnUnitGroup()
    local location whichLocation = null
    local integer i = 0
    loop
        exitwhen (i >= countMembers)
        set whichLocation = GetRandomLocInRange(x, y, range)
        call SetRespawnUnitGroupIndex(AddRespawnUnitPool(whichUnitPool, GetLocationY(whichLocation), GetLocationY(whichLocation)), groupIndex)
        call RemoveLocation(whichLocation)
        set whichLocation = null
        set i = i + 1
    endloop
    return groupIndex
endfunction

function AddRespawnUnitGroupFromRandomCreepLevel takes integer minCreepLevel, integer maxCreepLevel, integer countMembers, real x, real y, real range returns integer
    local integer groupIndex = AddRespawnUnitGroup()
    local location whichLocation = null
    local integer i = 0
    loop
        exitwhen (i >= countMembers)
        set whichLocation = GetRandomLocInRange(x, y, range)
        call SetRespawnUnitGroupIndex(AddRespawnUnitRandomCreep(GetRandomInt(minCreepLevel, maxCreepLevel), GetLocationY(whichLocation), GetLocationY(whichLocation)), groupIndex)
        call RemoveLocation(whichLocation)
        set whichLocation = null
        set i = i + 1
    endloop
    return groupIndex
endfunction

function RemoveRespawnUnitGroup takes integer index returns boolean
    if (IsRespawnUnitValid(index)) then
        set respawnUnitGroupIsValid[index] = false
        set respawnUnitGroupEnabled[index] = false

        if (respawnUnitGroup[index] != null) then
            call GroupClear(respawnUnitGroup[index])
            call DestroyGroup(respawnUnitGroup[index])
            set respawnUnitGroup[index] = null
        endif

        call PauseTimer(respawnUnitGroupTimer[index])
        call FlushChildHashtable(respawnUnitHashTable, GetHandleId(respawnUnitGroupTimer[index]))
        call DestroyTimer(respawnUnitGroupTimer[index])

        set respawnUnitFreeIndex = index

        if (index == respawnUnitCounter - 1) then
            set respawnUnitCounter = respawnUnitCounter - 1
        endif

        return true
    endif

    return false
endfunction

function SetRespawnUnitGroupTimeout takes integer index, real timeout returns nothing
    set respawnUnitGroupTimeout[index] = timeout
endfunction

function GetRespawnUnitGroupTimeout takes integer index returns real
    return respawnUnitGroupTimeout[index]
endfunction

function SetRespawnUnitGroupEnabled takes integer index, boolean enabled returns nothing
    set respawnUnitGroupEnabled[index] = enabled
endfunction

function IsRespawnUnitGroupEnabled takes integer index returns boolean
    return respawnUnitGroupEnabled[index]
endfunction

function GetRespawnUnitGroupUnits takes integer index returns group
    return respawnUnitGroup[index]
endfunction

function RespawnUnitGroup takes integer index returns boolean
    local integer i = 0
    if (not IsRespawnUnitGroupValid(index)) then
        return false
    endif
    // We do not know the members anymore since we have removed them from the group. Hence, we need to find all matching respawns.
    set i = 0
    loop
        exitwhen (i == GetRespawnUnitCounter())
        if (IsRespawnUnitValid(i) and GetRespawnUnitGroupIndex(i) == index) then
            call RespawnUnit(i)
        endif
        set i = i + 1
    endloop

    return true
endfunction

private function TimerFunctionRespawnUnitGroup takes nothing returns nothing
    local integer index = LoadInteger(respawnUnitHashTable, GetHandleId(GetExpiredTimer()), 0)
    call RespawnUnitGroup(index)
endfunction

function StartUnitGroupRespawn takes integer index returns nothing
    local unit member = null
    local integer memberIndex = -1
    local integer i = 0
    loop
        exitwhen (i == BlzGroupGetSize(respawnUnitGroup[index]))
        set member = BlzGroupUnitAt(respawnUnitGroup[index], i)
        set memberIndex = GetRespawnUnitIndexByHandleID(GetHandleId(member))
        set respawnUnitReadyForRespawn[memberIndex] = false
        set member = null
        call EvaluateAndExecuteCallbackRespawnStartsTriggers(memberIndex)
        set i = i + 1
    endloop

    // Cleanup since we do not know how long the unit will decay etc.
    set i = 0
    loop
        exitwhen (i == BlzGroupGetSize(respawnUnitGroup[index]))
        set member = BlzGroupUnitAt(respawnUnitGroup[index], i)
        set memberIndex = GetRespawnUnitIndexByHandleID(GetHandleId(member))

        if (not IsUnitType(member, UNIT_TYPE_HERO)) then
            if (respawnUnitHandleId[memberIndex] != 0) then
                call ClearRespawnUnitIndex(respawnUnitHandleId[memberIndex])
            endif
            set respawnUnitUnit[memberIndex] = null
            set respawnUnitHandleId[memberIndex] = 0
        endif

        set member = null
        set i = i + 1
    endloop

    call GroupClear(respawnUnitGroup[index])

    call TimerStart(respawnUnitGroupTimer[index], respawnUnitGroupTimeout[index], false, function TimerFunctionRespawnUnitGroup)
endfunction

private function TriggerConditionRespawnUnit takes nothing returns boolean
    local integer index = GetUnitRespawnUnitIndex(GetTriggerUnit())
    return IsRespawnUnitValid(index) and IsRespawnUnitEnabled(index)
endfunction

static if (UnitGroupRespawnSystemConfig_GET_UNIT_LEVEL_BY_TYPE) then
private function GetUnitLevelByType takes integer unitTypeId returns integer
    local unit dummy = CreateUnit(Player(PLAYER_NEUTRAL_AGGRESSIVE), unitTypeId, 0.0, 0.0, 0.0)
    local integer result = BlzGetUnitIntegerField(dummy , UNIT_IF_LEVEL)
    call RemoveUnit(dummy)
    set dummy = null
    return result
endfunction
endif

private function GetMaxUnitLevelFromGroup takes group whichGroup returns integer
    local integer maxLevel = 0
    local integer unitLevel = 0
    local integer i = 0
    loop
        exitwhen (i == BlzGroupGetSize(whichGroup))
static if (UnitGroupRespawnSystemConfig_GET_UNIT_LEVEL_BY_TYPE) then
        set unitLevel = GetUnitLevelByType(GetUnitTypeId(BlzGroupUnitAt(whichGroup, i)))
else
        set unitLevel = GetUnitLevel(BlzGroupUnitAt(whichGroup, i))
endif
        set maxLevel = IMaxBJ(unitLevel, maxLevel)
        set i = i + 1
    endloop
    return maxLevel
endfunction

private function IsUnitGroupReadyForRespawnEnum takes nothing returns nothing
    local integer index = GetRespawnUnitIndexByHandleID(GetHandleId(GetEnumUnit()))
    if not respawnUnitReadyForRespawn[index]  then
        set bj_isUnitGroupDeadResult = false
    endif
endfunction

private function IsUnitGroupReadyForRespawn takes group g returns boolean
    set bj_isUnitGroupDeadResult = true
    call ForGroup(g, function IsUnitGroupReadyForRespawnEnum)
    return bj_isUnitGroupDeadResult
endfunction

private function TriggerActionRespawnUnit takes nothing returns nothing
    local unit triggerUnit = GetTriggerUnit()
    local integer index = GetUnitRespawnUnitIndex(triggerUnit)
    local integer groupIndex = GetRespawnUnitGroupIndex(index)

    if (GetRespawnUnitUseDyingLoc(index)) then
        call SetRespawnUnitFacing(index, GetUnitFacing(triggerUnit))
        call SetRespawnUnitX(index, GetUnitX(triggerUnit))
        call SetRespawnUnitY(index, GetUnitY(triggerUnit))
    endif

    set respawnUnitReadyForRespawn[index] = true

    if (IsRespawnUnitGroupValid(groupIndex) and IsRespawnUnitGroupEnabled(groupIndex)) then
        if (IsUnitGroupReadyForRespawn(GetRespawnUnitGroupUnits(groupIndex))) then
static if (UnitGroupRespawnSystemConfig_AUTO_ADDED_DROP_RANDOM_ITEMS) then
            call UnitDropItem(triggerUnit, ChooseRandomItem(GetMaxUnitLevelFromGroup(GetRespawnUnitGroupUnits(groupIndex))))
endif
            call StartUnitGroupRespawn(groupIndex)
        endif
    else
        call StartUnitRespawn(index)
    endif
    set triggerUnit = null
endfunction

private function AddRespawnUnitGroupFromEnumUnit takes nothing returns nothing
static if (UnitGroupRespawnSystemConfig_AUTO_ADDED_GROUPS) then
    call AddRespawnUnitGroupFromUnit(GetEnumUnit())
else
    call AddRespawnUnit(GetEnumUnit())
endif
endfunction

static if (UnitGroupRespawnSystemConfig_AUTO_ADD_ALL_PREPLACED_CREEPS) then
private function AddAllPreplacedCreeps takes nothing returns nothing
    local group allCreeps = CreateGroup()
    call UnitGroupRespawnSystemConfig_AddAllUserSpecifiedPreplacedCreeps(allCreeps)
    call ForGroup(allCreeps, function AddRespawnUnitGroupFromEnumUnit)
    call GroupClear(allCreeps)
    call DestroyGroup(allCreeps)
    set allCreeps = null
endfunction
endif

private module Init

    private static method onInit takes nothing returns nothing
        call TriggerRegisterAnyUnitEventBJ(unitDeathOrCharmTrigger, EVENT_PLAYER_UNIT_DEATH)
        call TriggerRegisterAnyUnitEventBJ(unitDeathOrCharmTrigger, EVENT_PLAYER_UNIT_CHANGE_OWNER)
        call TriggerAddCondition(unitDeathOrCharmTrigger, Condition(function TriggerConditionRespawnUnit))
        call TriggerAddAction(unitDeathOrCharmTrigger, function TriggerActionRespawnUnit)

static if (UnitGroupRespawnSystemConfig_AUTO_ADD_ALL_PREPLACED_CREEPS) then
        call AddAllPreplacedCreeps()
endif
    endmethod
endmodule

private struct S
    implement Init
endstruct

private function RemoveUnitCleanup takes unit whichUnit returns nothing
    local integer handleID = GetHandleId(whichUnit)
    call ClearRespawnUnitIndex(handleID)
endfunction

hook RemoveUnit RemoveUnitCleanup

endlibrary
Contents

Baradé's Unit Respawn System 1.0 (Map)

Reviews
Wrda
There's a bug: killing Murloc Sorceress group (Murloc Sorceress, Murloc Huntsman x2, Murloc Nightcrawler) once works, it revives and displays the names in the Respawn Starts Event trigger (Unit Respawn Starts). When killing the group again, and you...

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,870
There's a bug: killing Murloc Sorceress group (Murloc Sorceress, Murloc Huntsman x2, Murloc Nightcrawler) once works, it revives and displays the names in the Respawn Starts Event trigger (Unit Respawn Starts). When killing the group again, and you only need to kill Murloc Huntsman x2 and Murloc Nightcrawler for the trigger to fire, even though Murloc Sorceress is alive. Weirdly enough, if you kill the hero, it will revive along with the rest of the units from the group.
When respawning all units after killing a standalone unit or a unit group creep, it not only attempts to respawn them, but also does create the units and appears to overwrite and combine (or some sort of duplication) with the already respawned units via RespawnAllUnits function.

Don't null function parameters, as you're doing in functions like PreventUnitRespawn, they do absolutely nothing good.
IsUnitAlive can cause problems, just likeIsUnitType(unit, UNIT_TYPE_DEAD). The native from common.ai UnitAlive is better. Declare it somewhere on top as native UnitAlive takes unit unitId returns boolean
And should add a warning to the user to comment the line out if he already has declared this function anywhere else (or if he got an error indicating this). If you don't want to use UnitAlive, which is the safest bet, you can use GetWidgetLife(unit > 0.405 and GetUnitTypeId(unit) !=0. This should work in 99% of the cases.

Why are you creating a "dummy" unit to get its level by unit type? You're already using a hashtable, would make more sense if you used it to store the general unit type's level, if it hadn't been stored before. Creating dummy units just for this purpose doesn't look like it sustains for itself.

AUTO_ADDED_GROUP_PLAYERS variable isn't configurable, yet it's mixed wih the other variables, it should be somewhat separated and clear to the user that it's not part of the configuration.

There's also missing documentation on the rest of the API, even the most straightforward ones should have something, for consistency. I would really shorten GetTriggerRespawnUnit to GetRespawningUnit and GetTriggerRespawnUnitIndex to GetRespawningUnitIndex

Also, the required triggers should be organized separately from others that are simply for demo purposes, so the user doesn't have to check unnecessary things.

Not Critical, but could improve: Sometimes I see some small problems on indention in functions such as TriggerActionRespawnUnit and GetMaxUnitLevelFromGroup. Mentioning the static ifs.
I see no reason for creating locations since you're already using VJASS, specially when you're going to use GetLocationX and Y such as in GetRandomLocInRange

Optional: A bit odd to me how you're using a module within a struct to make initialization for your library. Libraries have their own way to be initialized. You can simply have library UnitGroupRespawnSystemConfig initializer YourInit and then private function YourInit takes nothing returns nothing as your init function. Same goes for the other library.

You should include the link to the resource on top of the script, for any further updates.
It's quite a nice system, looks better than some, and an alternate to others.
Still needs some fixes.

Edit: Eh...I didn't realise this would make a double post.
 
Last edited:
Level 25
Joined
Feb 2, 2006
Messages
1,683
thx for the review.

I will try to fix the bug. Noticed it in my own map now :(

I can cache the unit levels in a hashtable.

I might get rid of AUTO_ADDED_GROUP_PLAYERS. It seems to be used only for filtering units with a respawn which can be done with IsRespawnUnitValid anyway.

The weird initialization has been copied from other systems due to another review: Baradé's Item Respawn System 1.1
I have been using it since this review.
 
Top