• 🏆 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 Idle Workers System 1.0

  • Like
Reactions: deepstrasz
This is a very simple system which detects idle workers per player periodically.
The idle workers can be manually refreshed and retrieved as unit groups.
The detection can also be done automatically by a timer which can be paused and resumed.
It might not be 100% accurate with a timer, so if you only use it at certain locations, just refresh it manually before.
As far as I know there is no event for idle workers, so it has to be done this way.

For custom race UIs it can be useful to detect idle workers.
It can also be useful to show the number of idle workers of allies, in some kind of statistics or to add some kind of reminder notification etc.
It has no dependencies except for vJass support.
The example map shows the number of idle workers in a leaderboard.

I have read somewhere that the "stand down" order 852113 also leads to idle workers although I only experienced the "none" order 0 as the idle one.

Let me know if this is a bad approach or how to improve it.
It would be nice to have a standard system for this.

Code:
JASS:
library IdleWorkersSystem initializer Init

/**
 * Baradé's Idle Workers System 1.0
 *
 * Allows detecting idle workers for every player. This can be useful for custom UI systems.
 * There is no idle order event. Hence, it can be checked periodically or on request.
 * Note that idle workers which have been revived by "Animate Dead" won't be counted as idle workers
 * in the standard button and neither by this system by default.
 * Besides, transported workers are not included by the standard button.
 * You can customize these settings by changing the constants' values.
 *
 * API:
 *
 * function GetIdleWorkers takes player whichPlayer returns group
 *
 * Returns all idle workers for the given player.
 *
 * function GetIdleWorkersSize takes player whichPlayer returns integer
 *
 * Returns the number of all idle workers for the given player.
 *
 * function IsUnitIdleWorker takes unit whichUnit returns boolean
 *
 * Returns if the given unit is an idle worker of its owner.
 *
 * function IsOrderIdle takes integer orderId returns boolean
 *
 * Returns if the given order is an idle order.
 *
 * function IsUnitAnimateDead takes unit whichUnit returns boolean
 *
 * Returns if the given unit is a target of "Animate Dead" abilities.
 *
 * function RefreshIdleWorkers takes player whichPlayer returns nothing
 *
 * Manually refreshes the idle workers for the given player.
 *
 * function RefreshAllIdleWorkers takes nothing returns nothing
 *
 * Manually refreshes the idle workers for all players.
 *
 * function PauseIdleWorkerDetection takes nothing returns nothing
 *
 * Pauses the idle worker detection.
 *
 * function ResumeIdleWorkerDetection takes nothing returns nothing
 *
 * Resumes the idle worker detection.
 *
 * function StartIdleWorkerDetection takes nothing returns nothing
 *
 * Starts the timer for idle worker detection.
 */

native UnitAlive takes unit id returns boolean
 
globals
    // Automatically starts the order detection timer in the beginning of the game if this value is true.
    public constant boolean USE_ORDER_DETECTION_TIMER = false
    // The interval in which the order detection timer will update the idle worker groups.
    public constant real ORDER_DETECTION_TIMEOUT = 0.03
    public constant integer ORDER_NONE = 0
    public constant integer ORDER_HOLD_POSITION = 851993
    // Does not included idle workers which have been revived with "Animate Dead" abilities if this value is true.
    // This is the standard behaviour of Warcraft.
    public constant boolean EXCLUDE_ANIMATE_DEAD_TARGETS = true
    // Does not included idle workers which are loaded in transports if this value is true.
    // This is the standard behaviour of Warcraft.
    public constant boolean EXCLUDE_LOADED_TARGETS = true
    // Removes idle workers from the idle worker groups automatically when they are removed from the game.
    public constant boolean HOOK_REMOVE_UNIT = true

    private timer orderDetectionTimer = CreateTimer()
    private group array idleWorkers
endglobals

function IsUnitAnimateDead takes unit whichUnit returns boolean
    return (UnitHasBuffBJ(whichUnit, 'BUan') or UnitHasBuffBJ(whichUnit, 'BUad'))
endfunction

function IsOrderIdle takes integer orderId returns boolean
    return orderId == ORDER_NONE or orderId == ORDER_HOLD_POSITION
endfunction

private function FilterIsIdleLivingWorker takes nothing returns boolean
    local unit filterUnit = GetFilterUnit()
    local boolean result = IsUnitType(filterUnit, UNIT_TYPE_PEON) and UnitAlive(filterUnit) and IsOrderIdle(GetUnitCurrentOrder(filterUnit))
static if (EXCLUDE_ANIMATE_DEAD_TARGETS) then
    if (result) then
        set result = not IsUnitAnimateDead(filterUnit)
    endif
endif
static if (EXCLUDE_LOADED_TARGETS) then
    if (result) then
        set result = not IsUnitLoaded(filterUnit)
    endif
endif
    set filterUnit = null
    return result
endfunction

function RefreshIdleWorkers takes player whichPlayer returns nothing
    local integer playerId = GetPlayerId(whichPlayer)
    call GroupClear(idleWorkers[playerId])
    call GroupEnumUnitsOfPlayer(idleWorkers[playerId], whichPlayer, Filter(function FilterIsIdleLivingWorker))
