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

[Snippet] PrintOrders

Level 8
Joined
Oct 3, 2008
Messages
367
This is a fine debugging tool that simply prints order data. It will print the order ID in hex if it's a normal order, in ASCII if it's a rawcode. It also displays the type of order, the unit that was issued the order, and the name of the order (if any). Optionally, it can also display data on the target of an order if DISPLAY_TARGET_INFO is flipped to true.

It's excellent for finding order ID's, especially since it's much quicker to copy down the hex equivalents. Also used to create the wildly popular :)gg:) AutocastOrderEvent.

Requires ASCII.

JASS:
library PrintOrders initializer Init requires Ascii

static if DEBUG_MODE then

//Config
globals
    private constant boolean CLEAR_BEFORE_PRINTING = false
    //Setting this to true will clear the screen of text messages when printing order data.
    
    private constant string ACTIVATE = "-printon"
    //Typing this in-game will activate PrintOrder (it is activated by default).
    private constant string DEACTIVATE = "-printoff"
    //Typing this in-game will deactivate PrintOrder.
    
    private constant boolean DISPLAY_TARGET_INFO = false
    //When flipped to true, the system will display information on targets of orders.
endglobals
//End config

globals
    private string array Hex_Values
    private trigger t1 = CreateTrigger()
    private trigger t2 = CreateTrigger()
    private trigger t3 = CreateTrigger()
    private constant integer OFFSET = 0xD0000
endglobals
 
private function Int2HexString takes integer i returns string
    local string hex = ""
    local integer r = 0
    local string neg = ""
    if (i<0) then
        set neg = "-"
        set i = -i
    endif
    loop
        set r = i - (i / 16) * 16
        set i = i/16
        set hex = Hex_Values[r] + hex
        exitwhen (i==0)
    endloop
    return neg+hex
endfunction

private function Rawcode2String takes integer i returns string
    local string c = ""
    loop
        exitwhen i == 0
        set c = Ascii2Char(i - (i / 256) * 256)+c
        set i = i / 256
    endloop
    return c
endfunction

private function Target takes nothing returns boolean
    local integer order = GetIssuedOrderId()
    local string s
    static if DISPLAY_TARGET_INFO then
        local string n
    endif
    if order - OFFSET < 8191 then
        set s = "0x" + Int2HexString(order)
    else
        set s = "'" + Rawcode2String(order) + "'"
    endif
    static if CLEAR_BEFORE_PRINTING then
        call ClearTextMessages()
    endif
    
    static if DISPLAY_TARGET_INFO then
        if GetOrderTargetUnit() != null then
            set n = GetUnitName(GetOrderTargetUnit())
        elseif GetOrderTargetDestructable() != null then
            set n = GetDestructableName(GetOrderTargetDestructable())
        else
            set n = GetItemName(GetOrderTargetItem())
        endif
        
        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, GetUnitName(GetTriggerUnit()) +/*
        */" issued target object order.\n  OrderId: " + s + "\n  order name: " +/*
        */OrderId2String(order) + "\n\nTarget name: " + n + "\nTarget handle id: " + /*
        */I2S(GetHandleId(GetOrderTarget())) + "\n\n")
    else
        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, GetUnitName(GetTriggerUnit()) +/*
        */" issued target object order.\n  OrderId: " + s + "\n  order name: " +/*
        */OrderId2String(order) + "\n\n")
    endif
    
    return false
endfunction

private function Point takes nothing returns boolean
    local integer order = GetIssuedOrderId()
    local string s
    if order - OFFSET < 8191 then
        set s = "0x" + Int2HexString(order)
    else
        set s = "'" + Rawcode2String(order) + "'"
    endif
    static if CLEAR_BEFORE_PRINTING then
        call ClearTextMessages()
    endif
    
    static if DISPLAY_TARGET_INFO then
        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, GetUnitName(GetTriggerUnit()) +/*
        */" issued target point order at (" + R2S(GetOrderPointX()) + "," + R2S(GetOrderPointY()) + ")"/*
        */ + ".\n  OrderId: " + s + "\n  order name: " +/*
        */OrderId2String(order) + "\n\n")
    else
        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, GetUnitName(GetTriggerUnit()) +/*
        */" issued target point order.\n  OrderId: " + s + "\n  order name: " +/*
        */OrderId2String(order) + "\n\n")
    endif
    
    return false
