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

[System] PreventOrders

A simple script I wrote because I needed it in a spell, this simply provides a way of preventing specific orders. It also provides a CancelOrders function which can be used externally and simply clears any orders the unit has, and works in response to SpellCast or IssuedOrder events, unlike simply issuing a "stop" order. Without any further ado, here's the code:

Requires:
AutoIndex by grim001
JASS:
/***********************************************************
*                           PreventOrders                   
*                       by Element of Water                 
************************************************************
* Requirements:                                             
* - AutoIndex by grim001                                    
* - JassHelper by Vexorian (for vJass)                      
************************************************************
* What is it?                                               
*                                                           
* PreventOrders is a simple library which provides a few    
* useful functions to do with the preventing and
* cancelling of certain orders. Whenever an order is
* "prevented", the unit will carry on with its previous
* order as usual (but if it has multiple orders queued
* up, it will not continue with those).                     
*                                                           
* CancelOrders immediately cancels a unit's orders. It      
* is different to simply issuing a "stop" order, because    
* it works even in response to SpellCast or IssuedOrder     
* events. It takes just a  single unit parameter. Call      
* it like this:                                             
*                                                           
* call CancelOrders(myUnit)                                 
*                                                           
* It's that simple!                                         
*
* PreventCurrentOrder immediately stops the unit from
* performing its current order and returns it to its
* previous order. The syntax for calling it is as follows:
*
* call PreventCurrentOrder(myUnit)
*
* Simplez!
*                                                           
* PreventOrders permanently stops a unit from responding    
* to immediate, point and/or target orders. Call it like    
* this:                                                     
*                                                           
* call PreventOrders(myUnit, immediate, point, target)      
*                                                           
* immediate, point and target are booleans. These should    
* be true if you want to disable immediate, point or        
* target orders respectively. If you want to re-enable      
* a type of order, simply call the function again with      
* the desired parameters as false, and the unit will be     
* able to recieve orders again                              
*                                                           
* PreventSpecificOrder permanently stops a unit from        
* responding to a specific order, such as "move", "smart",  
* "channel", "acidbomb", etc. Call it like this:            
*                                                           
* call PreventSpecificOrder(myUnit, order, prevent)         
*                                                           
* order is the order string which determines which order    
* is prevented, and prevent is a boolean. prevent should    
* be true if you want to disable the order, and false if    
* you want to re-enable it. Also, if it is false it will    
* bypass the blocking of all orders just for this order.    
* You can call ClearSpecificOrderPrevention(myUnit, order)  
* to reset for one specific order, or call                  
* ClearAllSpecificOrderPrevention(myUnit) to reset all      
* orders for that unit.
*                                                           
* There is a variation of this function,                    
* PreventSpecificOrderById, which takes an integer instead  
* of a string for if you prefer to work with order ids.     
* You may also use ClearSpecificOrderPreventionById.        
*                                                           
* AllowNextOrder allows a unit to respond to the next order 
* it is issued, whatever that may be. Even if the order     
* would not be blocked anyway, this is cleared immediately  
* after the next order issued to the unit. The syntax to    
* call it is as follows:                                    
*                                                           
* call AllowNextOrder(myUnit)                               
*                                                           
* This function is useful for when you want to control the  
* unit through triggers, but make it unresponsive to        
* player control. The PreventOrdersHooks add-on utilises    
* this function to allow all triggered orders via hooks.    
***********************************************************/
library PreventOrders initializer Init requires AutoIndex
    private keyword Data
    
    globals
        private timer       Tim         = CreateTimer()
        private Data array  CancelStack
        private integer     CancelCount = 0
        private hashtable   Hash        = InitHashtable()
        
        private constant integer ORDER_STOP = 851972
        
        // Purely for debugging purposes, this displays when an order is prevented
        private constant boolean DISPLAY_PREVENTED_ORDERS = false
        // Set this to true to make units keep their old orders rather than simply stopping when an order is prevented
        private constant boolean RETAIN_PREVIOUS_ORDERS   = false
    endglobals
    
    private function DebugMsg takes string unitName, string orderType, string orderString returns nothing
        debug static if DISPLAY_PREVENTED_ORDERS then
        debug     call BJDebugMsg("PreventOrders: Prevented " + unitName + " from being issued a" + orderType + " " + orderString + " order")
        debug endif
    endfunction
    
    private struct Data extends array
        static constant integer IMMEDIATE = 1
        static constant integer POINT     = 2
        static constant integer TARGET    = 3
        
        
        unit u
        boolean i
        boolean p
        boolean t
        
        boolean doNext
        
        integer lastOrderType
        integer lastOrderId
        widget  lastOrderTargetW
        real    lastOrderTargetX
        real    lastOrderTargetY
        boolean cancel
        
        method lastImmediateOrder takes integer id returns nothing
            set lastOrderType = IMMEDIATE
            set lastOrderId   = id
        endmethod
        
        method lastPointOrder takes integer id, real x, real y returns nothing
            set lastOrderType    = POINT
            set lastOrderId      = id
            set lastOrderTargetX = x
            set lastOrderTargetY = y
        endmethod
        
        method lastTargetOrder takes integer id, widget target returns nothing
            set lastOrderType    = TARGET
            set lastOrderId      = id
            set lastOrderTargetW = target
        endmethod
        
        method doLastOrder takes nothing returns nothing
            if cancel then
                set doNext = true
                call IssueImmediateOrderById(u, ORDER_STOP)
            else
                if     lastOrderType == IMMEDIATE then
                    set doNext = true
                    call IssueImmediateOrderById(u, lastOrderId)
                elseif lastOrderType == POINT then
                    set doNext = true
                    call IssuePointOrderById(u, lastOrderId, lastOrderTargetX, lastOrderTargetY)
                elseif lastOrderType == TARGET then
                    set doNext = true
                    call IssueTargetOrderById(u, lastOrderId, lastOrderTargetW)
                else
                    set doNext = true
                    call IssueImmediateOrderById(u, ORDER_STOP)
                endif
            endif
        endmethod
        
        method assign takes unit whichUnit returns nothing
            set u = whichUnit
        endmethod
        
        method destroy takes nothing returns nothing
            call FlushChildHashtable(Hash, this)

            set u = null
            set i = false
            set p = false
            set t = false
            
            set doNext = false
            
            set lastOrderType = 0
            set cancel        = false
        endmethod
    endstruct
    
    private function Cancel takes nothing returns nothing
        loop
            set CancelCount = CancelCount - 1
            static if RETAIN_PREVIOUS_ORDERS then
                call CancelStack[CancelCount].doLastOrder()
            else
                set CancelStack[CancelCount].doNext = true
                call IssueImmediateOrderById(CancelStack[CancelCount].u, ORDER_STOP)
            endif
            exitwhen CancelCount == 0
        endloop
    endfunction
    
    private function PreventCurrentOrderFromData takes Data d returns nothing
        if CancelCount == 0 then
            call TimerStart(Tim, 0., false, function Cancel)
        endif
        set CancelStack[CancelCount] = d
        set CancelCount = CancelCount + 1
    endfunction
    
    function PreventCurrentOrder takes unit u returns nothing
        call PreventCurrentOrderFromData(Data[GetUnitId(u)])
    endfunction
    
    function CancelOrders takes unit u returns nothing
        local Data d = Data[GetUnitId(u)]
        static if RETAIN_PREVIOUS_ORDERS then
            set d.cancel = true
        endif
        call PreventCurrentOrderFromData(d)
    endfunction
    
    function PreventOrders takes unit u, boolean i, boolean p, boolean t returns nothing
        local Data d = Data[GetUnitId(u)]
        
        if i or p or t then
            set d.i = i
            set d.p = p
            set d.t = t
        endif
    endfunction
    
    function PreventSpecificOrderById takes unit u, integer orderId, boolean prevent returns nothing
        local Data d = Data[GetUnitId(u)]
        
        if orderId != 0 then
            if prevent then
                call SaveBoolean(Hash, d, orderId, true)
            elseif d != 0 then
                call SaveBoolean(Hash, d, orderId, false)
            endif
        endif
    endfunction
    
    function PreventSpecificOrder takes unit u, string order, boolean prevent returns nothing
        call PreventSpecificOrderById(u, OrderId(order), prevent)
    endfunction
    
    function ClearSpecificOrderPreventionById takes unit u, integer orderId returns nothing
        local Data d = Data[GetUnitId(u)]
        
        if orderId != 0 and orderId != ORDER_STOP then
            call RemoveSavedBoolean(Hash, d, orderId)
        endif
    endfunction
    
    function ClearSpecificOrderPrevention takes unit u, string order returns nothing
        call ClearSpecificOrderPreventionById(u, OrderId(order))
    endfunction
    
    function ClearAllSpecificOrderPrevention takes unit u returns nothing
        local Data d = Data[GetUnitId(u)]
        call FlushChildHashtable(Hash, d)
    endfunction
    
    function AllowNextOrder takes unit u returns nothing 
        local Data d = Data[GetUnitId(u)]
        set d.doNext = true
    endfunction
    
    private function ImmediateOrder takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local Data d = Data[GetUnitId(u)]
        local integer i = GetIssuedOrderId()
        
        if d.doNext then
            set d.doNext = false
            call d.lastImmediateOrder(i)
        else
            if d.i then
                if (not HaveSavedBoolean(Hash, d, i)) or (LoadBoolean(Hash, d, i)) then
                    call PreventCurrentOrderFromData(d)
                    debug call DebugMsg(GetUnitName(u), "n immediate", OrderId2String(i))
                else
                    static if RETAIN_PREVIOUS_ORDERS then
                        call d.lastImmediateOrder(i)
                    endif
                endif
            elseif LoadBoolean(Hash, d, i) then
                call PreventCurrentOrderFromData(d)
                debug call DebugMsg(GetUnitName(u), "n immediate", OrderId2String(i))
            else
                static if RETAIN_PREVIOUS_ORDERS then
                    call d.lastImmediateOrder(i)
                endif
            endif
        endif
        
        set u = null
        
        return false
    endfunction
    
    private function PointOrder takes nothing returns boolean
        local unit u    = GetTriggerUnit()
        local Data d    = Data[GetUnitId(u)]
        local integer i = GetIssuedOrderId()
        
        if d.doNext then
            set d.doNext = false
            call d.lastPointOrder(i, GetOrderPointX(), GetOrderPointY())
        else
            if d.p then
                if (not HaveSavedBoolean(Hash, d, i)) or (LoadBoolean(Hash, d, i)) then
                    
                    call PreventCurrentOrderFromData(d)
                    debug call DebugMsg(GetUnitName(u), " point", OrderId2String(i))
                else
                    static if RETAIN_PREVIOUS_ORDERS then
                        call d.lastPointOrder(i, GetOrderPointX(), GetOrderPointY())
                    endif
                endif
            elseif LoadBoolean(Hash, d, i) then
                call PreventCurrentOrderFromData(d)
                debug call DebugMsg(GetUnitName(u), " point", OrderId2String(i))
            else
                static if RETAIN_PREVIOUS_ORDERS then
                    call d.lastPointOrder(i, GetOrderPointX(), GetOrderPointY())
                endif
            endif
        endif
        
        set u = null
        
        return false
    endfunction
    
    private function TargetOrder takes nothing returns boolean
        local unit u    = GetTriggerUnit()
        local Data d    = Data[GetUnitId(u)]
        local integer i = GetIssuedOrderId()
        
        if d.doNext then
            set d.doNext = false
            call d.lastTargetOrder(i, GetOrderTarget())
        else
            if d.t then
                if (not HaveSavedBoolean(Hash, d, i)) or (LoadBoolean(Hash, d, i)) then
                    call PreventCurrentOrderFromData(d)
                    debug call DebugMsg(GetUnitName(u), " target", OrderId2String(i))
                else
                    static if RETAIN_PREVIOUS_ORDERS then
                        call d.lastTargetOrder(i, GetOrderTarget())
                    endif
                endif
            elseif LoadBoolean(Hash, d, i) then
                call PreventCurrentOrderFromData(d)
                debug call DebugMsg(GetUnitName(u), " target", OrderId2String(i))
            else
                static if RETAIN_PREVIOUS_ORDERS then
                    call d.lastTargetOrder(i, GetOrderTarget())
                endif
            endif
        endif
        
        set u = null
        
        return false
    endfunction
    
    private function UnitIndexed takes unit u returns nothing
        local Data d = Data[GetUnitId(u)]
        call d.assign(u)
    endfunction
    
    private function UnitDeindexed takes unit u returns nothing
        local Data d = Data[GetUnitId(u)]
        call d.destroy()
    endfunction
    
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER)
        call TriggerAddCondition(t, Condition(function ImmediateOrder))
        
        set t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
        call TriggerAddCondition(t, Condition(function PointOrder))
        
        set t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
        call TriggerAddCondition(t, Condition(function TargetOrder))
        
        call OnUnitIndexed(UnitIndexed)
        call OnUnitDeindexed(UnitDeindexed)
    endfunction
