library LastOrder initializer Init
//******************************************************************************
//* BY: Rising_Dusk
//*
//* LastOrder is a library that was designed to allow interfacing with the last
//* N orders any unit on your map has received. This library was also designed
//* to be used as a means to reissue lost orders to a unit either after
//* preventing a spell cast or in many other situations.
//*
//* There are two configuration constants for you to play with in using this
//* script. ORDERS_TO_HOLD is basically the size of the game's memory for each
//* individual unit. The larger the number is, the further back you can retrace
//* a unit's order list. Setting this value to 3 suffices for all actual
//* mechanics purposes. Raise it only if you have a specific application where
//* you need more. Lowering it to 2 covers most cases, but may miss a few, and
//* lowering it to 1 prevents you from adequately canceling spells and properly
//* reissuing previous orders. I recommend leaving it alone at 3. The MAX_ORDERS
//* constant is the number of orders that the system holds over all units at a
//* given time. If you are worried about running out, go ahead and increase it,
//* but be aware that it will use big arrays (which are slower) if you use a
//* number over 8191. Don't lower it under 8191, there's no reason to. Don't
//* change the non-private constants, there's no reason to.
//*
//* function GetPastOrder takes unit u, integer whichOrder returns order
//* function GetPastOrderId takes unit u, integer whichOrder returns integer
//* function GetPastOrderString takes unit u, integer whichOrder returns string
//* function GetPastOrderType takes unit u, integer whichOrder returns integer
//* function GetPastOrderX takes unit u, integer whichOrder returns real
//* function GetPastOrderY takes unit u, integer whichOrder returns real
//* function GetPastOrderTarget takes unit u, integer whichOrder returns widget
//*
//* The above API is the main list of functions the user can use to interface
//* with past orders. If you want the immediate last order for a player, use 1
//* for the whichOrder integer. The 'order' returned by GetPastOrder is a struct
//* that contains all information of an issued order. If you want to interface
//* directly with the struct and skip all of the interfacing wrappers, you have
//* access to them via the following commands:
//*
//* [unit] .u The unit being ordered.
//* [integer] .id The order id of the past order.
//* [integer] .typ The type of the past order. (See constants)
//* [boolean] .fin A flag indicating whether the order was finished.
//* [widget] .tar The target widget of the past order.
//* [real] .x The x coordinate of the target point of the order.
//* [real] .y The y coordinate of the target point of the order.
//*
//* There is also a sizable API for backwards compatibility with older versions
//* of the library. This API assumes that you're talking about a specific past
//* order, either the lastmost order or the second lastmost. In most cases,
//* these are still useful because they remove an argument from the call.
//*
//* function GetLastOrder takes unit u returns order
//* function GetLastOrderId takes unit u returns integer
//* function GetLastOrderString takes unit u returns string
//* function GetLastOrderType takes unit u returns integer
//* function GetLastOrderX takes unit u returns real
//* function GetLastOrderY takes unit u returns real
//* function GetLastOrderTarget takes unit u returns widget
//* function IsLastOrderFinished takes unit u returns boolean
//*
//* Besides being able to get information about all of the past orders a unit
//* has been issued, the most useful part of this system is actually reissuing
//* those orders as a means to fix lost orders or intercept and prevent spell
//* casting. The following API is then available to the user:
//*
//* function IssuePastOrder takes unit u, integer whichOrder returns boolean
//* function IssueLastOrder takes unit u returns boolean
//* function IssueSecondLastOrder takes unit u returns boolean
//* function AbortOrder takes unit u returns boolean
//*
//* If you want to reissue a past order for a given unit, IssuePastOrder is the
//* function that you'll want to use on a unit. To issue the last or second last
//* orders, there are functions for those as well. (They mostly exist for
//* backwards compatibility) AbortOrder is a means for anyone using this script
//* to stop a unit from running any given order that they happen to have at any
//* moment. AbortOrder is used in conjunction with a supplementary library,
//* AbortSpell, to seamlessly replicate WC3's spell error messages.
//*
//* function IssueArbitraryOrder takes unit u, order o returns boolean
//*
//* IssueArbitraryOrder is special in that it ignores many of the normal checks
//* and automatically forces a unit to take on a specific order. This can be
//* convenient if you want to make one unit take on another unit's order, for
//* instance. Be forewarned that this overwrites whatever the unit was doing and
//* issues the order no matter what, so use only as needed.
//*
//* If you have any further questions regarding LastOrder or how to use it, feel
//* free to visit [url]www.wc3c.net[/url] and ask questions there. This library should only
//* ever be released at WC3C and at no other site. Please give credits if this
//* library finds its way into your maps, and otherwise thanks for reading!
//*
//* Enjoy!
//*
globals
//Order type variables
constant integer ORDER_TYPE_TARGET = 1
constant integer ORDER_TYPE_POINT = 2
constant integer ORDER_TYPE_IMMEDIATE = 3
//System constants
private constant integer ORDERS_TO_HOLD = 3 //How many past orders the
//system holds for each unit.
//Should be at least 2.
private constant integer MAX_ORDERS = 8191 //The max number of orders
//the system can maintain.
//Going over 8191 uses big
//arrays, which are slower
//than normal arrays.
endglobals
globals
private hashtable ht = InitHashtable()
endglobals
struct order[MAX_ORDERS]
unit u
integer id
integer typ
boolean fin
widget tar
real x
real y
static method create takes unit ordered, integer ordid, integer ordtyp, widget target, real ordx, real ordy returns order
local order o = order.allocate()
local integer i = ORDERS_TO_HOLD
local integer hid = GetHandleId(ordered)
set o.u = ordered
set o.id = ordid
set o.typ = ordtyp
set o.fin = false
set o.tar = target
set o.x = ordx
set o.y = ordy
//Handle stored orders in the hashtable
loop
//We hold up to the constant ORDERS_TO_HOLD in the table
exitwhen i == 1
//Moves the N-1th order to the Nth slot, etc. except storing new last order
if HaveSavedInteger(ht, hid, i-1) then
if i == ORDERS_TO_HOLD and HaveSavedInteger(ht, hid, i) then
//Destroy lastmost order struct
call order.destroy(order(LoadInteger(ht, hid, i)))
endif
//Can only do this if the N-1th order exists
call SaveInteger(ht, hid, i, LoadInteger(ht, hid, i-1))
endif
set i = i - 1
endloop
//Store the new order to the hashtable as 1th last order
call SaveInteger(ht, hid, 1, integer(o))
return o
endmethod
endstruct
//******************************************************************************
//! textmacro LastOrderDebug takes ORDER, RETURN
debug if $ORDER$ > ORDERS_TO_HOLD then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Order out of range")
debug return $RETURN$
debug endif
debug if not HaveSavedInteger(ht, GetHandleId(u), $ORDER$) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: The "+I2S($ORDER$)+"th order doesn't exist")
debug return $RETURN$
debug endif
//! endtextmacro
function GetPastOrder takes unit u, integer whichOrder returns order
//! runtextmacro LastOrderDebug("whichOrder", "0")
return order(LoadInteger(ht, GetHandleId(u), whichOrder))
endfunction
function GetPastOrderId takes unit u, integer whichOrder returns integer
//! runtextmacro LastOrderDebug("whichOrder", "0")
return order(LoadInteger(ht, GetHandleId(u), whichOrder)).id
endfunction
function GetPastOrderString takes unit u, integer whichOrder returns string
//! runtextmacro LastOrderDebug("whichOrder", "\"\"")
return OrderId2String(order(LoadInteger(ht, GetHandleId(u), whichOrder)).id)
endfunction
function GetPastOrderType takes unit u, integer whichOrder returns integer
//! runtextmacro LastOrderDebug("whichOrder", "0")
return order(LoadInteger(ht, GetHandleId(u), whichOrder)).typ
endfunction
function GetPastOrderX takes unit u, integer whichOrder returns real
//! runtextmacro LastOrderDebug("whichOrder", "0.")
return order(LoadInteger(ht, GetHandleId(u), whichOrder)).x
endfunction
function GetPastOrderY takes unit u, integer whichOrder returns real
//! runtextmacro LastOrderDebug("whichOrder", "0.")
return order(LoadInteger(ht, GetHandleId(u), whichOrder)).y
endfunction
function GetPastOrderTarget takes unit u, integer whichOrder returns widget
//! runtextmacro LastOrderDebug("whichOrder", "null")
return order(LoadInteger(ht, GetHandleId(u), whichOrder)).tar
endfunction
//******************************************************************************
function GetLastOrder takes unit u returns order
return GetPastOrder(u, 1)
endfunction
function GetLastOrderId takes unit u returns integer
return GetPastOrderId(u, 1)
endfunction
function GetLastOrderString takes unit u returns string
return GetPastOrderString(u, 1)
endfunction
function GetLastOrderType takes unit u returns integer
return GetPastOrderType(u, 1)
endfunction
function GetLastOrderX takes unit u returns real
return GetPastOrderX(u, 1)
endfunction
function GetLastOrderY takes unit u returns real
return GetPastOrderY(u, 1)
endfunction
function GetLastOrderTarget takes unit u returns widget
return GetPastOrderTarget(u, 1)
endfunction
function IsLastOrderFinished takes unit u returns boolean
//! runtextmacro LastOrderDebug("1", "false")
return GetUnitCurrentOrder(u) == 0 or order(LoadInteger(ht, GetHandleId(u), 1)).fin
endfunction
//******************************************************************************
private function OrderFilter takes unit u, integer id returns boolean
//* Excludes specific orders or unit types from registering with the system
//*
//* 851972: stop
//* Stop is excluded from the system because it is the order that
//* tells a unit to do nothing. It should be ignored by the system.
//*
//* 851971: smart
//* 851986: move
//* 851983: attack
//* 851984: attackground
//* 851990: patrol
//* 851993: holdposition
//* These are the UI orders that are passed to the system.
//*
//* 851973: stunned
//* This order is issued when a unit is stunned onto the stunner
//* It's ignored by the system, since you'd never want to reissue it
//*
//* >= 852055, <= 852762
//* These are all spell IDs from defend to incineratearrowoff with
//* a bit of leeway at the ends for orders with no strings.
//*
return id == 851971 or id == 851986 or id == 851983 or id == 851984 or id == 851990 or id == 851993 or (id >= 852055 and id <= 852762)
endfunction
private function IssuePastOrderFilter takes unit u, integer whichOrder returns boolean
//* Some criteria for whether or not a unit's last order should be given
//*
//* INSTANT type orders are excluded because generally, reissuing an instant
//* order doesn't make sense. You can remove that check below if you'd like,
//* though.
//*
//* The Type check is really just to ensure that no spell recursion can
//* occur with IssueLastOrder. The problem with intercepting the spell cast
//* event is that it happens after the order is 'caught' and registered to
//* this system. Therefore, to just IssueLastOrder tells it to recast the
//* spell! That's a problem, so we need a method to eliminate it.
//*
return GetUnitTypeId(u) != 0 and not IsUnitType(u, UNIT_TYPE_DEAD) and GetPastOrderType(u, whichOrder) != 0 and GetPastOrderType(u, whichOrder) != ORDER_TYPE_IMMEDIATE
endfunction
//******************************************************************************
function IssuePastOrder takes unit u, integer whichOrder returns boolean
local order o = GetPastOrder(u, whichOrder)
if IssuePastOrderFilter(u, whichOrder) and not o.fin then
if o.typ == ORDER_TYPE_TARGET then
return IssueTargetOrderById(u, o.id, o.tar)
elseif o.typ == ORDER_TYPE_POINT then
if o.id == 851971 then
//This adjusts for a bug in the point order's boolean return
//when issuing a smart order
call IssuePointOrderById(u, o.id, o.x, o.y)
return true
else
return IssuePointOrderById(u, o.id, o.x, o.y)
endif
elseif o.typ == ORDER_TYPE_IMMEDIATE then
return IssueImmediateOrderById(u, o.id)
endif
endif
return false
endfunction
function IssueLastOrder takes unit u returns boolean
return IssuePastOrder(u, 1)
endfunction
function IssueSecondLastOrder takes unit u returns boolean
return IssuePastOrder(u, 2)
endfunction
function IssueArbitraryOrder takes unit u, order o returns boolean
if o.typ == ORDER_TYPE_TARGET then
return IssueTargetOrderById(u, o.id, o.tar)
elseif o.typ == ORDER_TYPE_POINT then
if o.id == 851971 then
//This adjusts for a bug in the point order's boolean return
//when issuing a smart order
call IssuePointOrderById(u, o.id, o.x, o.y)
return true
else
return IssuePointOrderById(u, o.id, o.x, o.y)
endif
elseif o.typ == ORDER_TYPE_IMMEDIATE then
return IssueImmediateOrderById(u, o.id)
endif
return false
endfunction
function AbortOrder takes unit u returns boolean
if IsUnitPaused(u) then
return false
else
call PauseUnit(u, true)
call IssueImmediateOrder(u, "stop")
call PauseUnit(u, false)
endif
return true
endfunction
//**********************************************************
private function Conditions takes nothing returns boolean
return OrderFilter(GetTriggerUnit(), GetIssuedOrderId())
endfunction
private function Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local widget t = GetOrderTarget()
local integer oid = GetIssuedOrderId()
local integer oty = 0
if GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER then
call order.create(u, oid, ORDER_TYPE_TARGET, t, GetWidgetX(t), GetWidgetY(t))
elseif GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER then
call order.create(u, oid, ORDER_TYPE_POINT, null, GetOrderPointX(), GetOrderPointY())
elseif GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_ORDER then
call order.create(u, oid, ORDER_TYPE_IMMEDIATE, null, GetUnitX(u), GetUnitY(u))
debug else
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Invalid order type")
endif
set u = null
set t = null
endfunction
//**********************************************************
private function SpellActions takes nothing returns nothing
local order o = GetPastOrder(GetTriggerUnit(), 1)
set o.fin = true
endfunction
//**********************************************************
private function OnAdd takes nothing returns boolean
local integer hid = GetHandleId(GetFilterUnit())
local integer i = ORDERS_TO_HOLD
//Handle stored orders in the hashtable
loop
//We hold up to the constant ORDERS_TO_HOLD in the table
exitwhen i == 0
//If any of the N orders exist for this handle id, kill them all
if HaveSavedInteger(ht, hid, i) then
call order.destroy(order(LoadInteger(ht, hid, i)))
call RemoveSavedInteger(ht, hid, i)
endif
set i = i - 1
endloop
return false
endfunction
//**********************************************************
private function Init takes nothing returns nothing
local trigger trg = CreateTrigger()
local region re = CreateRegion()
local rect m = GetWorldBounds()
//Main order catching trigger
call TriggerAddAction(trg, function Actions)
call TriggerAddCondition(trg, Condition(function Conditions))
call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_ISSUED_ORDER)
//Spell trigger to set a flag that indicates a spell order's completion
set trg = CreateTrigger()
call TriggerAddAction(trg, function SpellActions)
call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_SPELL_EFFECT)
//Entering world trigger that clears old data from handle ids
set trg = CreateTrigger()
call RegionAddRect(re, m)
call TriggerRegisterEnterRegion(trg, re, Condition(function OnAdd))
call RemoveRect(m)
set trg = null
set re = null
set m = null
endfunction
endlibrary