endfunction

private function Order takes nothing returns boolean
    local integer order = GetIssuedOrderId()
    local string s
    if order - OFFSET < 8191 then
        set s = "0x" + Int2HexString(order)
    else
        set s = "'" + Rawcode2String(order) + "'"
    endif
    static if CLEAR_BEFORE_PRINTING then
        call ClearTextMessages()
    endif
    call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, GetUnitName(GetTriggerUnit()) +/*
    */" issued order with no target.\n  OrderId: " + s + "\n  order name: " +/*
    */OrderId2String(order) + "\n\n")
    return false
endfunction

private function Activate takes nothing returns boolean
    call EnableTrigger(t1)
    call EnableTrigger(t2)
    call EnableTrigger(t3)
    call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "PrintOrder activated.")
    return false
endfunction

private function Deactivate takes nothing returns boolean
    call DisableTrigger(t1)
    call DisableTrigger(t2)
    call DisableTrigger(t3)
    call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "PrintOrder deactivated.")
    return false
endfunction

private function Init takes nothing returns nothing
    local integer i = 15
    
    local trigger t = CreateTrigger()
    call TriggerRegisterPlayerChatEvent(t, GetLocalPlayer(), ACTIVATE, true)
    call TriggerAddCondition(t, Condition(function Activate))
    
    set t = CreateTrigger()
    call TriggerRegisterPlayerChatEvent(t, GetLocalPlayer(), DEACTIVATE, true)
    call TriggerAddCondition(t, Condition(function Deactivate))

    loop
        call TriggerRegisterPlayerUnitEvent(t1, Player(i), EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)
        call TriggerRegisterPlayerUnitEvent(t2, Player(i), EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
        call TriggerRegisterPlayerUnitEvent(t3, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
        exitwhen i == 0
        set i = i - 1
    endloop
    call TriggerAddCondition(t1, Filter(function Target))
    call TriggerAddCondition(t2, Filter(function Point))
    call TriggerAddCondition(t3, Filter(function Order))
    set Hex_Values[0] = "0"
    set Hex_Values[1] = "1"
    set Hex_Values[2] = "2"
    set Hex_Values[3] = "3"
    set Hex_Values[4] = "4"
    set Hex_Values[5] = "5"
    set Hex_Values[6] = "6"
    set Hex_Values[7] = "7"
    set Hex_Values[8] = "8"
    set Hex_Values[9] = "9"
    set Hex_Values[10] = "A"
    set Hex_Values[11] = "B"
    set Hex_Values[12] = "C"
    set Hex_Values[13] = "D"
    set Hex_Values[14] = "E"
    set Hex_Values[15] = "F"
endfunction

endif

endlibrary
 
Last edited:
Level 18
Joined
Jan 21, 2006
Messages
2,552
This doesn't need 3 triggers, it only needs one. There are 3 functions that are almost duplicates of each other that do not need to be, and could easily be compressed into half the size that it is.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Well its not like you would be copying+pasting the code from each function into one and then separating them using an if-statement. It also wouldn't reduce the efficiency to do it this way.

Most of the functions have common variables, such as GetTriggerUnit() and GetIssuedOrderId(). The only varying values would be returned from GetOrderTarget() and GetOrderPointX/Y. The programming behind this is just bad in general.
 
Level 8
Joined
Oct 3, 2008
Messages
367
>The only varying values would be returned from GetOrderTarget() and GetOrderPointX/Y .

And that's exactly why differentiating between events would be required. And since it's required, separate triggers are better.

>The programming behind this is just bad in general.

Explanations are always nice.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Well typically duplicating code unnecessarily is a bad programming practice. In this case it is avoidable, without any reduction to the efficiency of the code. For such a simple task as displaying a message it makes no difference whether it is 3 triggers or 1 with an if-statement. Having 3 triggers execute really small pieces of duplicate code is a bad programming practice, especially when there is the option of having 1 trigger executing a mildly larger block of code, with absolutely no duplication.

I really don't feel like arguing about it though, I really don't care enough.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
A speed freak wouldn't refer to Player( i ) 3 times, instead he would set it up as a local variable. The same for the local player. Also, in the functions Order / Target / Point he uses GetIssuedOrderId() despite having that value saved as a local integer. There is nothing "speed-freaky" about this.

Also the ASCII library should really be optional, since there is no requirement in that library just to simply print the orders.
 
Last edited:
Level 8
Joined
Oct 3, 2008
Messages
367
Finally, something sane to change.

>GetIssuedOrderId() despite having that value saved as a local integer.

Fixed.

>A speed freak wouldn't refer to Player( i ) 3 times

Is there any proof that a local variable is any faster? The player native's speed is nearly on par with an array call.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Which is still slower than a local variable reference. Not that that makes any much of a difference, since its only called on map initialization.

But it would be nice if ASCII wasn't required though, merely optional.
 
Level 8
Joined
Oct 3, 2008
Messages
367
>Which is still slower than a local variable reference.

You have to set the local variable too, ya know.

>But it would be nice if ASCII wasn't required though, merely optional.

There's no sense in duplicating large amounts of code when there's a perfectly good resource available for use.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
No, I'm talking about not displaying the IDs as hex strings, instead just using their regular integer ID. The library is only required because that is how your system handles the output, but it is not necessary for the printed orders, it is just a preference. If you were to make it optional, then the user could choose between displaying the hex-string or the regular ID --obviously you wouldn't be able to display the hex string if the library was not available --with the boolean LIBRARY_ASCII.

azlier said:
You have to set the local variable too, ya know.

Well if you want a clear winner, then you can bench-mark it (or maybe I will) but I'm pretty certain that using a local variable would be quicker than referencing the function 3 times. I'm guessing that referencing a local variable and allocating the necessary memory is light-years quicker than an array look-up. Only one way to know for certain.
 
Level 8
Joined
Oct 3, 2008
Messages
367
>No, I'm talking about not displaying the IDs as hex strings, instead just using their regular integer ID.

That takes away perhaps this library's best feature. It makes copying down order ID's so much simpler. I refuse to take it away, even optionally.

>Well if you want a clear winner, then you can bench-mark it (or maybe I will)

Feel free to benchmark it yourself. I can't benchmark anything for some odd reason.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Couldn't you just inline the Ascii library ?
Coz having a library requirement just for a debug library is painful, plus the library Ascii doesn't have useful functions like RawCode2String, and it's not going to be changed.
Give him credits and that's all.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
ASCII also runs from a library initializer, making it so that any struct initializer or module initializer is gonna be screwed. I think it's illegitimate to inline his library without his consent, even if he's not working on it any more. Someone just needs to do it... better.

Inlines means inlines, so there is no way that we will be screwed.

Also could you tell me a concrete case where ASCII is useful and which is not for a debug purpose ?
About the "illegitimate" thing, giving credits and link to the original library is fine for me.
Anyway even if you don't do it, i will do it for myself, "problem" (all is relative) is that i will have to store it somewhere (don't have internet connection when i use windows 7 , due a personal choice)
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,466
A much-shorter version of this for Lua:

Lua:
OnGlobalInit(function()
    if not AnyPlayerUnitEvent then return end --https://www.hiveworkshop.com/threads/collection-gui-repair-kit.317084
    --PrintOrders by Azlier with Lua conversion by Bribe
    local CLEAR_BEFORE_PRINTING = true          --Setting this to true will clear the screen of text messages when printing order data.
   
    local ACTIVATE              = "-printon"    --Typing this in-game will activate PrintOrder (it is activated by default).
    local DEACTIVATE            = "-printoff"   --Typing this in-game will deactivate PrintOrder.
   
    local DISPLAY_TARGET_INFO   = true          --When flipped to true, the system will display information on targets of orders.
    local OFFSET, activated = 0xD0000, true
    local function getOrder()
        if CLEAR_BEFORE_PRINTING then ClearTextMessages() end
       
        local order = GetIssuedOrderId()
       
        if order - OFFSET < 8191 then
            return order, "0x".. string.format("%x", order)
        end
        local c = "'"
        local i = order
        while i ~= 0 do
            local j = i // 256
            c = string.char(i - j * 256)..c
            i = j
        end
        return order, "'"..c
    end
    local function regChat(str, state, msg)
        local t = CreateTrigger()
        TriggerRegisterPlayerChatEvent(t, GetLocalPlayer(), str, true)
        TriggerAddCondition(t, Filter(function() activated = state ; print("PrintOrder "..msg) end))
    end
    regChat(ACTIVATE, true, "activated")
    regChat(DEACTIVATE, false, "deactivated")
    local function baseStr(str, s, order)
        if not activated then return end
        if not s then order, s = getOrder() end
        print(GetUnitName(GetTriggerUnit()) .." issued "..str..".\n  OrderId: ".. s .."\n  order name: "..OrderId2String(order) .."\n\n")
    end
    AnyPlayerUnitEvent.add(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER,
    function()
        if DISPLAY_TARGET_INFO and activated then
            local order, s = getOrder()
            local n = (GetOrderTargetUnit() and GetUnitName(GetOrderTargetUnit())) or (GetOrderTargetDestructable() and GetDestructableName(GetOrderTargetDestructable())) or GetItemName(GetOrderTargetItem())
           
            print(GetUnitName(GetTriggerUnit()) .." issued target object order.\n  OrderId: ".. s .."\n  order name: "..OrderId2String(order) .."\n\nTarget name: ".. n .."\nTarget handle id: ".. I2S(GetHandleId(GetOrderTarget())) .."\n\n")
        else
            baseStr("target object order")
        end
    end)
    AnyPlayerUnitEvent.add(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER,
    function()
        if DISPLAY_TARGET_INFO and activated then
            local order, s  = getOrder()
            print(GetUnitName(GetTriggerUnit()) .." issued target point order at (".. R2S(GetOrderPointX()) ..",".. R2S(GetOrderPointY()) ..")".. ".\n  OrderId: ".. s .."\n  order name: "..OrderId2String(order) .."\n\n")
        else
            baseStr("target point order")
        end
    end)
    AnyPlayerUnitEvent.add(EVENT_PLAYER_UNIT_ISSUED_ORDER, function() baseStr("order with no target") end)
end)
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,466
I'm going to code review you for fun. Oh it's a COMIN.

Update: Wow LUA sucks.
9D8D9547-9D34-459F-9F90-584599DE8621.jpeg
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,466
Or if you need 3 monitors lengthwise to see a line of code.
At first glance it looks like local constants are a thing in LUA? If it's anything life WC3 then locals are faster or some nonsense.
Constants are not a thing in Lua.

Lua is all about "tables". Everything that's not stored in a local declaration or lost due to never having been declared in a scope is garbage collected.

A "global" variable is declared without the "local" prefix, but it is not a "variable" itself. Instead, it is an object stored in a table called the "global table", accessed via its name "_G". You can reference a global via _G["CreateUnit"], _G.CreateUnit or just CreateUnit. The ability to access globals via string concatenation gives us a lot of power previously only offered at pre-processing time with vJass modules and textmacros.

The power of Lua comes, therefore, in being able to mess around with _G and its values, to deliver perfect hooks. I've found many interesting things such as being able to take a global variable in GUI and treat it like a function call (Global Variable Remapper, I've linked it in my sig).