endlibrary
The following is an add-on for PreventOrders which uses hooks to make the system ignore any triggered issued orders.
JASS:
/*********************************************************
*                   PreventOrdersHooks                   *
*                  by Element of Water                   *
**********************************************************
* Requirements:                                          *
* - PreventOrders by Element of Water                    *
* - JassHelper by Vexorian (for Zinc)                    *
**********************************************************
* What is it?                                            *
*                                                        *
* PreventOrdersHooks is a simple add-on library for      *
* PreventOrders which makes every order issued by        *
* triggers bypass the system. It does this at the cost   *
* a minor efficiency hit for EVERY call in GUI, JASS,    *
* whatever, to ANY function which issues an order to a   *
* unit. Don't use it if you issue lots of orders to      *
* units and notice lag. This library is completely       *
* automatic, so you don't have to worry about calling    *
* any functions.                                         *
*********************************************************/

library PreventOrdersHooks requires PreventOrders
    globals
        private constant integer ORDER_STOP = 851972
    endglobals
    
    function IssueBuildOrder_hook takes unit u, string s, real x, real y returns nothing
        call AllowNextOrder(u)
    endfunction
    function IssueBuildOrderById_hook takes unit u, integer i, real x, real y returns nothing
        call AllowNextOrder(u)
    endfunction
    function IssueBuildOrderByIdLocBJ_hook takes unit u, integer i, location l returns nothing
        call AllowNextOrder(u)
    endfunction
    
    function IssueHauntOrderAtLocBJ_hook takes unit u, location l returns nothing
        call AllowNextOrder(u)
    endfunction
    
    function IssueImmediateOrder_hook takes unit u, string s returns nothing
        call AllowNextOrder(u)
    endfunction
    function IssueImmediateOrderById_hook takes unit u, integer i returns nothing
        if i != ORDER_STOP then
            call AllowNextOrder(u)
        endif
    endfunction
    
    function IssueInstantPointOrder_hook takes unit u, string s, real x, real y, widget w returns nothing
        call AllowNextOrder(u)
    endfunction
    function IssueInstantPointOrderById_hook takes unit u, integer i, real x, real y, widget w returns nothing
        call AllowNextOrder(u)
    endfunction
    
    function IssueInstantTargetOrder_hook takes unit u, string s, widget w, widget w2 returns nothing
        call AllowNextOrder(u)
    endfunction
    function IssueInstantTargetOrderById_hook takes unit u, integer i, widget w, widget w2 returns nothing
        call AllowNextOrder(u)
    endfunction
    
    function IssueNeutralImmediateOrder_hook takes player p, unit u, string s returns nothing
        call AllowNextOrder(u)
    endfunction
    function IssueNeutralImmediateOrderById_hook takes player p, unit u, integer i returns nothing
        call AllowNextOrder(u)
    endfunction
    
    function IssueNeutralPointOrder_hook takes player p, unit u, string s, real x, real y returns nothing
        call AllowNextOrder(u)
    endfunction
    function IssueNeutralPointOrderById_hook takes player p, unit u, integer i, real x, real y returns nothing
        call AllowNextOrder(u)
    endfunction
    
    function IssueNeutralTargetOrder_hook takes player p, unit u, string s, widget w returns nothing
        call AllowNextOrder(u)
    endfunction
    function IssueNeutralTargetOrderById_hook takes player p, unit u, integer i, widget w returns nothing
        call AllowNextOrder(u)
    endfunction
    
    function IssuePointOrderLoc_hook takes unit u, string s, location l returns nothing
        call AllowNextOrder(u)
    endfunction
    
    function IssueTargetOrder_hook takes unit u, string s, widget w returns nothing
        call AllowNextOrder(u)
    endfunction
    function IssueTargetOrderById_hook takes unit u, integer i, widget w returns nothing
        call AllowNextOrder(u)
    endfunction
    
    hook IssueBuildOrder                IssueBuildOrder_hook
    hook IssueBuildOrderById            IssueBuildOrderById_hook
    hook IssueBuildOrderByIdLocBJ       IssueBuildOrderByIdLocBJ_hook
    
    hook IssueHauntOrderAtLocBJ         IssueHauntOrderAtLocBJ_hook
    
    hook IssueImmediateOrder            IssueImmediateOrder_hook
    hook IssueImmediateOrderBJ          IssueImmediateOrder_hook
    hook IssueImmediateOrderById        IssueImmediateOrderById_hook
    
    hook IssueInstantPointOrder         IssueInstantPointOrder_hook
    hook IssueInstantPointOrderById     IssueInstantPointOrderById_hook
    
    hook IssueInstantTargetOrder        IssueInstantTargetOrder_hook
    hook IssueInstantTargetOrderById    IssueInstantTargetOrderById_hook
    
    hook IssueNeutralImmediateOrder     IssueNeutralImmediateOrder_hook
    hook IssueNeutralImmediateOrderById IssueNeutralImmediateOrderById_hook
    
    hook IssueNeutralPointOrder         IssueNeutralPointOrder_hook
    hook IssueNeutralPointOrderById     IssueNeutralPointOrderById_hook
    
    hook IssueNeutralTargetOrder        IssueNeutralTargetOrder_hook
    hook IssueNeutralTargetOrderById    IssueNeutralTargetOrderById_hook
    
    hook IssuePointOrder                IssueBuildOrder_hook
    hook IssuePointOrderById            IssueBuildOrderById_hook
    hook IssuePointOrderByIdLoc         IssueBuildOrderByIdLocBJ_hook
    hook IssuePointOrderLoc             IssuePointOrderLoc_hook
    hook IssuePointOrderLocBJ           IssuePointOrderLoc_hook
    
    hook IssueTargetDestructableOrder   IssueTargetOrder_hook
    hook IssueTargetItemOrder           IssueTargetOrder_hook
    hook IssueTargetOrder               IssueTargetOrder_hook
    hook IssueTargetOrderBJ             IssueTargetOrder_hook
    hook IssueTargetOrderById           IssueTargetOrderById_hook
    
    hook IssueTrainOrderByIdBJ          IssueImmediateOrderById_hook
    hook IssueUpgradeOrderByIdBJ        IssueImmediateOrderById_hook