endfunction

function RefreshAllIdleWorkers takes nothing returns nothing
    local integer i = 0
    loop
        call RefreshIdleWorkers(Player(i))
        set i = i + 1
        exitwhen (i >= bj_MAX_PLAYERS)
    endloop
endfunction

function GetIdleWorkers takes player whichPlayer returns group
    return idleWorkers[GetPlayerId(whichPlayer)]
endfunction

function GetIdleWorkersSize takes player whichPlayer returns integer
    return BlzGroupGetSize(idleWorkers[GetPlayerId(whichPlayer)])
endfunction

function IsUnitIdleWorker takes unit whichUnit returns boolean
    return IsUnitInGroup(whichUnit, idleWorkers[GetPlayerId(GetOwningPlayer(whichUnit))])
endfunction

function PauseIdleWorkerDetection takes nothing returns nothing
    call PauseTimer(orderDetectionTimer)
endfunction

function ResumeIdleWorkerDetection takes nothing returns nothing
    call ResumeTimer(orderDetectionTimer)
endfunction

function StartIdleWorkerDetection takes nothing returns nothing
    call TimerStart(orderDetectionTimer, ORDER_DETECTION_TIMEOUT, true, function RefreshAllIdleWorkers)
endfunction

private function Init takes nothing returns nothing
    local integer i = 0
    loop
        set idleWorkers[i] = CreateGroup()
        set i = i + 1
        exitwhen (i >= bj_MAX_PLAYERS)
    endloop
static if (USE_ORDER_DETECTION_TIMER) then
    call StartIdleWorkerDetection()
endif
endfunction

private function RemoveUnitHook takes unit whichUnit returns nothing
static if (HOOK_REMOVE_UNIT) then
    if (IsUnitIdleWorker(whichUnit)) then
        call GroupRemoveUnit(idleWorkers[GetPlayerId(GetOwningPlayer(whichUnit))], whichUnit)
    endif
endif
endfunction

static if (HOOK_REMOVE_UNIT) then
hook RemoveUnit RemoveUnitHook
endif

endlibrary
Contents

Baradé's Idle Workers System 1.0 (Map)

Reviews
Wrda
FilterIsIdleLivingWorker can be improved by caching GetFilterUnit() into a local variable. Also, UnitAlive (from common.ai), is the safest bet to detect alive/dead units. private function FilterIsIdleLivingWorker takes nothing returns boolean...
Level 25
Joined
Feb 2, 2006
Messages
1,689
Update (but still 1.0):
  • Targets by Animate Dead are not included by default (Warcraft's behavior).
  • Targets loaded in transports are not included by default (Warcraft's behavior).
  • Disable the timer by default for better performance and instead manually refresh the groups in the leaderboard timer of the example map which makes more sense.
  • Add functions RefreshAllIdleWorkers, StartIdleWorkerDetection and IsUnitAnimateDead.
  • Made constants public.
  • Add constants USE_ORDER_DETECTION_TIMER, EXCLUDE_ANIMATE_DEAD_TARGETS and EXCLUDE_LOADED_TARGETS.
  • Add some documentation.
 

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,873
FilterIsIdleLivingWorker can be improved by caching GetFilterUnit() into a local variable. Also, UnitAlive (from common.ai), is the safest bet to detect alive/dead units.
JASS:
private function FilterIsIdleLivingWorker takes nothing returns boolean
    //local unit u
    local boolean result = IsUnitType(GetFilterUnit(), UNIT_TYPE_PEON) and IsUnitAliveBJ(GetFilterUnit()) and IsOrderIdle(GetUnitCurrentOrder(GetFilterUnit()))
static if (EXCLUDE_ANIMATE_DEAD_TARGETS) then
    if (result) then
        set result = not IsUnitAnimateDead(GetFilterUnit())
    endif
endif
static if (EXCLUDE_LOADED_TARGETS) then
    if (result) then
        set result = not IsUnitLoaded(GetFilterUnit())
    endif
endif
    return result
endfunction

In CopyGroup function you can use BlzGroupAddGroupFast instead, it's a new native and it's faster. I'm not sure in which version you are, nor which patch this native started to be available in, but it's worth a try. Sadly, the local variable group is a reference leak (it's not an object leak), similar how it was said in a resource. You'd have to use a global group variable for this part.

I have read somewhere that the "stand down" order 852113 also leads to idle workers although I only experienced the "none" order 0 as the idle one.
What exactly is the difference between ORDER_NONE and ORDER_STAND_DOWN? They appear to be the same thing for me. Can you explain your reasoning?
Stop order is an instant order so it will never work as "current order" unless you are within a trigger that has an order event.

Definetely useful for some derived melee maps, custom races and the likes of it.
The issues are very small, hence

Approved
 
Level 25
Joined
Feb 2, 2006
Messages
1,689
Updated the system. I wasn't sure about the orders but I removed stand down and stop now (which was from a previous try with order detection triggers which does not work since the order none does not trigger anything). Removed the CopyGroup functionality. The user can create a copy himself. Storing the filtered unit in a local variable now and using UnitAlive.
 
Top