Overall, it's got everything JASS had, most things vJass had, and since it is an actual programming language there is an abundance of helpful information online that lets you read up on it.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
"everything JASS had" -- hashtables?

I'm doing some reading now its discouraging to read things like "this scripting language let's you do anything you set your mind to". It says it's used in embedded systems.. Embedded systems with Windows though?

Seems a bit hand wavey to me in terms of its value. I haven't modded though maybe it's a community kinda thing.

I can't see how LUA offers better performance it's like saying a better steering wheel makes your car go faster.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,466
"everything JASS had" -- hashtables?

I'm doing some reading now its discouraging to read things like "this scripting language let's you do anything you set your mind to". It says it's used in embedded systems.. Embedded systems with Windows though?

Seems a bit hand wavey to me in terms of its value. I haven't modded though maybe it's a community kinda thing.

I can't see how LUA offers better performance it's like saying a better steering wheel makes your car go faster.
Hashtables are type-restricted tuples of Lua tables. Lua offers functions as actual containers, which can be stored in those tables (and created dynamically, rather than at compile-time). The closest match was vJass function interfaces, which helped, but could not be dynamically-allocated.

The biggest benefit of having dynamic function creation is that the container (function) inherits the locals of the calling function, and allow something like "TimerStart" to have its callback perfectly inherit the local state of the calling function.