endlibrary
 
Last edited:
Level 9
Joined
Nov 28, 2008
Messages
704
"It is different to simply issuing a "stop" order" is incorrect grammar.

I would recommend making a wrapper error function so as to increase readability of errors, the color tags are mildly annoying and add line length. JassHelper will inline it, so no need for the eyesore.

JASS:
    private function Cancel takes nothing returns nothing
        call IssueImmediateOrderById(U, ORDER_STOP)
    endfunction

    function CancelOrders takes unit u returns nothing
        set U = u
        call TimerStart(Tim, 0., false, function Cancel)
    endfunction

Just a curiosity, why? I never noticed that you needed to wait a split second before issuing a stop order for it to work, it's always worked for me.

Under "PointOrder", I notice random spaces between the variables names for no particular reason, unless you like the syntax that way, but under "Immediate order", it's not like that. Your globals block is also out of whack with spaces. Nitpicking though, sorry.

You are using conditions intentionally instead of actions on triggers. Is there some sort of speed boost I'm unaware of? Seems to me like it would be more inefficient than actions since it has to return something and this forces the engine to read a return value from an interpreted language into C++, as opposed to an action which returns nothing.

You are doing geneirc unit events rather than dynamically adding units into the trigger as the function to prevent their orders is called. Minor inefficiency, although I've heard people yell about dynamic triggers corrupting things and whatnot. Never happened to me though so I wouldn't worry.

