• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Safe lag generator

Level 3
Joined
Jun 25, 2011
Messages
36
Hello guys,

I would like to share this system that I created. I didn't find such a system on hive and many people always ask me for it so I created it by myself. But it maybe doesn't work perfectly so I need your advides.

I often play ice escape and players who lag have disavantage. This system generates safe lag so all players are on an equal footing. But you should use it with delay reducer to minimize normal lag. Personally i also use it because ice escapes are too easy, so it make a challenge.

I hope you'll enjoy this safe delay generator :). Delay 1oo% guaranted !

I would need your help to create a spike generator to make a better system.

Needed library :
JASS:
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) or UnitId2String(id) != null
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 or UnitId2String(GetPastOrderId(u, whichOrder)) != null)
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
                if UnitId2String(o.id) != null then
                    return IssuePointOrderById(u, o.id, o.x, o.y)
                else
                    return IssueBuildOrderById(u, o.id, o.x, o.y)
                endif
            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

Safe delay generator :
JASS:
library SafeDelayGenerator uses LastOrder, TimerUtils

struct Delay //No extend array for more code generation and more lags :)

    public static real min = 0.5
    public static real max = 1
    
    public static boolean enabled = true
    
    private unit orderedUnit = null
    
    private static method unpauseOrderedUnit takes nothing returns nothing
        local thistype this = GetTimerData(GetExpiredTimer())
        call DestroyTimer(GetExpiredTimer())
        set enabled = false
        call IssuePastOrder(this.orderedUnit, 2)
        set enabled = true
        call this.destroy()
    endmethod
    
    private static method anyOrderAction takes nothing returns nothing
        local thistype this
        if enabled then
            set this = thistype.create() //2 functions call -> more lags :)
            set this.orderedUnit = GetOrderedUnit()
            set enabled = false
            call IssuePastOrder(this.orderedUnit, 2)
            call TimerStart(NewTimerEx(this), GetRandomReal(min, max), false, function thistype.unpauseOrderedUnit)
            //Would be better if I created a new timer each time but need timer utils for data attachment : /
            set enabled = true
        endif
    endmethod
    
    private static method onInit takes nothing returns nothing
        local trigger anyOrder = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(anyOrder, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
        call TriggerRegisterAnyUnitEventBJ(anyOrder, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
        call TriggerRegisterAnyUnitEventBJ(anyOrder, EVENT_PLAYER_UNIT_ISSUED_ORDER)
        call TriggerAddAction(anyOrder, function thistype.anyOrderAction) //TriggerAction and not condition for more lags
    endmethod

endstruct
 
Yeah you finally made it !! Good job, it will be very useful for my maps :).

I tested it and seems be working. Otherwise, I noticed that you're also generating unsafe lags and that doesn't really make sense for such a system.
You really should avoid these double function calls (thistype.create() -> thistype.allocate()...) ,.. but +rep

Edit:
You could maybe link to delay reducer, so we can esnure we only use custom lags & delay.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
You can't destroy TimerUtils' timers, they are meant to be recycled with ReleaseTimer, not destroyed.
Also 2 simple function calls instead of one will hardly make any difference. Same for struct extends array with an inlined struct allocator VS struct no extends array.
And anyway IIRC .allocate() or .create() is just the same with the default vJass struct allocate once it has been inlined (no debug mode).

Finally correct me if i'm wrong but in your case pause/unpause the unit should be enough.
I mean no need to re-order, the order should be kept and re-issued after the unpause.

Now about the delay, for each player it will still be his delay + this order delay.
You can't make them to have the same, excepted ofc if they have quite the same delay.
 
Last edited:
This is a poor excuse for a resource. Completely rewrite it. Also link to required libraries, don't include them in your post.


You should be able to apply delay to specific units, not just all units. Let users rely on a unit indexer to apply to all.

You should be relying on a unit indexer and the struct should extend array. There should be 0 use of allocate, create, etc, just use this unit's index.

Have a real variable for how much to delay the unit that the user can set.

For your rewrite, move to mag's resource instead of using the bjs and use a condition. The lag you are talking about by slowing code down = spikes from JASS, not delay, and you won't get those with so little. You need something mad like Scrambler to get them.

Don't forget to null your local trigger, even if you aren't destroying it. A reference is a reference.

JASS:
        local trigger anyOrder = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(anyOrder, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
        call TriggerRegisterAnyUnitEventBJ(anyOrder, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
        call TriggerRegisterAnyUnitEventBJ(anyOrder, EVENT_PLAYER_UNIT_ISSUED_ORDER)
        call TriggerAddAction(anyOrder, function thistype.anyOrderAction) //TriggerAction and not condition for more lags



If you prefer to keep it as this static garbage (that's what it is, I'm not going to sugarcoat it for you), then we can gy it here and now. There is no room for negotiation.
 
Last edited:
Top