Another major perk is Precise Wait, which overrides PolledWait and TriggerSleepAction and makes them wait in exact timing rather than based on randomness, imprecision and a minimum wait interval. This is huge for GUI.

I don't care as much about speed, but if we're talking about speed then Lua takes the trophy in the cases I've mentioned above. It might use more RAM due to dynamic functions, but it's garbage collected.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,466
That Lua version wasn't working correctly thanks to the infamous % character. This will fare much better, because I've actually tested it:
Lua:
--PrintOrders by Azlier with Lua conversion by Bribe
do
---@param event playerunitevent
---@param code function
local function registerAnyPlayerUnitEvent(event, code)
    local t = CreateTrigger()
    TriggerRegisterAnyUnitEventBJ(t, event)
    TriggerAddAction(t, code)
end

local CLEAR_BEFORE_PRINTING = true          --Setting this to true will clear the screen of text messages when printing order data.

local ACTIVATE              = "-printon"    --Typing this in-game will activate PrintOrder (it is activated by default).
local DEACTIVATE            = "-printoff"   --Typing this in-game will deactivate PrintOrder.

local DISPLAY_TARGET_INFO   = true          --When flipped to true, the system will display information on targets of orders.
local OFFSET                = 0xD0000

local activated = true

---@param orderId integer
---@return string
local function getByteChars(orderId)
    local result = {}
    repeat
        local dividedId = orderId // 256
        table.insert(result, 1, string.char(orderId - dividedId * 256))
        orderId = dividedId
    until orderId == 0
    return table.concat(result)