I'm sure it could be useful for many things, such as an alternative to pausing a unit and forcing a players unit to do things while still selected by the player. Oh, wait, nevermind, just looked at it again and it can't do that. It would work until the user tried to do something. >.>.

Maybe you could add that functionality?
 
1. I'm a Brit. I know more than anyone from any other country what is correct grammar and what is not.

2. Will do

3. So it works directly in response to orders or cast event. If you just issue a stop order in response to an order event directly, it won't work, but my method will.

4. Meh, makes it more readable IMO

5. Actions start a new thread, conditions don't. Conditions are faster.

6. Generic unit events are completely safe and the efficiency hit is only incredibly minor.

7. I'll try. Not sure how though.
 
Level 9
Joined
Nov 28, 2008
Messages
704
One thing I completely forgot to mention: if units in a loop are mass issued orders and they are all blocked for whatever reason, or StopOrder is used multiple times with no delays, your single global timer will likely fail, as 0.0 second timers do in fact create threads and dont run instantly in the current function - which is why it works to cancel orders, apparently.

Could be wrong on that, but as far as I know, that's how it works.

Great job regardless, I can definitely see some uses for this.
 
Level 8
Joined
Oct 3, 2008
Messages
367
So it does. Blizzard seems to be breaking things left and right.

Oh, and you could use a stack instead of multiple timers or an unsafe global.

EDIT:

Now for deeper code reading.

The PointOrder function leaks a unit variable. Same with TargetOrder and UnitDeath.

You should probably put 'debug' before debug messages. More efficient that way.

Why not make Data an array and use unit ids as indices instead of the array you're using now?
 
Last edited:
Level 8
Joined
Oct 3, 2008
Messages
367
The only thing wrong I see at this point is the destroy method.

JASS:
method destroy takes nothing returns nothing
            local integer id = GetUnitId(u)

            set u = null
            set i = false
            set p = false
            set t = false

            set doNext = false

            call FlushChildHashtable(Hash, id)
endmethod

This would be better:
JASS:
method destroy takes nothing returns nothing
            call FlushChildHashtable(Hash, this)

            set u = null
            set i = false
            set p = false
            set t = false

            set doNext = false

endmethod
 
Level 8
Joined
Oct 3, 2008
Messages
367
Bah, I didn't see this before. In the PreventOrders method, the local integer j is only used once and should be inlined.

Also, what happens if I try to register Stop as an order that should be prevented?
 
Level 8
Joined
Oct 3, 2008
Messages
367
Well that's not good. You can abuse that to find an enemy that teleported away and/or turned invisible for a while. Simply order an attack, and when they disappear issue an order that is prevented. The unit will suddenly go and home in on the attacker who escaped.
 
Level 9
Joined
Nov 28, 2008
Messages
704
I would appreciate a way to be able to prevent a stop order from being issued.

When I look at this system, I think of it as a way of controlling a players unit without putting him in cinematic mode, and still allowing him to chat, and do everything he can normally - except control his one unit.

If he then issues a stop order while you are triggering his unit to move somewhere or perform an action (say, become berserk?), it adds a world of trouble for you to trigger. You have to repeatedly issue the same order, or something else, which is inefficient and something it would be easier to not deal with.

You should find a way to be able to block stop orders that dont come from this system itself. It seems like it would be simple enough.
 
Level 7
Joined
Feb 9, 2021
Messages
301
It seems like it does not work for mana shield and berserk type spells. Any way to fix this?

Also, I can stop unit orders, but cannot activate them back: "call PreventOrders(target, false, false , false)" -> Does not work
 
Top