end

---@param orderType string
---@param extraInfo? string
local function printOrder(orderType, extraInfo)
    if not activated then
        return
    end

    if CLEAR_BEFORE_PRINTING then
        ClearTextMessages()
    end

    local orderId = GetIssuedOrderId()
    local formattedStr ---@type string

    if orderId - OFFSET < 8191 then
        formattedStr = "0x" .. ("\x25x"):format(orderId):upper()
    else
        formattedStr = "'" .. getByteChars(orderId) .. "'"
    end

    print(
        GetUnitName(GetTriggerUnit()) .. " issued " .. orderType ..
        "\n  Order ID: " .. formattedStr ..
        "\n  Raw Order ID: " .. orderId ..
        "\n  Order name: " .. OrderId2String(orderId) ..
        ((extraInfo and ("\n" .. extraInfo)) or "") ..
        "\n\n"
    )
end

---@param userInput string
---@param state boolean
---@param stateStr string
local function registerChatEvent(userInput, state, stateStr)
    local t = CreateTrigger()
    TriggerRegisterPlayerChatEvent(t, GetLocalPlayer(), userInput, true)
    TriggerAddAction(t,
        function()
            activated = state
            print("PrintOrder " .. stateStr)
        end
    )
end
registerChatEvent(ACTIVATE, true, "activated")
registerChatEvent(DEACTIVATE, false, "deactivated")

---@param getTarget fun(): widget | nil
---@param getName fun(target: widget): string
local function getOrderTargetName(getTarget, getName)
    local target = getTarget()
    return target and getName(target)
end

registerAnyPlayerUnitEvent(
    EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER,
    function()
        local targetInfo ---@type string
        if DISPLAY_TARGET_INFO then
            local targetName =
                getOrderTargetName(GetOrderTargetUnit, GetUnitName) or
                getOrderTargetName(GetOrderTargetDestructable, GetDestructableName) or
                getOrderTargetName(GetOrderTargetItem, GetItemName) or
                "Unknown"

            targetInfo =
                "\n  Target name: ".. targetName ..
                "\n  Target handle ID: ".. GetHandleId(GetOrderTarget())
        end
        printOrder("a target widget order.", targetInfo)
    end
)

registerAnyPlayerUnitEvent(
    EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER,
    function()
        local coordinates ---@type string
        if DISPLAY_TARGET_INFO then
            coordinates = "  Coordinates: (" .. GetOrderPointX() .. ", " .. GetOrderPointY() .. ")"
        end
        printOrder("a target point order.", coordinates)
    end
)

registerAnyPlayerUnitEvent(
    EVENT_PLAYER_UNIT_ISSUED_ORDER,
    function()
        printOrder("an order with no target.")
    end
)
end
 
Last edited:
Top