Lua Debug Utils (incl. Ingame Console)

This bundle is marked as director's cut. It exceeds all expectations and excels in every regard.
Debug Utils 2.2

Overview

Quick Reference

Details

Installation

Changelog


A debug library for Lua mapmaking that will save you tons of time.

Feature Overview:
  • Automatic ingame error messages.
  • Ingame Console (execute code via Wc3 ingame chat).
  • Debug-Library functions for error handling and investigation.
  • Automatic warnings upon reading nil-globals.
  • Print Caching: Prints during loading screen will be executed after game start.
  • Name Caching: tostring and print will show the actual string-name of an object.
Refer to the tab Quick Reference for a feature summary or to Details for more in-depth explanations.

This resource was a lot of work. Be gentle upon providing feedback and leave a like, if you found it helpful.

Motivation

Without the help of a resource like this, Wc3 mapping in Lua is rather prone to bugs. One main reason is Lua's missing type-safety: the language doesn't allow to denote the types of function parameters and return values, so the Lua syntax check can't inform you, if you passed the wrong type. The same holds, if you pass the wrong number of arguments to a function, accidently swap two arguments or misspell the name of anything (like CraeteUnit): Lua syntax check won't tell you. It can't in fact, because playing with argument types and argument counts is legit to do on purpose.
Even worse, Warcraft by default won't even inform you about breaking code ingame. If a trigger fails to execute, it will fail silently. You will simply see that nothing happens, when it should.

If you are coding without proper safeguard in place, a simple mistake like any of the above will silently break your code, costing you hours to debug an issue that Jass Syntax check would have spotted before even starting the map.

Lua theoretically offers its own debug library, but Blizzard disabled it in Warcraft Reforged for an unknown reason.
Clustering thousands of prints into your code is clearly not a good alternative, so what shall we do?

This resource attempts to solve all of these issues. It enables automatic ingame error messages, provides useful tools for bug investigation, gives advice on how to setup a proper developing environment to compensate for Lua's bad syntax check and even allows for ingame code execution.

Avoiding Bugs

Debug Utils provides error handling functionality, but it doesn't prevent you from causing them in the first place. Clearly, being supported in avoiding bugs is just as important as being supported in solving them, so let's begin with some good advice before proceeding with the resource.

Use a proper development environment
Switching to a proper development environment was one of the most effective things I’ve ever done to make my coding more efficient and joyful. That fact should go without saying, but I know from personal experience that working in a text editor (like Notepad++) or even in the Wc3 editor doesn’t feel bad enough as long as you haven’t tested the alternatives. Trust me, once you tried a proper development environment (like VS Code), you will never want to go back.

I personally use Visual Studio Code with sumneko’s lua extension “sumneko.lua”. It offers syntax highlighting, auto-complete, function parameter preview and many other intellisense features.

vscode_autocomplete-png.418739


Attached to A comprehensive guide to mapping in Lua, you also find the definition files common.lua, blizzard.lua and HiddenNatives.lua that you can use with the mentioned sumneko extension. They enable auto-completion and parameter preview for all Wc3 natives, just as shown in the screenshot above. The same guide (under Getting Started -> IDE Setup) also contains all the instructions you need to setup Visual Studio Code and import the definition files.

Annotate your code
The sumneko extension in VS Code also enables the option to annotate your code.
An annotation is a special type of comment that provides additional standardized documentation, which your development environment might depend on to provide certain intellisense features like type checking and parameter preview.

emmyannotation-png.418746

Annotations play a crucial role in Lua programming, because they are the only option for denoting function input and output parameter types, compensating for Lua's missing type safety by letting your development environment conduct the syntax check that WE isn't capable of.

Adding several lines of comments to every single function can feel annoying at first, but trust me, it's worth it. Being lazy on annotation will lead to bugs and cost you time, so better don't be lazy.
Consult this guide about Emmy annotation, if you want to learn more.

Credits

- @Bribe, for pretty table print, showing that xpcall's message handler executes before the stack unwinds and useful suggestions like name caching and stack trace improvements.
- @Jampion, for useful suggestions like print caching and applying Debug.try to all code entry points
- @Luashine, for useful feedback and building WC3 Debug Console Paste Helper
- @HerlySQR, for showing a way to get a stack trace in Wc3
- @Macadamia, for showing a way to print warnings upon accessing undeclared globals (where this all started with)


Quick Reference

Debug Utils enables the features and library functions listed below.
The ⚙️-boxes contain configuration settings related to the feature.
You can change the configuration in the downloaded code of Debug Utils (scroll down to directly below the documentation).


FeatureExplanation
Automatic Error Handling
Debug Utils will automatically print error messages for code executed within triggers or timers.
The error message will contain the relevant document, line number, description and a stack trace.

errorwithtrace-png.418748

⤴️Please refer to the Details tab (chapter: Error Handling) to learn how to interpret an error message like this.
⚙️
Lua:
--Activate Automatic Error Handling:
--TRY refers to the library function Debug.try.
USE_TRY_ON_TRIGGERADDACTION = true -- (default)
USE_TRY_ON_CONDITION = true -- (default)
USE_TRY_ON_TIMERSTART = true -- (default)
USE_TRY_ON_ENUMFUNCS = true -- (default)
USE_TRY_ON_COROUTINES = true -- (default)
--Include Stack Traces:
SHOW_TRACE_ON_ERROR = true -- (default)
Convert war3map.lua-referencesUse Debug.beginFile(<fileName>) in the very first line (even above comments!!) and Debug.endFile() in the very last line of every script file in your map. This will convert references for errors happening in that file from war3map.lua to the specified file name (and adjust the line number accordingly).

errorwithtraceconverted-png.418749


⚠️Debug.beginFile is executed during Lua root and thus requires Debug Utils to have loaded beforehand. Ensure this by placing the Debug Utils script topmost in your trigger editor.

For additional safety, it's advisable to write the statements like this:
Lua:
if Debug and Debug.beginFile then Debug.beginFile(<fileName>) end
... <script code>
if Debug and Debug.endFile then Debug.endFile() end
Ingame Code ExecutionType -exec <code> into the ingame chat to execute <code> and print return values on screen.

Type -console to open an Ingame Console that will execute any further chat input as code, showing inputs, outputs, timestamps, prints and errors in different colors.
Type exit to close the console.

ingameconsole_small-png.418747


⚙️Requires ALLOW_INGAME_CODE_EXECUTION = true in the settings.

You can also download @Luashine's Console Paste Helper. This little tool helps with pasting code from outside Wc3 to the Ingame Console to effectively bypass the restrictions of the Wc3 ingame chat.
Warnings for nil-globalsDebug Utils prints ingame warnings, if you try to access nil-globals.
This feature is meant to spot both un-initialized variables and name misspellings, such as calling CraeteUnit instead of CreateUnit.

undeclaredglobals-png.419644


Variables are excluded from the warnings after first initialization (i.e. after assigning any value to them, including MyGlobal = nil), as they are treated intentionally nilled from that point on.

The feature takes effect after game start to prevent nil-warnings for library dependency checks during loading screen.


⚙️
Lua:
--Activate nil warnings
WARNING_FOR_NIL_GLOBALS = true -- (default)
--Include stack trace into nil warnings
SHOW_TRACE_FOR_NIL_WARNINGS = true -- (default: false)
--Disable warnings for bj-globals
EXCLUDE_BJ_GLOBALS_FROM_NIL_WARNINGS = true -- (default: false)
--Disable warnings for initialized globals
EXCLUDE_INITIALIZED_GLOBALS_FROM_NIL_WARNINGS = true -- (default)

Similar to MyGlobal = nil, you can call Debug.disableNilWarningsFor("MyGlobal") to disable nil-warnings for a particular global variable.
Manual Error HandlingThe below library functions can catch/throw errors, which looks just like the screenshot presented in the Automatic Error Handling section.

⚙️Stack trace is included, if SHOW_TRACE_ON_ERROR is set to true.
  • Debug.try(func, ...)
    will execute func(...) in protected mode, returning all return values from func(...) if successful, and displaying an error message otherwise.

  • assert(condition, errorMsg, ...)
    will return ..., if the condition holds, and otherwise throws errorMsg and halts code execution.
    errorMsg is only visible in protected mode, which Debug Utils' automatically applies to all code from triggers and timers. Hence, you can freely use assert in such code.

  • Debug.assert(condition, errorMsg, ...)
    works exactly like assert, except that it can be used outside of protected mode (such as in Lua root) and doesn't halt code execution.

  • error(errorMsg)
    halts code execution and throws an error consisting of the specified message, if in protected mode.
    Again, Debug Utils' automatic error handling makes sure you can use this within code executed by triggers and timers.

  • Debug.throwError(...)
    prints an error message consisting of all specified arguments on screen.
    Similar to error, but can be used outside of protected mode and doesn't halt code execution.
Error InvestigationThese library functions help investigating on errors.
  • Debug.log(...)
    writes all specified parameters to the Debug-log, which is printed upon the next error being catched (by any of the library functions above). This only saves the last execution at any particular code location, so you don't need to worry about the Debug-log getting flooded with information.
    debug-log-png.418753


  • Debug.wc3Type(object)
    returns the Warcraft 3 type, if object is a Wc3-object. Returns the Lua-type otherwise.
    Lua:
    Debug.wc3Type(Player(0)) --> "player"
    Debug.wc3Type({}) --> "table"

  • table.print(T:table, [depth:integer], [pretty_yn:boolean])
    alternatively table.print(T, pretty_yn), prints all (key,value)-pairs inside T and subtables on screen.
    You can quickly take a look into your data structures this way.
    Concise version: (table.print(T))
    table-print-png.418754

    Pretty version: (table.print(T,true))
    table-print_pretty-png.418756


  • Debug.traceback()
    returns the stack trace at the point where this is called.
    Errors print a stack trace anyway, but this function can output traces without a given error.

  • Debug.getLine()
    returns the line in war3map.lua, where this is called (not converted to local file).
Print Override
⚙️
Lua:
--Delay loading screen prints to after game start
USE_PRINT_CACHE = true
--Set Print Duration (leave nil for default behaviour)
PRINT_DURATION = nil ---@type number
Name CachingDebug Utils will cache the string name for objects in global scope and override print and tostring to show these names.
  • Printing global objects will show their name instead of their memory position.
    Lua:
    print(CreateUnit)
    --> function: 0000016F29E16700  (if feature is turned off)
    --> function: CreateUnit  (if feature is turned on)
    The same applies to tostring.
  • New Globals will automatically be added to the name cache.
    Lua:
    T = {} --declared after game start
    print(T) --> table: T
  • Even objects within subtables of global scope will automatically get a name up to the depth specified in the settings.
    Lua:
    T = {loc = Location(0,0)}
    print(T.loc) --> locaton: T.loc (assuming you have set a depth of 1 or higher)
⚙️
Lua:
--Activate Name Cache for existing globals
USE_NAME_CACHE = true -- (default)
--Automatically add new globals to the name cache (adds __newindex to _G)
AUTO_REGISTER_NEW_NAMES = true -- (default)
--Auto-name new objects within subtables of _G, up to the specified depth
--Adds __newindex to every(!) such subtable
NAME_CACHE_DEPTH = 4 --deactivate by setting to 0.
Library functions
(require USE_NAME_CACHE = true)
  • Debug.registerName(object:any, name:string)
    adds the specified name for the specified object to the name cache. Use this for any object not affected by the automatic features listed above, for example locals.
  • Debug.registerNamesFrom(parentTable:table, [parentTableName:string], [maxDepth:integer])
    will add names for all elements within the specified parentTable to the name cache, using the best applicable notation out of <parentTableName>[<childName>] and <parentTableName>.<childName>.
  • Debug.oldTostring(object)
    returns the old name of any object (i.e. without using the name cache).

In-Depth Feature Discussion

Ingame Console and -exec command

A usual thing upon encountering a bug in our map during testing is that we want to immediately investigate on it and retrieve as much information from the running game state as possible. Unfortunately, adding prints to our code requires restarting Wc3 and trying to reproduce the error, which is time-consuming and annoying.

DebugUtils helps out with this issue by enabling Ingame Code Execution via either an Ingame Console or using the -exec command.

⚙️You can disable the functionality for ingame code execution by setting ALLOW_INGAME_CODE_EXECUTION to false.

-exec command

Typing -exec <code> ingame will directly execute <code>.

For instance, if we have a data structure x that we consider related to the bug, we might just type -exec table.print(x) and hopefully spot the issue in a blink of an eye (table.print will be discussed later). We can also use Wc3 natives, e.g. -exec CreateUnit(Player(0), FourCC('hfoo'), 0,0,0) would create a Footman at location (0,0).
We may even define further code, like first -exec function a(x) return x end and then -exec print(a('Test')).

The -exec command prints all return values given by the executed statement. As such, you an also use it for term evaluation, i.e. type -exec 2+5 to see 7 being printed, or -exec v to see the value stored inside the variable v.

execcommand-png.418750


The -exec command is a niche utility, though. You typically use the Ingame Console instead, which is presented below.

Ingame Console

With Debug Utils in your map, you can open up an Ingame Console by typing -console into the Ingame Chat. It will execute any further chat input as code, except input beginning with a hyphen (to still allow for trigger-based chat commands).
The console supports function execution, variable declaration (consequently function definitions), term statements (like just writing y to see whats inside) and multiline-input.
It shows all inputs and outputs, including prints and error messages, in different colors.

2_4_console-jpg.374083


The Wc3 ingame chat obviously misses a lot of features that other console inputs typically have. It's limited to a low amount of characters. It misses the ability to use up and down-arrow to navigate through all previous inputs. You can't even use left and right arrow to move the text cursor, because this will instead move Warcraft's ingame camera (or can you actually move the text cursor? If you know how, leave a comment!).
You can however use Home and End buttons on your keyboard to jump to the very beginning or end of the input. This often comes in handy.

Multiline-Input

You can prevent a chat input from being immediately executed by preceeding it with the '>' character. All lines entered this way are halted, until the first line without '>' is entered, which will execute all halted lines (and itself) in one chunk.

Examplary Chat Input:
Lua:
>function foo(x,y)
>return x+y
end
The preceeding '>' are not part of the code, but they just halt execution. The end line is not preceeded, so the whole chunk is executed at that point.

Multiplayer

The console is fully multiplayer-compatible, allowing both separate consoles for each player as well as a single shared one. In a multiplayer testgame, every player typing -console will get their own reserved console, which they can share by typing share afterwards (which is a reserved keyword). Sharing a console will close other player's active consoles.
No matter if separate or shared, all players will always code in the same environment. That means, if any player enters a = 5, all players can do print(a) and will see “5” being printed.

Reserved Keywords

Lua:
 --[[---------------
 |Reserved Keywords|
 -------------------
 The following keywords have a reserved functionality, i.e. are direct commands for the console and will not be interpreted as code:
 - 'help'          - will show a list of all reserved keywords and short explanations.
 - 'exit'          - will shut down the console
 - 'share'         - will share the players console with every other player, allowing others to read and write into it. Will force-close other players consoles, if they have one active.
 - 'clear'         - will clear all text from the console, except the word 'clear'
 - 'lasttrace'     - will show the stack trace of the latest error that occured within IngameConsole
 - 'show'          - will show the console, after it was accidently hidden (you can accidently hide it by showing another multiboard, while the console functionality is still up and running).
 - 'printtochat'   - will let the print function return to normal behaviour (i.e. print to the chat instead of the console).
 - 'printtoconsole'- will let the print function print to the console (which is default behaviour).
 - 'autosize on'   - will enable automatic console resize depending on the longest string in the display. This is turned on by default.
 - 'autosize off'  - will disable automatic console resize and instead linebreak long strings into multiple lines.
 - 'textlang eng'  - lets the console use english Wc3 text language font size to compute linebreaks (look in your Blizzard launcher settings to find out)
 - 'textlang ger'  - lets the console use german Wc3 text language font size to compute linebreaks (look in your Blizzard launcher settings to find out)--]]

Restrictions

The console is based off a Wc3 multiboard, so opening it will hide other active multiboards. If you need other multiboards to stay open, better rely on the -exec command.
As such, the console also needs to compute linebreaks manually, so those will not be perfect. A big bummer during development was that the font size is actually depending on the Wc3 installation text language (which can be changed in the Blizzard launcher). E.g. the letter y consumes less pixels in English text language than it does in German text language. The console is currently only optimized for English and German (default: English), so using other languages might in worst case lead to end of lines exceeding the multiboard. Also the linebreaks were computed using 1920 pixel width resolution, so using other resolution might yield suboptimal results as well.

Configuration

The Ingame Console code contains a settings section directly below the documentation. Feel free to adjust the values as desired. Some of the values are just the start up configuration and will change during playtime (such as currentWidth).
The most important setting to change is textLanguage, which you should set to either 'eng' or 'ger', depending on the text language configured in your blizzard launcher.

Lua:
IngameConsole = {
    --Settings
    numRows = 20                        ---@type integer Number of Rows of the console (multiboard), excluding the title row. So putting 20 here will show 21 rows, first being the title row.
    ,   autosize = true                 ---@type boolean Defines, whether the width of the main Column automatically adjusts with the longest string in the display.
    ,   currentWidth = 0.5              ---@type number Current and starting Screen Share of the console main column.
    ,   mainColMinWidth = 0.3           ---@type number Minimum Screen share of the console main column.
    ,   mainColMaxWidth = 0.8           ---@type number Maximum Scren share of the console main column.
    ,   tsColumnWidth = 0.06            ---@type number Screen Share of the Timestamp Column
    ,   linebreakBuffer = 0.008         ---@type number Screen Share that is added to longest string in display to calculate the screen share for the console main column. Compensates for the small inaccuracy of the String Width function.
    ,   maxLinebreaks = 8               ---@type integer Defines the maximum amount of linebreaks, before the remaining output string will be cut and not further displayed.
    ,   printToConsole = true           ---@type boolean defines, if the print function should print to the console or to the chat
    ,   sharedConsole = false           ---@type boolean defines, if the console is displayed to each player at the same time (accepting all players input) or if all players much start their own console.
    ,   showTraceOnError = false        ---@type boolean defines, if the console shows a trace upon printing errors. Usually not too useful within console, because you have just initiated the erroneous call.
    ,   textLanguage = 'eng'            ---@type string text language of your Wc3 installation, which influences font size (look in the settings of your Blizzard launcher). Currently only supports 'eng' and 'ger'.
    ,   colors = {
        timestamp = "bbbbbb"            ---@type string Timestamp Color
        ,   singleLineInput = "ffffaa"  ---@type string Color to be applied to single line console inputs
        ,   multiLineInput = "ffcc55"   ---@type string Color to be applied to multi line console inputs
        ,   returnValue = "00ffff"      ---@type string Color applied to return values
        ,   error = "ff5555"            ---@type string Color to be applied to errors resulting of function calls
        ,   keywordInput = "ff00ff"     ---@type string Color to be applied to reserved keyword inputs (console reserved keywords)
        ,   info = "bbbbbb"             ---@type string Color to be applied to info messages from the console itself (for instance after creation or after printrestore)
    }
...
}

Luashine's Paste Helper

The Ingame Console is restricted by the Wc3 Ingame Chat, which only supports a limited amount of characters for any input.
It does support multiline-input for that reason, but that as well is tedious to use and you can easily screw it up by forgetting any preceeding '>' character.
For that reason, @Luashine has created a tool that simplifies pasting multiple lines of code from outside Wc3 into the IngameConsole.
This is particularly useful, when you want to execute a large chunk of testcode containing several linebreaks.
Go here.

Glint's Console

There has been another post at hive offering an Ingame Console, which I also want to mention. It was made by @Glint and can be found here. His implementation is using custom UI and a proper textbox, which has its own advantages and disadvantages. His implementation was using it's own Lua environment, so you couldn't access Warcraft natives or even your own map code. As such, it's not really suitable for debugging.

Error Handling
Error Handling, in the context of Debug Utils, refers to getting a visible error message ingame on screen, after running erroneous code.

Automatic Error Handling

Debug Utils automatically handles errors within code executed by triggers (both conditions and actions), timers and coroutines.
It does so by overriding the natives TriggerAddAction, Condition, Filter, TimerStart, coroutine.create and coroutine.wrap, letting them wrap their callbacks into Debug.try (based of xpcall), effectively executing them in protected mode.

⚙️You can disable this behaviour by setting these constants to false in the configuration section:
Lua:
USE_TRY_ON_TRIGGERADDACTION
USE_TRY_ON_CONDITION
USE_TRY_ON_TIMERSTART
USE_TRY_ON_ENUMFUNCS
USE_TRY_ON_COROUTINES

Other sources of code execution (everything except triggers, timers and coroutines) will not automatically be error handled, so you have to do it manually (you will learn how to further below).
These include the Lua root, but also code executed by hooking into loading screen natives (like MarkGameStarted). To safely execute functions during loading screen, I suggest using Total Initialization, which offers several code entry points at different points during loading screen, all of them being properly error handled.

Interpreting Error Messages

An error message will look like this:

errorwithtrace-png.418748


You see that the message contains a file, a line number, a description and a stack trace.
The file will usually be war3map.lua, which is generated by the World Editor by appending all your trigger editor scripts to one big file, and thus contains all user-written code. This is a sad fact, because it requires us to look into the generated file to make sense of the given line number and get to the code causing the issue.

We can do so in two ways:
  1. Force a syntax error in your map (I usually put any single letter, say x, into any empty line of my code and save the map). A popup will appear, allowing you to scroll to the desired line. You typically want to use PageUp/PageDown buttons for that, because the mouse wheel takes ages.
    Note that the line number in the popup can be off by one from that in the error message.
  2. You can also export war3map.lua from your World Editor via File -> Export Script.
Both ways are applicable, while Warcraft is still up and running.
After finding the erroneous line, we can start investigating on the issue. As we will see in the chapter about Ingame Console, this doesn't even require closing Warcraft 3.

Replace war3map.lua references with local references

The war3map.lua-references are quite annoying, because they force us to waste time on opening and searching through the generated file on every new error. Wouldn't it be nicer to have the same file names and line numbers as in your development environment? Well, Debug Utils allows for that, but it requires a small bit of effort to get started:
You may add the line Debug.beginFile(<fileName>) (<fileName> must be a string) to line 1 of any script and Debug.endFile() to the very last line of the same script. Now, if an error occurs between the two, Debug Utils treats it as part of <fileName> and converts both the file name and the line number in its error message.
If you do this for every script you have, you get the same files and lines as in your IDE within every error message.
A little bit annoying to setup, but probably worth it.

Example (that is randomly assumed to start at war3map.lua:2230):
Lua:
Debug.beginFile("ExampleFile")              --war3map.lua:2230, ExampleFile:1
                                            --war3map.lua:2231, ExampleFile:2
---Adds one to the input parameter          --war3map.lua:2232, ExampleFile:3
---@param x number                          --war3map.lua:2233, ExampleFile:4
---@return number                           --war3map.lua:2234, ExampleFile:5
function foo(x)                             --war3map.lua:2235, ExampleFile:6
    return x+1                              --war3map.lua:2236, ExampleFile:7
end                                         --war3map.lua:2237, ExampleFile:8
                                            --war3map.lua:2238, ExampleFile:9
function bar()                              --war3map.lua:2239, ExampleFile:10
    print(foo(true))                        --war3map.lua:2400, ExampleFile:11
end                                         --war3map.lua:2401, ExampleFile:12
                                            --war3map.lua:2402, ExampleFile:13
Debug.endFile()                             --war3map.lua:2403, ExampleFile:14

Again, Debug.beginFile(<fileName>) marks line 1 of the document, so you must call it in the first line (even before comments) to really match the lines in your IDE!

errorwithtraceconverted-png.418749


⚠️Debug.beginFile() will crash, if Debug Utils has not yet been loaded (as it is executed during Lua root). To prevent this, always place Debug Utils at topmost position in your trigger editor. Likewise, remaining beginFile- and endFile-statements will crash after removing Debug Utils from your map, which most likely happens closely before official release. To prevent this kind of issue, it's advisable to use these statements instead:
Lua:
if Debug and Debug.beginFile then Debug.beginFile(<fileName>) end
... <file content>
if Debug and Debug.endFile then Debug.endFile() end

Stack Traces

Error Messages show a stack trace by default, listing the chain of function executions that let to the error in question. Such stack traces are a little bit imprecise in Warcraft 3 in the sense that they contain holes. Not every function in the execution chain is guaranteed to be part of the printed stack trace, but the existing information should still be helpful.

Looking at the stack trace from the "ExampleFile" example above, you see that file names are not repeated. In this case ExampleFile:7 <- 11, line 7 and 11 were part of the same file, so it was only mentioned once.

⚙️If you don't need stack traces, you can turn them off by setting SHOW_TRACE_ON_ERROR to false.

All references inside the stack trace are also affected by the war3map.lua-to-local-file conversion.

Credits to @HerlySQR for the original resource.

Error Handling (Library functions)
Lua theoretically offers the pcall and xpcall functions for manual error handling. Both try to execute another function and return a boolean indicating the success of the call plus either that functions return values (if successful) or an error message (if unsuccessful). These two error handlers are not that quick to use, though, because they require several lines of code to make them work with both the successful and unsuccessful case (printing the error message), making them tedious to use as a routine.

Debug.try(func, ...) -> func(...)

Debug Utils offers Debug.try(func, ...) (equivalently try(func, ...)) for the same purpose (where ... are the parameters passed to func). It does all the work for you and is really quick to use. It calls a potentially erroneous function in protected mode and displays an error message in case of an error, while still retaining all return values and letting your code continue as normal in the successful case.
Plus, it incorporates a stack trace into error messages, uses war3map.lua-reference-conversion (as stated previously) and interacts with other library functions such as Debug.log(...) (which we will discuss in the chapter about Error Investigation).

Manually applying Debug.try makes sense, if you can't rely on the automatic error handling. In particular, that is the case for error handling within the Lua root.

Example Use:
Lua:
---Consider this simple erroneous function
---@param unitId string
function CreateUnitForPlayerRed(unitId)
    return CreateUnit(0, FourCC(unitId),0,0,0)
end
u = CreateUnitForPlayerRed('hfoo') --doesn’t work and you want to know why.

Investigation with Debug.try:
Lua:
u = Debug.try(CreateUnitForPlayerRed, 'hfoo')
--or equivalently:
u = Debug.try(function() return CreateUnitForPlayerRed('hfoo') end)
which will display an error message like war3map.lua:633: bad argument #1 to CreateUnit ('player' expected).

assert(condition, errorMsg, ...) -> ...

assert(condition, errorMsg, ...) is a native Lua function. It throws an error consisting of the specified message, IF the specified condition fails (i.e. resolves to false or nil), and returns ... otherwise.
It only shows a visible error message if called in protected mode, i.e. inside pcall and xpcall and as such, also in Debug.try. That means, given that your settings USE_TRY_ON_TRIGGERADDACTION, USE_TRY_ON_CONDITION and USE_TRY_ON_TIMERSTART are set to true, you can freely use this inside any function that you execute within triggers and timers due to Debug Utils' automatic error handling functionality.

assert halts code execution, if its condition fails (i.e. code after it will not be executed anymore).

Example:
Lua:
Debug.beginFile("ExampleFile")

---Adds one to the input parameter
---@param x number
---@return number
function foo(x)
    assert(type(x)=='number', "wrong type of param #1 for foo: " .. tostring(x) .. " is not a number")
    return x+1
end

function bar()
    print(foo(true)) --will throw an error due to attempting to evaluate true+1
end

Debug.endFile()

The specified condition type(x)=='number' fails for the input true, so assert will print the following error:

assert-png.418751


As assert returns its ... arguments in case the condition holds, you can even rewrite foo as follows:

Lua:
function foo(x)
    return assert(type(x)=='number', "wrong type of param #1 for foo: " .. tostring(x) .. " is not a number", x) + 1
end

Do not write assert(type(x)=='number', "wrong type of param #1 for foo: " .. tostring(x) .. " is not a number", x+1), though, because it would throw an error inside the brackets, before assert is even executed.

Debug.assert(condition, errorMsg, ...) -> ...

Debug.assert(condition, errorMsg, ...) works exactly like assert(condition, errorMsg, ...), except that it also works outside of protected mode and it does not halt code execution. As such, you can use it in every function, no matter if that is called within Debug.try or not (for example within Lua root).
If used in protected mode, the fact that it doesn't halt code execution can obviously lead to another error being thrown directly afterwards.
Lua:
--Assuming the value true for x, this might print two errors (the second one only if in protected mode)
function foo(x)
    --Debug.assert prints an error, but doesn't halt code execution
    Debug.assert(type(x)=='number', "wrong type of param #1 for foo: " .. tostring(x) .. " is not a number")
    return x+1 --true+1 prints an error in protected mode
end

error(errorMsg)

error(errorMsg) is a native Lua function. It halts code execution and throws an error if in protected mode that can be picked up by the error handling functions pcall, xpcall and Debug.try.
As Debug Utils wraps all functions executed within triggers and timers in Debug.try, you can freely use error in such code (and it will contain the specified error message, document, line number, stack trace and Debug-log).

Debug.throwError(...)

Prints an error message on screen consisting of all arguments specified (plus document, line number, stack trace and Debug-log).
Works similar to error, except that it doesn't halt code execution and works outside protected mode.

Error Investigation
This chapter discusses the options Debug Utils gives you to investigate errors.

The most important tool has already been presented in the previous chapter: Using the Ingame Console to look at your data, while the buggy gamestate is still available.

Debug.log(...)

Debug.log(...) writes all arguments into the Debug-log, which is going to be printed upon every error catched by Debug Utils. This helps tracking valuable information such as function parameters that led to the actual error. But yes, this requires altering your code, restarting Wc3 and reproducing the error.

You can log any type of object. The Debug-log saves only one set of arguments per code-location, so you don't need to worry about that any periodically executed function including Debug.log(...) could spam your screen.

Example:
Lua:
Debug.beginFile("ExampleFile")

---Adds one to the input parameter
---@param x number
---@return number
function foo(x)
    Debug.log("Input param for foo:", x)
    Debug.log("Some more useful information")
    return x+1
end

function bar()
    print(foo(true)) --will throw an error due to attempting to evaluate true+1
end

Debug.endFile()

Resulting Error:

debug-log-png.418753


Note that Debug.log(...) adds its arguments to any subsequent error message, no matter if they are related to the issue or not.

table.tostring and table.print

Often, when we encounter bugs in our map, we want to take a look at those parts of our data structure that are potentially bug-related. We usually choose to print a bunch of values on screen, until we find something that looks suspicious.

Sometimes it can be a lot quicker to print a whole part of your data structure at once, i.e. a whole table including all keys and values.

We can do so by using table.tostring(T) or table.print(T) (which just prints table.tostring(T)).

Example:
Lua:
T = {function() end, x = "Hello", y = {true,false}}
table.print(T)

table-print-png.418754


Both functions also have two optional parameters: table.print(T, [depth:integer], [pretty_yn:boolean]).
Not specifying a depth will unpack all subtables of T, as you can see above. Consequently and unsurprisingly, table.print(T) on self-referential/recursive tables will lead to a stack overflow.
Better limit the depth up to which subtables will be unpacked by providing a sensible integer for the depth parameter. This will also improve readability for big tables.

Example:
Lua:
T = {function() end, x = "Hello", y = {true,false}}
table.print(T, 1)

table-print_depth-png.418755


The one-liner output of table.print fits well on screen, but can be hard to read. You can instead use its pretty version, which includes more linebreaks and indentation. Wc3 has limited ability to display linebreaks though, so preferably do this for small tables only. You can use both table.print(T, depth, pretty_yn) and table.print(T, pretty_yn) syntax. In short, just append true to make it pretty.
Credits to @Bribe for contributing this version.

Example:
Lua:
T = {function() end, x = "Hello", y = {true,false}}
table.print(T, true)

table-print_pretty-png.418756


Debug.wc3Type(object) -> string

Debug.wc3Type(object) or short Wc3Type(object) returns the Warcraft-3-type of Warcraft objects and the Lua-type of Lua-objects. Lua is not type-safe, so extracting the type actually stored at a certain place is often useful. Lua's native type function returns "userdata" for Warcraft objects (which is exactly their Lua type), hence the need for this Wc3-version.

Lua:
Debug.wc3Type( Player(0) ) --> "player"
Debug.wc3Type( GetPlayableMapRect() ) --> "rect"
Debug.wc3Type( {} ) --> "table"
Debug.wc3Type( 1.5 ) --> "number"

Debug.traceback() -> string

Returns the stack trace at the code location, where it is executed. Stack traces are printed on every error anyway, if the setting SHOW_TRACE_ON_ERROR is set to true. This library function however can be used at any point in code, not just in case of errors. This just returns a string, so you must print(Debug.traceback()) to see it on screen.
Note that the stack trace provided by Wc3 is sometimes incomplete. It can miss out function calls that were definitely part of the chain.
The trace only contains documents and line numbers. It does not currently include function names or even arguments passed. If you know a method to receive this kind of information, please write me a PM or leave a comment under this post.

Debug.getLine([depth:integer]) -> integer?

Debug.getLine() returns the line in war3map.lua, where this function is executed (NOT converted to local file).
This is mainly utilized by Debug Utils to feed the other library functions, but can have useful applications for your own debugging as well.
You can specify a depth d >= 0 to instead return the line number of the d-th function in the stack trace leading to this execution.

Lua:
--Consider this simple example:
function Third()                        --war3map.lua:1000
    print(Debug.getLine(depth))         --war3map.lua:1001
end                                     --war3map.lua:1002
                                        --war3map.lua:1003
function Second()                       --war3map.lua:1004
    Third()                             --war3map.lua:1005
end                                     --war3map.lua:1006
                                        --war3map.lua:1007
function First()                        --war3map.lua:1008
    Second()                            --war3map.lua:1009
end                                     --war3map.lua:1010

Given that your code executes First, the call of Debug.getLine(depth) would output 1001 for depth = 0, 1005 for depth = 1 and 1009 for depth = 2.

Debug.getLine(depth) can return nil for depth >= 4 (not sure about the exact threshold). This is, because the Wc3 stack trace contains nil-entries, which Debug.traceback() simply skips, but they are there. Be sure to either not use high choices for depth or include a nil-check, if you rely on the result being an integer.

Debug.getLocalErrorMsg(errorMsg) -> string

Debug.getLocalErrorMsg(errorMsg) takes a string in the format <document>:<lineNumber><restOfMsg> (referred to as error message, but it's honestly just any string in the mentioned format) and returns the same string with war3map.lua-references replaced by local ones, as defined by your usage of Debug.beginFile() and Debug.endFile(). That means, if <document> is "war3map.lua", then both <document> and <lineNumber> are replaced by their local counterparts. Otherwise, this just returns the input string.

Warnings for Nil-Globals
DebugUtils will print warnings on screen, if you attempt to read any global variable containing a nil-value (with the exceptions listed below). This feature is meant to spot any case where you forgot to initialize a variable with a value or misspelled a variable or function name, such as calling CraeteUnit instead of CreateUnit.

undeclaredglobals-png.419644


Remember that the Lua syntax check will not inform you about spelling errors, so this is merely an ingame warning replacement for something that we got for free during Jass-times.
Sure thing, Visual Studio Code and sumneko extension already do 90% of the spelling error prevention for you, but this feature still often comes in handy.

⚙️The feature is enabled by default. You can disable it by setting WARNING_FOR_NIL_GLOBALS = false (default: true).

Much like proper errors, warnings can include a stack trace, if you enable that in the settings.

⚙️SHOW_TRACE_FOR_NIL_GLOBALS = true (default: false).

Keep in mind though that Lua can't really distinguish undeclared globals (which we try to spot here) from those intentionally nilled, because the concept of global declaration doesn't actually exist: You can just use any global name without previous declaration. Thus, reading any such variable will simply return nil, no matter if intentional or not. To reduce the amount of false-positive warnings on intentionally nilled globals, Debug Utils disables warnings for all globals after they have been first initialized with any value (including nil). I.e. setting MyGlobal = nil (or to any other value) in your code will prevent future nil-warnings.

If you want to keep nil-warnings even after first initialization, you can do so with this setting:

⚙️EXCLUDE_INITIALIZED_GLOBALS_FROM_NIL_WARNINGS = false (default: true).

A special case are bj-variables, i.e. those globals present in Blizzard.lua. We have little control about how those are handled, so you can actively disable warnings for these, if you get any false-positives:

⚙️EXCLUDE_BJ_GLOBALS_FROM_NIL_WARNINGS = true (default: false).


Undeclared global warnings start to take effect after loading screen, because otherwise requirement checking would be affected and bring up many false-positive warnings. I.e. any line if someLibrary then would print a warning, if someLibrary didn't exist. Requirement checking during game runtime is still affected, so if you do so in your map, that may be a reason to turn the feature off or use the following library function instead:

Debug.disableNilWarningsFor("MyGlobalVariable") will disable nil-warnings for any particular global variable.

Print Caching and Print Duration
We often like to cluster print statements into our code during debugging. That is typically not applicable for code running during loading screen though, because loading screen prints will not show up in Wc3.

Debug Utils solves this issue by delaying all early print statements to after game start, so you can see them.

⚙️You can disable this behaviour by setting USE_PRINT_CACHE to false.

Warcraft's implementation of print automatically calculates the on-screen duration based on the length of the printed string. This can be annoying during debugging, so Debug Utils allows you to set a fixed duration instead. This can even be a very high number to keep them "forever".

⚙️Simply set PRINT_DURATION = <desiredDurationInSeconds> or set it to nil (default) to keep original behaviour.

Name Caching
Debug Utils will cache the string name for objects in global scope and override tostring to show these names instead of their memory position (as such, it also applies to print).
Objects refers to call-by-reference data, i.e. everything except strings, numbers, booleans and nil.

Lua:
print(CreateUnit) --printing a native
--> function: 0000016F29E16700  (if feature is turned off)
--> function: CreateUnit  (if feature is turned on)

⚙️Set USE_NAME_CACHE to false to deactivate this feature.

Global objects (including Wc3 objects)
Globals are added to the name cache during loading screen and at game start.
This basic feature includes all native globals and those you have created during loading screen.

Automatically add new globals to name cache

Debug Utils automatically adds new globals (i.e. created after game start) to the name cache.

⚙️Set AUTO_REGISTER_NEW_NAMES to false to deactivate this feature.

Lua:
T = {} --executed after game start
print(T) --> table: T

Debug Utils enables this feature by setting a __newindex metamethod to _G. As such, it only works for new names (because that's how __newindex operates).
Objects can only have one name each, so the first name will be preferred over later names (in case you are assigning the same object to a different variable).
As a consequence of these two facts, the name cache for objects added during map runtime can theoretically get outdated, as you can see in this example:

Lua:
U = {}
print(U) --> prints "U"
V = U
print(V) --> prints "U" (already had a name, first was preferred)
U = {} --> U now holds a new table, but this didn't trigger __newindex...
print(V) --> still prints "U", although U now doesn't hold the same table anymore
print(U) --> prints "table: 000001F34E982"

Despite this possibility to screw up object names, the automatic name feature should in general be very reliable. In doubt, you can always get the "old" name (table: 000001F34E982 or similar) by using Debug.oldTostring(object).

Also note that Debug Utils prevents any two different objects from getting the same name. If an object receives a name that was already assigned to another object, the name is transferred and the previous object gets nameless, showing up with the default <type>: <memPos> again (although this process couldn't take place in the example above, because we coded in a way that __newindex didn't even trigger).
As such, you are safe to compare two different objects by name, which I consider a basic requirement for any implementation of tostring. If the names are the same, then the objects are the same. If the names are different, the objects are different.

Automatic Name Caching for subtables of global scope

Debug Utils can even apply the automatic Name Caching for objects within subtables of global scope up to a specified maximum depth, if you enable that in the settings. Be sure about the implications listed below, before you do so.

Lua:
T = {loc = Location(0,0), sub = {loc2 = Location(0,0)} }
--Assume you have set the maximum depth to 1
print(T.loc) --> prints "locaton: T.loc" (this object is on depth 1)
print(T.sub) --> prints "table: sub" (depth 1)
print(T.sub.loc2) --> prints "location: 000001A29C8231" (depth 2, not added to name cache)

⚙️Set NAME_CACHE_DEPTH to an integer > 0 to let Debug Utils automatically register Names up to the specified depth (=distance from global scope).
Set to 0 (default) to deactivate name caching within subtables.

Caution: This feature is invasive and as such deactivated by default. It requires Debug Utils to set a __newindex metamethod to every subtable of _G up to the maximum depth to scan for new objects during runtime. This obviously also requires to create a metatable in case the subtable didn't already have one. Existing __newindex metamethods will be altered (i.e. name caching is added, while the old behaviour is being kept).
This behaviour should be safe in terms of functionality, but you can theoretically come up with very rare edge cases, where it can cause bugs. One such example would be checking the __newindex metamethod for equality to another function (might have returned true before the altering and false afterwards). I personally consider this type of bug so rare that it shouldn't happen for you whatsoever. Still, if you are unsure, leave this feature deactivated.

The creation of metatables obviously also has performance implications, because you effectively now create two tables instead of one when in depth-range of global scope. This shouldn't matter too much, though, because scenarios creating a lot of (temporary) tables nearly always happen in local scope, which is not affected by this feature.

Library functions

  • Debug.registerName(object:any, name:string)
    adds the specified name for the specified object to the name cache. Use this for any object not affected by the automatic features listed above, for example locals.
    Will overwrite the existing name for the object, if there is any.
    Will do nothing for primitive objects (booleans, numbers, strings, nil).
  • Debug.registerNamesFrom(parentTable:table, [parentTableName:string], [maxDepth:integer])
    will add names for all non-primitive values within the specified parentTable to the name cache, using the best applicable notation out of <parentTableName>.<stringKey> and <parentTableName>[<nonStingKey>].
    If you specify maxDepth, Debug.registerNamesFrom will also give names to values inside subtables of the parent table up to the specified depth (default is 1, i.e. only give names to direct elements of the parent table).
    You can also specify parentTableName, if you don't want to use the original one (you might find it too long already). If the parent table doesn't yet have a name, you obviously must specify this and the parent table will receive that name before applying it to its elements.
    In contrast to Debug.registerName, this does not replace existing names.
  • Debug.oldTostring(object)
    returns the old name of any object (i.e. <type>:<memPos> as if you weren't using the name cache).


Installation

Please conduct the following steps to use Debug Utils in your map:
  • Either open the attached map file LuaDebugUtils_v2.2.w3x, copy/paste the "Debug Utils" folder from the trigger editor into your own map (and you are good to go)
    OR create two new script files in your Trigger Editor ("DebugUtils" above "IngameConsole") and manually paste the code from the below spoilers into it.
  • In any case, make sure that DebugUtils is the topmost script in your trigger editor at all times. Some features rely on that fact.
  • If you are using Visual Studio Code (which you should), be sure to also copy the two script files (DebugUtils and IngameConsole) into your workspace.
  • Also import the attached common.1.33.v2.lua, blizzard.1.33.v2.lua and HiddenNatives.1.33.v2.lua into your VS Code workspace, if you haven't already. This is not directly related to the debug functionality, but will help a ton and avoid bugs in the first place.
After the installation, please take a look at the Configuration section of Debug Utils and Ingame Console (in both cases in their script files directly below their documentation). Some features might be desired, others might not, and you better set it up properly before getting surprised.

Lua:
do; local _, codeLoc = pcall(error, "", 2) --get line number where DebugUtils begins.
--[[
 -------------------------
 -- | Debug Utils 2.2 | --
 -------------------------

 --> https://www.hiveworkshop.com/threads/lua-debug-utils-incl-ingame-console.353720/

 - by Eikonium, with special thanks to:
    - @Bribe, for pretty table print, showing that xpcall's message handler executes before the stack unwinds and useful suggestions like name caching and stack trace improvements.
    - @Jampion, for useful suggestions like print caching and applying Debug.try to all code entry points
    - @Luashine, for useful feedback and building "WC3 Debug Console Paste Helper" (https://github.com/Luashine/wc3-debug-console-paste-helper#readme)
    - @HerlySQR, for showing a way to get a stack trace in Wc3 (https://www.hiveworkshop.com/threads/lua-getstacktrace.340841/)
    - @Macadamia, for showing a way to print warnings upon accessing nil globals, where this all started with (https://www.hiveworkshop.com/threads/lua-very-simply-trick-to-help-lua-users-track-syntax-errors.326266/)

-----------------------------------------------------------------------------------------------------------------------------
| Provides debugging utility for Wc3-maps using Lua.                                                                        |
|                                                                                                                           |
| Including:                                                                                                                |
|   1. Automatic ingame error messages upon running erroneous code from triggers or timers.                                 |
|   2. Ingame Console that allows you to execute code via Wc3 ingame chat.                                                  |
|   3. Automatic warnings upon reading nil globals (which also triggers after misspelling globals)                   |
|   4. Debug-Library functions for manual error handling.                                                                   |
|   5. Caching of loading screen print messages until game start (which simplifies error handling during loading screen)    |
|   6. Overwritten tostring/print-functions to show the actual string-name of an object instead of the memory position.     |
|   7. Conversion of war3map.lua-error messages to local file error messages.                                               |
|   8. Other useful debug utility (table.print and Debug.wc3Type)                                                           |
-----------------------------------------------------------------------------------------------------------------------------

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Installation:                                                                                                                                                             |
|                                                                                                                                                                           |
|   1. Copy the code (DebugUtils.lua, StringWidth.lua and IngameConsole.lua) into your map. Use script files (Ctrl+U) in your trigger editor, not text-based triggers!      |
|   2. Order the files: DebugUtils above StringWidth above IngameConsole. Make sure they are above ALL other scripts (crucial for local line number feature).               |
|   3. Adjust the settings in the settings-section further below to receive the debug environment that fits your needs.                                                     |
|                                                                                                                                                                           |
| Deinstallation:                                                                                                                                                           |
|                                                                                                                                                                           |
|  - Debug Utils is meant to provide debugging utility and as such, shall be removed or invalidated from the map closely before release.                                    |
|  - Optimally delete the whole Debug library. If that isn't suitable (because you have used library functions at too many places), you can instead replace Debug Utils     |
|    by the following line of code that will invalidate all Debug functionality (without breaking your code):                                                               |
|    Debug = setmetatable({try = function(...) return select(2,pcall(...)) end}, {__index = function(t,k) return DoNothing end}); try = Debug.try                           |
|  - If that is also not suitable for you (because your systems rely on the Debug functionality to some degree), at least set ALLOW_INGAME_CODE_EXECUTION to false.         |
|  - Be sure to test your map thoroughly after removing Debug Utils.                                                                                                        |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
* Documentation and API-Functions:
*
*       - All automatic functionality provided by Debug Utils can be deactivated using the settings directly below the documentation.
*
* -------------------------
* | Ingame Code Execution |
* -------------------------
*       - Debug Utils provides the ability to run code via chat command from within Wc3, if you have conducted step 3 from the installation section.
*       - You can either open the ingame console by typing "-console" into the chat, or directly execute code by typing "-exec <code>".
*       - See IngameConsole script for further documentation.
*
* ------------------
* | Error Handling |
* ------------------
*        - Debug Utils automatically applies error handling (i.e. Debug.try) to code executed by your triggers and timers (error handling means that error messages are printed on screen, if anything doesn't run properly).
*        - You can still use the below library functions for manual debugging.
*
*    Debug.try(funcToExecute, ...) -> ...
*        - Help function for debugging another function (funcToExecute) that prints error messages on screen, if funcToExecute fails to execute.
*        - DebugUtils will automatically apply this to all code run by your triggers and timers, so you rarely need to apply this manually (maybe on code run in the Lua root).
*        - Calls funcToExecute with the specified parameters (...) in protected mode (which means that following code will continue to run even if funcToExecute fails to execute).
*        - If the call is successful, returns the specified function's original return values (so p1 = Debug.try(Player, 0) will work fine).
*        - If the call is unsuccessful, prints an error message on screen (including stack trace and parameters you have potentially logged before the error occured)
*        - By default, the error message consists of a line-reference to war3map.lua (which you can look into by forcing a syntax error in WE or by exporting it from your map via File -> Export Script).
*          You can get more helpful references to local script files instead, see section about "Local script references".
*        - Example: Assume you have a code line like "func(param1,param2)", which doesn't work and you want to know why.
*           Option 1: Change it to "Debug.try(func, param1, param2)", i.e. separate the function from the parameters.
*           Option 2: Change it to "Debug.try(function() return func(param1, param2) end)", i.e. pack it into an anonymous function (optionally skip the return statement).
*    Debug.log(...)
*        - Logs the specified parameters to the Debug-log. The Debug-log will be printed upon the next error being catched by Debug.try, Debug.assert or Debug.throwError.
*        - The Debug-log will only hold one set of parameters per code-location. That means, if you call Debug.log() inside any function, only the params saved within the latest call of that function will be kept.
*    Debug.throwError(...)
*        - Prints an error message including document, line number, stack trace, previously logged parameters and all specified parameters on screen. Parameters can have any type.
*        - In contrast to Lua's native error function, this can be called outside of protected mode and doesn't halt code execution.
*    Debug.assert(condition:boolean, errorMsg:string, ...) -> ...
*        - Prints the specified error message including document, line number, stack trace and previously logged parameters on screen, IF the specified condition fails (i.e. resolves to false/nil).
*        - Returns ..., IF the specified condition holds.
*        - This works exactly like Lua's native assert, except that it also works outside of protected mode and does not halt code execution.
*    Debug.traceback() -> string
*        - Returns the stack trace at the position where this is called. You need to manually print it.
*    Debug.getLine([depth: integer]) -> integer?
*        - Returns the line in war3map.lua, where this function is executed.
*        - You can specify a depth d >= 1 to instead return the line, where the d-th function in the stack trace was called. I.e. depth = 2 will return the line of execution of the function that calls Debug.getLine.
*        - Due to Wc3's limited stack trace ability, this might sometimes return nil for depth >= 3, so better apply nil-checks on the result.
*    Debug.getLocalErrorMsg(errorMsg:string) -> string
*        - Takes an error message containing a file and a linenumber and converts war3map.lua-lines to local document lines as defined by uses of Debug.beginFile() and Debug.endFile().
*        - Error Msg must be formatted like "<document>:<linenumber><Rest>".
*
* ----------------------------
* | Warnings for nil-globals |
* ----------------------------
*        - DebugUtils will print warnings on screen, if you read any global variable in your code that contains nil.
*        - This feature is meant to spot any case where you forgot to initialize a variable with a value or misspelled a variable or function name, such as calling CraeteUnit instead of CreateUnit.
*        - By default, warnings are disabled for globals that have been initialized with any value (including nil). I.e. you can disable nil-warnings by explicitly setting MyGlobalVariable = nil. This behaviour can be changed in the settings.
*
*    Debug.disableNilWarningsFor(variableName:string)
*        - Manually disables nil-warnings for the specified global variable.
*        - Variable must be inputted as string, e.g. Debug.disableNilWarningsFor("MyGlobalVariable").
*
* -----------------
* | Print Caching |
* -----------------
*        - DebugUtils caches print()-calls occuring during loading screen and delays them to after game start.
*        - This also applies to loading screen error messages, so you can wrap erroneous parts of your Lua root in Debug.try-blocks and see the message after game start.
*
* -------------------------
* | Local File Stacktrace |
* -------------------------
*        - By default, error messages and stack traces printed by the error handling functionality of Debug Utils contain references to war3map.lua (a big file just appending all your local scripts).
*        - The Debug-library provides the two functions below to index your local scripts, activating local file names and line numbers (matching those in your IDE) instead of the war3map.lua ones.
*        - This allows you to inspect errors within your IDE (VSCode) instead of the World Editor.
*
*    Debug.beginFile(fileName: string [, depth: integer])
*        - Tells the Debug library that the specified file begins exactly here (i.e. in the line, where this is called).
*        - Using this improves stack traces of error messages. "war3map.lua"-references between <here> and the next Debug.endFile() will be converted to file-specific references.
*        - All war3map.lua-lines located between the call of Debug.beginFile(fileName) and the next call of Debug.beginFile OR Debug.endFile are treated to be part of "fileName".
*        - !!! To be called in the Lua root in Line 1 of every document you wish to track. Line 1 means exactly line 1, before any comment! This way, the line shown in the trace will exactly match your IDE.
*        - Depth can be ignored, except if you want to use a custom wrapper around Debug.beginFile(), in which case you need to set the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.beginFile().
*    Debug.endFile([depth: integer])
*        - Ends the current file that was previously begun by using Debug.beginFile(). War3map.lua-lines after this will not be converted until the next instance of Debug.beginFile().
*        - The next call of Debug.beginFile() will also end the previous one, so using Debug.endFile() is optional. Mainly recommended to use, if you prefer to have war3map.lua-references in a certain part of your script (such as within GUI triggers).
*        - Depth can be ignored, except if you want to use a custom wrapper around Debug.endFile(), you need to increase the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.endFile().
*
* ----------------
* | Name Caching |
* ----------------
*        - DebugUtils overwrites the tostring-function so that it prints the name of a non-primitive object (if available) instead of its memory position. The same applies to print().
*        - For instance, print(CreateUnit) will show "function: CreateUnit" on screen instead of "function: 0063A698".
*        - The table holding all those names is referred to as "Name Cache".
*        - All names of objects in global scope will automatically be added to the Name Cache both within Lua root and again at game start (to get names for overwritten natives and your own objects).
*        - New names entering global scope will also automatically be added, even after game start. The same applies to subtables of _G up to a depth of Debug.settings.NAME_CACHE_DEPTH.
*        - Objects within subtables will be named after their parent tables and keys. For instance, the name of the function within T = {{bla = function() end}} is "T[1].bla".
*        - The automatic adding doesn't work for objects saved into existing variables/keys after game start (because it's based on __newindex metamethod which simply doesn't trigger)
*        - You can manually add names to the name cache by using the following API-functions:
*
*    Debug.registerName(whichObject:any, name:string)
*        - Adds the specified object under the specified name to the name cache, letting tostring and print output "<type>: <name>" going foward.
*        - The object must be non-primitive, i.e. this won't work on strings, numbers and booleans.
*        - This will overwrite existing names for the specified object with the specified name.
*    Debug.registerNamesFrom(parentTable:table [, parentTableName:string] [, depth])
*        - Adds names for all values from within the specified parentTable to the name cache.
*        - Names for entries will be like "<parentTableName>.<key>" or "<parentTableName>[<key>]" (depending on the key type), using the existing name of the parentTable from the name cache.
*        - You can optionally specify a parentTableName to use that for the entry naming instead of the existing name. Doing so will also register that name for the parentTable, if it doesn't already has one.
*        - Specifying the empty string as parentTableName will suppress it in the naming and just register all values as "<key>". Note that only string keys will be considered this way.
*        - In contrast to Debug.registerName(), this function will NOT overwrite existing names, but just add names for new objects.
*    Debug.oldTostring(object:any) -> string
*        - The old tostring-function in case you still need outputs like "function: 0063A698".
*
* -----------------
* | Other Utility |
* -----------------
*
*    Debug.wc3Type(object:any) -> string
*        - Returns the Warcraft3-type of the input object. E.g. Debug.wc3Type(Player(0)) will return "player".
*        - Returns type(object), if used on Lua-objects.
*    table.tostring(whichTable [, depth:integer] [, pretty_yn:boolean])
*        - Creates a list of all (key,value)-pairs from the specified table. Also lists subtable entries up to the specified depth (unlimited, if not specified).
*        - E.g. for T = {"a", 5, {7}}, table.tostring(T) would output '{(1, "a"), (2, 5), (3, {(1, 7)})}' (if using concise style, i.e. pretty_yn being nil or false).
*        - Not specifying a depth can potentially lead to a stack overflow for self-referential tables (e.g X = {}; X[1] = X). Choose a sensible depth to prevent this (in doubt start with 1 and test upwards).
*        - Supports pretty style by setting pretty_yn to true. Pretty style is linebreak-separated, uses indentations and has other visual improvements. Use it on small tables only, because Wc3 can't show that many linebreaks at once.
*        - All of the following is valid syntax: table.tostring(T), table.tostring(T, depth), table.tostring(T, pretty_yn) or table.tostring(T, depth, pretty_yn).
*        - table.tostring is not multiplayer-synced.
*    table.print(whichTable [, depth:integer] [, pretty_yn:boolean])
*        - Prints table.tostring(...).
*
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------]]

    -- disable sumneko extension warnings for imported resource
    ---@diagnostic disable

    ----------------
    --| Settings |--
    ----------------

    Debug = {
        --BEGIN OF SETTINGS--
        settings = {
            --Ingame Error Messages
                SHOW_TRACE_ON_ERROR = true                      ---Set to true to show a stack trace on every error in addition to the regular message (msg sources: automatic error handling, Debug.try, Debug.throwError, ...)
            ,   INCLUDE_DEBUGUTILS_INTO_TRACE = true            ---Set to true to include lines from Debug Utils into the stack trace. Those show the source of error handling, which you might consider redundant.
            ,   USE_TRY_ON_TRIGGERADDACTION = true              ---Set to true for automatic error handling on TriggerAddAction (applies Debug.try on every trigger action).
            ,   USE_TRY_ON_CONDITION = true                     ---Set to true for automatic error handling on boolexpressions created via Condition() or Filter() (essentially applies Debug.try on every trigger condition).
            ,   USE_TRY_ON_TIMERSTART = true                    ---Set to true for automatic error handling on TimerStart (applies Debug.try on every timer callback).
            ,   USE_TRY_ON_ENUMFUNCS = true                     ---Set to true for automatic error handling on ForGroup, ForForce, EnumItemsInRect and EnumDestructablesInRect (applies Debug.try on every enum callback)
            ,   USE_TRY_ON_COROUTINES = true                    ---Set to true for improved stack traces on errors within coroutines (applies Debug.try on coroutine.create and coroutine.wrap). This lets stack traces point to the erroneous function executed within the coroutine (instead of the function creating the coroutine).
            --Ingame Console and -exec
            ,   ALLOW_INGAME_CODE_EXECUTION = true              ---Set to true to enable IngameConsole and -exec command.
            --Warnings for nil globals
            ,   WARNING_FOR_NIL_GLOBALS = true                  ---Set to true to print warnings upon accessing nil-globals (i.e. globals containing no value).
            ,   SHOW_TRACE_FOR_NIL_WARNINGS = false              ---Set to true to include a stack trace into nil-warnings.
            ,   EXCLUDE_BJ_GLOBALS_FROM_NIL_WARNINGS = false    ---Set to true to exclude bj_ variables from nil-warnings.
            ,   EXCLUDE_INITIALIZED_GLOBALS_FROM_NIL_WARNINGS = true  ---Set to true to disable warnings for initialized globals, (i.e. nil globals that held a value at some point will be treated intentionally nilled and no longer prompt warnings).
            --Print Caching
            ,   USE_PRINT_CACHE = true                          ---Set to true to let print()-calls during loading screen be cached until the game starts.
            ,   PRINT_DURATION = nil                            ---Adjust the duration in seconds that values printed by print() last on screen. Set to nil to use default duration (which depends on string length).
            --Name Caching
            ,   USE_NAME_CACHE = true                           ---Set to true to let tostring/print output the string-name of an object instead of its memory location (except for booleans/numbers/strings). E.g. print(CreateUnit) will output "function: CreateUnit" instead of "function: 0063A698".
            ,   AUTO_REGISTER_NEW_NAMES = true                  ---Automatically adds new names from global scope (and subtables of _G up to NAME_CACHE_DEPTH) to the name cache by adding metatables with the __newindex metamethod to ALL tables accessible from global scope.
            ,   NAME_CACHE_DEPTH = 4                            ---Set to 0 to only affect globals. Experimental feature: Set to an integer > 0 to also cache names for subtables of _G (up to the specified depth). Warning: This will alter the __newindex metamethod of subtables of _G (but not break existing functionality).
        }
        --END OF SETTINGS--
        --START OF CODE--
        ,   data = {
                nameCache = {}                                  ---@type table<any,string> contains the string names of any object in global scope (random for objects that have multiple names)
            ,   nameCacheMirror = {}                            ---@type table<string,any> contains the (name,object)-pairs of all objects in the name cache. Used to prevent name duplicates that might otherwise occur upon reassigning globals.
            ,   nameDepths = {}                                 ---@type table<any,integer> contains the depth of the name used by by any object in the name cache (i.e. the depth within the parentTable).
            ,   autoIndexedTables = {}                          ---@type table<table,boolean> contains (t,true), if DebugUtils already set a __newindex metamethod for name caching in t. Prevents double application.
            ,   paramLog = {}                                   ---@type table<string,string> saves logged information per code location. to be filled by Debug.log(), to be printed by Debug.try()
            ,   sourceMap = {{firstLine= 1,file='DebugUtils'}}  ---@type table<integer,{firstLine:integer,file:string,lastLine?:integer}> saves lines and file names of all documents registered via Debug.beginFile().
            ,   printCache = {n=0}                              ---@type string[] contains the strings that were attempted to print during loading screen.
            ,   globalWarningExclusions = {}                    ---@type table<string,boolean> contains global variable names that should be excluded from warnings.
        }
    }
    --localization
    local settings, paramLog, nameCache, nameDepths, autoIndexedTables, nameCacheMirror, sourceMap, printCache = Debug.settings, Debug.data.paramLog, Debug.data.nameCache, Debug.data.nameDepths, Debug.data.autoIndexedTables, Debug.data.nameCacheMirror, Debug.data.sourceMap, Debug.data.printCache

    --Write DebugUtils first line number to sourceMap:
    ---@diagnostic disable-next-line
    Debug.data.sourceMap[1].firstLine = tonumber(codeLoc:match(":\x25d+"):sub(2,-1))

    -------------------------------------------------
    --| File Indexing for local Error Msg Support |--
    -------------------------------------------------

    -- Functions for war3map.lua -> local file conversion for error messages.

    ---Returns the line number in war3map.lua, where this is called (for depth = 0).
    ---Choose a depth d > 0 to instead return the line, where the d-th function in the stack leading to this call is executed.
    ---@param depth? integer default: 0.
    ---@return number?
    function Debug.getLine(depth)
        depth = depth or 0
        local _, location = pcall(error, "", depth + 3) ---@diagnostic disable-next-line
        local line = location:match(":\x25d+") --extracts ":1000" from "war3map.lua:1000:..."
        return tonumber(line and line:sub(2,-1)) --check if line is nil before applying string.sub to prevent errors (nil can result from string.match above, although it should never do so in our case)
    end

    ---Tells the Debug library that the specified file begins exactly here (i.e. in the line, where this is called).
    ---
    ---Using this improves stack traces of error messages. Stack trace will have "war3map.lua"-references between this and the next Debug.endFile() converted to file-specific references.
    ---
    ---To be called in the Lua root in Line 1 of every file you wish to track! Line 1 means exactly line 1, before any comment! This way, the line shown in the trace will exactly match your IDE.
    ---
    ---If you want to use a custom wrapper around Debug.beginFile(), you need to increase the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.beginFile().
    ---@param fileName string
    ---@param depth? integer default: 0. Set to 1, if you call this from a wrapper (and use the wrapper in line 1 of every document).
    ---@param lastLine? integer Ignore this. For compatibility with Total Initialization.
    function Debug.beginFile(fileName, depth, lastLine)
        depth, fileName = depth or 0, fileName or '' --filename is not actually optional, we just default to '' to prevent crashes.
        local line = Debug.getLine(depth + 1)
        if line then --for safety reasons. we don't want to add a non-existing line to the sourceMap
            table.insert(sourceMap, {firstLine = line, file = fileName, lastLine = lastLine}) --automatically sorted list, because calls of Debug.beginFile happen logically in the order of the map script.
        end
    end

    ---Tells the Debug library that the file previously started with Debug.beginFile() ends here.
    ---This is in theory optional to use, as the next call of Debug.beginFile will also end the previous. Still good practice to always use this in the last line of every file.
    ---If you want to use a custom wrapper around Debug.endFile(), you need to increase the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.endFile().
    ---@param depth? integer
    function Debug.endFile(depth)
        depth = depth or 0
        local line = Debug.getLine(depth + 1)
        sourceMap[#sourceMap].lastLine = line
    end

    ---Takes an error message containing a file and a linenumber and converts both to local file and line as saved to Debug.sourceMap.
    ---@param errorMsg string must be formatted like "<document>:<linenumber><RestOfMsg>".
    ---@return string convertedMsg a string of the form "<localDocument>:<localLinenumber><RestOfMsg>"
    function Debug.getLocalErrorMsg(errorMsg)
        local startPos, endPos = errorMsg:find(":\x25d*") --start and end position of line number. The part before that is the document, part after the error msg.
        if startPos and endPos then --can be nil, if input string was not of the desired form "<document>:<linenumber><RestOfMsg>".
            local document, line, rest = errorMsg:sub(1, startPos), tonumber(errorMsg:sub(startPos+1, endPos)), errorMsg:sub(endPos+1, -1) --get error line in war3map.lua
            if document == 'war3map.lua:' and line then --only convert war3map.lua-references to local position. Other files such as Blizzard.j.lua are not converted (obiously).
                for i = #sourceMap, 1, -1 do --find local file containing the war3map.lua error line.
                    if line >= sourceMap[i].firstLine then --war3map.lua line is part of sourceMap[i].file
                        if not sourceMap[i].lastLine or line <= sourceMap[i].lastLine then --if lastLine is given, we must also check for it
                            return sourceMap[i].file .. ":" .. (line - sourceMap[i].firstLine + 1) .. rest
                        else --if line is larger than firstLine and lastLine of sourceMap[i], it is not part of a tracked file -> return global war3map.lua position.
                            break --prevent return within next step of the loop ("line >= sourceMap[i].firstLine" would be true again, but wrong file)
                        end
                    end
                end
            end
        end
        return errorMsg
    end
    local convertToLocalErrorMsg = Debug.getLocalErrorMsg

    ----------------------
    --| Error Handling |--
    ----------------------

    local concat
    ---Applies tostring() on all input params and concatenates them 4-space-separated.
    ---@param firstParam any
    ---@param ... any
    ---@return string
    concat = function(firstParam, ...)
        if select('#', ...) == 0 then
            return tostring(firstParam)
        end
        return tostring(firstParam) .. '    ' .. concat(...)
    end

    ---Returns the stack trace between the specified startDepth and endDepth.
    ---The trace lists file names and line numbers. File name is only listed, if it has changed from the previous traced line.
    ---The previous file can also be specified as an input parameter to suppress the first file name in case it's identical.
    ---@param startDepth integer
    ---@param endDepth integer
    ---@return string trace
    local function getStackTrace(startDepth, endDepth)
        local trace, separator = "", ""
        local _, currentFile, lastFile, tracePiece, lastTracePiece
        for loopDepth = startDepth, endDepth do --get trace on different depth level
            _, tracePiece = pcall(error, "", loopDepth) ---@type boolean, string
            tracePiece = convertToLocalErrorMsg(tracePiece)
            if #tracePiece > 0 and lastTracePiece ~= tracePiece then --some trace pieces can be empty, but there can still be valid ones beyond that
                currentFile = tracePiece:match("^.-:")
                --Hide DebugUtils in the stack trace (except main reference), if settings.INCLUDE_DEBUGUTILS_INTO_TRACE is set to true.
                if settings.INCLUDE_DEBUGUTILS_INTO_TRACE or (loopDepth == startDepth) or currentFile ~= "DebugUtils:" then
                    trace = trace .. separator .. ((currentFile == lastFile) and tracePiece:match(":\x25d+"):sub(2,-1) or tracePiece:match("^.-:\x25d+"))
                    lastFile, lastTracePiece, separator = currentFile, tracePiece, " <- "
                end
            end
        end
        return trace
    end

    ---Message Handler to be used by the try-function below.
    ---Adds stack trace plus formatting to the message and prints it.
    ---@param errorMsg string
    ---@param startDepth? integer default: 4 for use in xpcall
    local function errorHandler(errorMsg, startDepth)
        startDepth = startDepth or 4 --xpcall doesn't specify this param, so it must default to 4 for this case
        errorMsg = convertToLocalErrorMsg(errorMsg)
        --Print original error message and stack trace.
        print("|cffff5555ERROR at " .. errorMsg .. "|r")
        if settings.SHOW_TRACE_ON_ERROR then
            print("|cffff5555Traceback (most recent call first):|r")
            print("|cffff5555" .. getStackTrace(startDepth,200) .. "|r")
        end
        --Also print entries from param log, if there are any.
        for location, loggedParams in pairs(paramLog) do
            print("|cff888888Logged at " .. convertToLocalErrorMsg(location) .. loggedParams .. "|r")
            paramLog[location] = nil
        end
    end

    ---Tries to execute the specified function with the specified parameters in protected mode and prints an error message (including stack trace), if unsuccessful.
    ---
    ---Example use: Assume you have a code line like "CreateUnit(0,1,2)", which doesn't work and you want to know why.
    ---* Option 1: Change it to "Debug.try(CreateUnit, 0, 1, 2)", i.e. separate the function from the parameters.
    ---* Option 2: Change it to "Debug.try(function() return CreateUnit(0,1,2) end)", i.e. pack it into an anonymous function. You can skip the "return", if you don't need the return values.
    ---When no error occured, the try-function will return all values returned by the input function.
    ---When an error occurs, try will print the resulting error and stack trace.
    ---@param funcToExecute function the function to call in protected mode
    ---@param ... any params for the input-function
    ---@return ... any
    function Debug.try(funcToExecute, ...)
        return select(2, xpcall(funcToExecute, errorHandler,...))
    end
    ---@diagnostic disable-next-line lowercase-global
    try = Debug.try

    ---Prints "ERROR:" and the specified error objects on the Screen. Also prints the stack trace leading to the error. You can specify as many arguments as you wish.
    ---
    ---In contrast to Lua's native error function, this can be called outside of protected mode and doesn't halt code execution.
    ---@param ... any objects/errormessages to be printed (doesn't have to be strings)
    function Debug.throwError(...)
        errorHandler(getStackTrace(4,4) .. ": " .. concat(...), 5)
    end

    ---Prints the specified error message, if the specified condition fails (i.e. if it resolves to false or nil).
    ---
    ---Returns all specified arguments after the errorMsg, if the condition holds.
    ---
    ---In contrast to Lua's native assert function, this can be called outside of protected mode and doesn't halt code execution (even in case of condition failure).
    ---@param condition any actually a boolean, but you can use any object as a boolean.
    ---@param errorMsg string the message to be printed, if the condition fails
    ---@param ... any will be returned, if the condition holds
    function Debug.assert(condition, errorMsg, ...)
        if condition then
            return ...
        else
            errorHandler(getStackTrace(4,4) .. ": " .. errorMsg, 5)
        end
    end

    ---Returns the stack trace at the code position where this function is called.
    ---The returned string includes war3map.lua/blizzard.j.lua code positions of all functions from the stack trace in the order of execution (most recent call last). It does NOT include function names.
    ---@return string
    function Debug.traceback()
        return getStackTrace(3,200)
    end

    ---Saves the specified parameters to the debug log at the location where this function is called. The Debug-log will be printed for all affected locations upon the try-function catching an error.
    ---The log is unique per code location: Parameters logged at code line x will overwrite the previous ones logged at x. Parameters logged at different locations will all persist and be printed.
    ---@param ... any save any information, for instance the parameters of the function call that you are logging.
    function Debug.log(...)
        local _, location = pcall(error, "", 3) ---@diagnostic disable-next-line: need-check-nil
        paramLog[location or ''] = concat(...)
    end

    ----------------------------------
    --| Undeclared Global Warnings |--
    ----------------------------------
    
    --Utility function here. _G metatable behaviour is defined in Gamestart section further below.

    local globalWarningExclusions = Debug.data.globalWarningExclusions

    ---Disables nil-warnings for the specified variable.
    ---@param variableName string
    function Debug.disableNilWarningsFor(variableName)
        globalWarningExclusions[variableName] = true
    end

    ------------------------------------
    --| Name Caching (API-functions) |--
    ------------------------------------

    --Help-table. The registerName-functions below shall not work on call-by-value-types, i.e. booleans, strings and numbers (renaming a value of any primitive type doesn't make sense).
    local skipType = {boolean = true, string = true, number = true, ['nil'] = true}
    --Set weak keys to nameCache and nameDepths and weak values for nameCacheMirror to prevent garbage collection issues
    setmetatable(nameCache, {__mode = 'k'})
    setmetatable(nameDepths, getmetatable(nameCache))
    setmetatable(nameCacheMirror, {__mode = 'v'})

    ---Removes the name from the name cache, if already used for any object (freeing it for the new object). This makes sure that a name is always unique.
    ---This doesn't solve the 
    ---@param name string
    local function removeNameIfNecessary(name)
        if nameCacheMirror[name] then
            nameCache[nameCacheMirror[name]] = nil
            nameCacheMirror[name] = nil
        end
    end

    ---Registers a name for the specified object, which will be the future output for tostring(whichObject).
    ---You can overwrite existing names for whichObject by using this.
    ---@param whichObject any
    ---@param name string
    function Debug.registerName(whichObject, name)
        if not skipType[type(whichObject)] then
            removeNameIfNecessary(name)
            nameCache[whichObject] = name
            nameCacheMirror[name] = whichObject
            nameDepths[name] = 0
        end
    end

    ---Registers a new name to the nameCache as either just <key> (if parentTableName is the empty string), <table>.<key> (if parentTableName is given and string key doesn't contain whitespace) or <name>[<key>] notation (for other keys in existing tables).
    ---Only string keys without whitespace support <key>- and <table>.<key>-notation. All other keys require a parentTableName.
    ---@param parentTableName string | '""' empty string suppresses <table>-affix.
    ---@param key any
    ---@param object any only call-be-ref types allowed
    ---@param parentTableDepth? integer
    local function addNameToCache(parentTableName, key, object, parentTableDepth)
        parentTableDepth = parentTableDepth or -1
        --Don't overwrite existing names for the same object, don't add names for primitive types.
        if nameCache[object] or skipType[type(object)] then
            return
        end
        local name
        --apply dot-syntax for string keys without whitespace
        if type(key) == 'string' and not string.find(key, "\x25s") then
            if parentTableName == "" then
                name = key
                nameDepths[object] = 0
            else
                name =  parentTableName .. "." .. key
                nameDepths[object] = parentTableDepth + 1
            end
        --apply bracket-syntax for all other keys. This requires a parentTableName.
        elseif parentTableName ~= "" then
            name = type(key) == 'string' and ('"' .. key .. '"') or key
            name = parentTableName .. "[" .. tostring(name) .. "]"
            nameDepths[object] = parentTableDepth + 1
        end
        --Stop in cases without valid name (like parentTableName = "" and key = [1])
        if name then
            removeNameIfNecessary(name)
            nameCache[object] = name
            nameCacheMirror[name] = object
        end
    end

    ---Registers all call-by-reference objects in the given parentTable to the nameCache.
    ---Automatically filters out primitive objects and already registed Objects.
    ---@param parentTable table
    ---@param parentTableName? string
    local function registerAllObjectsInTable(parentTable, parentTableName)
        parentTableName = parentTableName or nameCache[parentTable] or ""
        --Register all call-by-ref-objects in parentTable
        for key, object in pairs(parentTable) do
            addNameToCache(parentTableName, key, object, nameDepths[parentTable])
        end
    end

    ---Adds names for all values of the specified parentTable to the name cache. Names will be "<parentTableName>.<key>" or "<parentTableName>[<key>]", depending on the key type.
    ---
    ---Example: Given a table T = {f = function() end, [1] = {}}, tostring(T.f) and tostring(T[1]) will output "function: T.f" and "table: T[1]" respectively after running Debug.registerNamesFrom(T).
    ---The name of T itself must either be specified as an input parameter OR have previously been registered. It can also be suppressed by inputting the empty string (so objects will just display by their own names).
    ---The names of objects in global scope are automatically registered during loading screen.
    ---@param parentTable table base table of which all entries shall be registered (in the Form parentTableName.objectName).
    ---@param parentTableName? string|'""' Nil: takes <parentTableName> as previously registered. Empty String: Skips <parentTableName> completely. String <s>: Objects will show up as "<s>.<objectName>".
    ---@param depth? integer objects within sub-tables up to the specified depth will also be added. Default: 1 (only elements of whichTable). Must be >= 1.
    ---@overload fun(parentTable:table, depth:integer)
    function Debug.registerNamesFrom(parentTable, parentTableName, depth)
        --Support overloaded definition fun(parentTable:table, depth:integer)
        if type(parentTableName) == 'number' then
            depth = parentTableName
            parentTableName = nil
        end
        --Apply default values
        depth = depth or 1
        parentTableName = parentTableName or nameCache[parentTable] or ""
        --add name of T in case it hasn't already
        if not nameCache[parentTable] and parentTableName ~= "" then
            Debug.registerName(parentTable, parentTableName)
        end
        --Register all call-by-ref-objects in parentTable. To be preferred over simple recursive approach to ensure that top level names are preferred.
        registerAllObjectsInTable(parentTable, parentTableName)
        --if depth > 1 was specified, also register Names from subtables.
        if depth > 1 then
            for _, object in pairs(parentTable) do
                if type(object) == 'table' then
                    Debug.registerNamesFrom(object, nil, depth - 1)
                end
            end
        end
    end

    -------------------------------------------
    --| Name Caching (Loading Screen setup) |--
    -------------------------------------------

    ---Registers all existing object names from global scope and Lua incorporated libraries to be used by tostring() overwrite below.
    local function registerNamesFromGlobalScope()
        --Add all names from global scope to the name cache.
        Debug.registerNamesFrom(_G, "")
        --Add all names of Warcraft-enabled Lua libraries as well:
        --Could instead add a depth to the function call above, but we want to ensure that these libraries are added even if the user has chosen depth 0.
        for _, lib in ipairs({coroutine, math, os, string, table, utf8, Debug}) do
            Debug.registerNamesFrom(lib)
        end
        --Add further names that are not accessible from global scope:
        --Player(i)
        for i = 0, GetBJMaxPlayerSlots() - 1 do
            Debug.registerName(Player(i), "Player(" .. i .. ")")
        end
    end

    --Set empty metatable to _G. __index is added when game starts (for "attempt to read nil-global"-errors), __newindex is added right below (for building the name cache).
    setmetatable(_G, getmetatable(_G) or {}) --getmetatable(_G) should always return nil provided that DebugUtils is the topmost script file in the trigger editor, but we still include this for safety-

    -- Save old tostring into Debug Library before overwriting it.
    Debug.oldTostring = tostring

    if settings.USE_NAME_CACHE then
        local oldTostring = tostring
        tostring = function(obj) --new tostring(CreateUnit) prints "function: CreateUnit"
            --tostring of non-primitive object is NOT guaranteed to be like "<type>:<hex>", because it might have been changed by some __tostring-metamethod.
            if settings.USE_NAME_CACHE then --return names from name cache only if setting is enabled. This allows turning it off during runtime (via Ingame Console) to revert to old tostring.
                return nameCache[obj] and ((oldTostring(obj):match("^.-: ") or (oldTostring(obj) .. ": ")) .. nameCache[obj]) or oldTostring(obj)
            end
            return Debug.oldTostring(obj)
        end
        --Add names to Debug.data.objectNames within Lua root. Called below the other Debug-stuff to get the overwritten versions instead of the original ones.
        registerNamesFromGlobalScope()

        --Prepare __newindex-metamethod to automatically add new names to the name cache
        if settings.AUTO_REGISTER_NEW_NAMES then
            local nameRegisterNewIndex
            ---__newindex to be used for _G (and subtables up to a certain depth) to automatically register new names to the nameCache.
            ---Tables in global scope will use their own name. Subtables of them will use <parentName>.<childName> syntax.
            ---Global names don't support container[key]-notation (because "_G[...]" is probably not desired), so we only register string type keys instead of using prettyTostring.
            ---@param t table
            ---@param k any
            ---@param v any
            ---@param skipRawset? boolean set this to true when combined with another __newindex. Suppresses rawset(t,k,v) (because the other __newindex is responsible for that).
            nameRegisterNewIndex = function(t,k,v, skipRawset)
                local parentDepth = nameDepths[t] or 0
                --Make sure the parent table has an existing name before using it as part of the child name
                if t == _G or nameCache[t] then
                    local existingName = nameCache[v]
                    if not existingName then
                        addNameToCache((t == _G and "") or nameCache[t], k, v, parentDepth)
                    end
                    --If v is a table and the parent table has a valid name, inherit __newindex to v's existing metatable (or create a new one), if that wasn't already done.
                    if type(v) == 'table' and nameDepths[v] < settings.NAME_CACHE_DEPTH then
                        if not existingName then
                            --If v didn't have a name before, also add names for elements contained in v by construction (like v = {x = function() end} ).
                            Debug.registerNamesFrom(v, settings.NAME_CACHE_DEPTH - nameDepths[v])
                        end
                        --Apply __newindex to new tables.
                        if not autoIndexedTables[v] then
                            autoIndexedTables[v] = true
                            local mt = getmetatable(v)
                            if not mt then
                                mt = {}
                                setmetatable(v, mt) --only use setmetatable when we are sure there wasn't any before to prevent issues with "__metatable"-metamethod.
                            end
                            ---@diagnostic disable-next-line: assign-type-mismatch
                            local existingNewIndex = mt.__newindex
                            local isTable_yn = (type(existingNewIndex) == 'table')
                            --If mt has an existing __newindex, add the name-register effect to it (effectively create a new __newindex using the old)
                            if existingNewIndex then
                                mt.__newindex = function(t,k,v)
                                    nameRegisterNewIndex(t,k,v, true) --setting t[k] = v might not be desired in case of existing newindex. Skip it and let existingNewIndex make the decision.
                                    if isTable_yn then
                                        existingNewIndex[k] = v
                                    else
                                        return existingNewIndex(t,k,v)
                                    end
                                end
                            else
                            --If mt doesn't have an existing __newindex, add one that adds the object to the name cache.
                                mt.__newindex = nameRegisterNewIndex
                            end
                        end
                    end
                end
                --Set t[k] = v.
                if not skipRawset then
                    rawset(t,k,v)
                end
            end

            --Apply metamethod to _G.
            local existingNewIndex = getmetatable(_G).__newindex --should always be nil provided that DebugUtils is the topmost script in your trigger editor. Still included for safety.
            local isTable_yn = (type(existingNewIndex) == 'table')
            if existingNewIndex then
                getmetatable(_G).__newindex = function(t,k,v)
                    nameRegisterNewIndex(t,k,v, true)
                    if isTable_yn then
                        existingNewIndex[k] = v
                    else
                        existingNewIndex(t,k,v)
                    end
                end
            else
                getmetatable(_G).__newindex = nameRegisterNewIndex
            end
        end
    end

    ------------------------------------------------------
    --| Native Overwrite for Automatic Error Handling  |--
    ------------------------------------------------------

    --A table to store the try-wrapper for each function. This avoids endless re-creation of wrapper functions within the hooks below.
    --Weak keys ensure that garbage collection continues as normal.
    local tryWrappers = setmetatable({}, {__mode = 'k'}) ---@type table<function,function>
    local try = Debug.try

    ---Takes a function and returns a wrapper executing the same function within Debug.try.
    ---Wrappers are permanently stored (until the original function is garbage collected) to ensure that they don't have to be created twice for the same function.
    ---@param func? function
    ---@return function
    local function getTryWrapper(func)
        if func then
            tryWrappers[func] = tryWrappers[func] or function(...) return try(func, ...) end
        end
        return tryWrappers[func] --returns nil for func = nil (important for TimerStart overwrite below)
    end

    --Overwrite TriggerAddAction, TimerStart, Condition, Filter and Enum natives to let them automatically apply Debug.try.
    --Also overwrites coroutine.create and coroutine.wrap to let stack traces point to the function executed within instead of the function creating the coroutine.
    if settings.USE_TRY_ON_TRIGGERADDACTION then
        local originalTriggerAddAction = TriggerAddAction
        TriggerAddAction = function(whichTrigger, actionFunc)
            return originalTriggerAddAction(whichTrigger, getTryWrapper(actionFunc))
        end
    end
    if settings.USE_TRY_ON_TIMERSTART then
        local originalTimerStart = TimerStart
        TimerStart = function(whichTimer, timeout, periodic, handlerFunc)
            originalTimerStart(whichTimer, timeout, periodic, getTryWrapper(handlerFunc))
        end
    end
    if settings.USE_TRY_ON_CONDITION then
        local originalCondition = Condition
        Condition = function(func)
            return originalCondition(getTryWrapper(func))
        end
        Filter = Condition
    end
    if settings.USE_TRY_ON_ENUMFUNCS then
        local originalForGroup = ForGroup
        ForGroup = function(whichGroup, callback)
            originalForGroup(whichGroup, getTryWrapper(callback))
        end
        local originalForForce = ForForce
        ForForce = function(whichForce, callback)
            originalForForce(whichForce, getTryWrapper(callback))
        end
        local originalEnumItemsInRect = EnumItemsInRect
        EnumItemsInRect = function(r, filter, actionfunc)
            originalEnumItemsInRect(r, filter, getTryWrapper(actionfunc))
        end
        local originalEnumDestructablesInRect = EnumDestructablesInRect
        EnumDestructablesInRect = function(r, filter, actionFunc)
            originalEnumDestructablesInRect(r, filter, getTryWrapper(actionFunc))
        end
    end
    if settings.USE_TRY_ON_COROUTINES then
        local originalCoroutineCreate = coroutine.create
        ---@diagnostic disable-next-line: duplicate-set-field
        coroutine.create = function(f)
            return originalCoroutineCreate(getTryWrapper(f))
        end
        local originalCoroutineWrap = coroutine.wrap
        ---@diagnostic disable-next-line: duplicate-set-field
        coroutine.wrap = function(f)
            return originalCoroutineWrap(getTryWrapper(f))
        end
    end

    ------------------------------------------
    --| Cache prints during Loading Screen |--
    ------------------------------------------

    -- Apply the duration as specified in the settings.
    if settings.PRINT_DURATION then
        local display, getLocalPlayer, dur = DisplayTimedTextToPlayer, GetLocalPlayer, settings.PRINT_DURATION
        print = function(...) ---@diagnostic disable-next-line: param-type-mismatch
            display(getLocalPlayer(), 0, 0, dur, concat(...))
        end
    end

    -- Delay loading screen prints to after game start.
    if settings.USE_PRINT_CACHE then
        local oldPrint = print
        --loading screen print will write the values into the printCache
        print = function(...)
            if bj_gameStarted then
                oldPrint(...)
            else --during loading screen only: concatenate input arguments 4-space-separated, implicitely apply tostring on each, cache to table
                ---@diagnostic disable-next-line
                printCache.n = printCache.n + 1
                printCache[printCache.n] = concat(...)
            end
        end
    end

    -------------------------
    --| Modify Game Start |--
    -------------------------

    local originalMarkGameStarted = MarkGameStarted
    --Hook certain actions into the start of the game.
    MarkGameStarted = function()
        originalMarkGameStarted()
        if settings.WARNING_FOR_NIL_GLOBALS then
            local existingIndex = getmetatable(_G).__index
            local isTable_yn = (type(existingIndex) == 'table')
            getmetatable(_G).__index = function(t, k) --we made sure that _G has a metatable further above.
                --Don't show warning, if the variable name has been actively excluded or if it's a bj_ variable (and those are excluded).
                if  (not globalWarningExclusions[k]) and ((not settings.EXCLUDE_BJ_GLOBALS_FROM_NIL_WARNINGS) or string.sub(tostring(k),1,3) ~= 'bj_') then --prevents intentionally nilled bj-variables from triggering the check within Blizzard.j-functions, like bj_cineFadeFinishTimer.
                    print("Trying to read nil global at " .. getStackTrace(4,4) .. ": " .. tostring(k)
                        .. (settings.SHOW_TRACE_FOR_NIL_WARNINGS and "\nTraceback (most recent call first):\n" .. getStackTrace(4,200) or ""))
                end
                if existingIndex then
                    if isTable_yn then
                        return existingIndex[k]
                    end
                    return existingIndex(t,k)
                end
                return rawget(t,k)
            end

            if settings.EXCLUDE_INITIALIZED_GLOBALS_FROM_NIL_WARNINGS then
                local existingNewIndex = getmetatable(_G).__newindex
                local isTable_yn = (type(existingNewIndex) == 'table')
                getmetatable(_G).__newindex = function(t,k,v)
                    --First, exclude the initialized global from future warnings
                    globalWarningExclusions[k] = true
                    --Second, execute existing newindex, if there is one in place.
                    if existingNewIndex then
                        if isTable_yn then
                            existingNewIndex[k] = v
                        else
                            existingNewIndex(t,k,v)
                        end
                    else
                        rawset(t,k,v)
                    end
                end
            end
        end

        --Add names to Debug.data.objectNames again to ensure that overwritten natives also make it to the name cache.
        --Overwritten natives have a new value, but the old key, so __newindex didn't trigger. But we can be sure that objectNames[v] doesn't yet exist, so adding again is safe.
        if settings.USE_NAME_CACHE then
            for _,v in pairs(_G) do
                nameCache[v] = nil
            end
            registerNamesFromGlobalScope()
        end

        --Print messages that have been cached during loading screen.
        if settings.USE_PRINT_CACHE then
            --Note that we don't restore the old print. The overwritten variant only applies caching behaviour to loading screen prints anyway and "unhooking" always adds other risks.
            for _, str in ipairs(printCache) do
                print(str)
            end ---@diagnostic disable-next-line: cast-local-type
            printCache = nil --frees reference for the garbage collector
        end

        --Create triggers listening to "-console" and "-exec" chat input.
        if settings.ALLOW_INGAME_CODE_EXECUTION and IngameConsole then
            IngameConsole.createTriggers()
        end
    end

    ---------------------
    --| Other Utility |--
    ---------------------

    do
        ---Returns the type of a warcraft object as string, e.g. "unit" upon inputting a unit.
        ---@param input any
        ---@return string
        function Debug.wc3Type(input)
            local typeString = type(input)
            if typeString == 'userdata' then
                typeString = tostring(input) --tostring returns the warcraft type plus a colon and some hashstuff.
                return typeString:sub(1, (typeString:find(":", nil, true) or 0) -1) --string.find returns nil, if the argument is not found, which would break string.sub. So we need to replace by 0.
            else
                return typeString
            end
        end
        Wc3Type = Debug.wc3Type --for backwards compatibility

        local conciseTostring, prettyTostring

        ---Translates a table into a comma-separated list of its (key,value)-pairs. Also translates subtables up to the specified depth.
        ---E.g. {"a", 5, {7}} will display as '{(1, "a"), (2, 5), (3, {(1, 7)})}'.
        ---@param object any
        ---@param depth? integer default: unlimited. Unlimited depth will throw a stack overflow error on self-referential tables.
        ---@return string
        conciseTostring = function (object, depth)
            depth = depth or -1
            if type(object) == 'string' then
                return '"' .. object .. '"'
            elseif depth ~= 0 and type(object) == 'table' then
                local elementArray = {}
                local keyAsString
                for k,v in pairs(object) do
                    keyAsString = type(k) == 'string' and ('"' .. tostring(k) .. '"') or tostring(k)
                    table.insert(elementArray, '(' .. keyAsString .. ', ' .. conciseTostring(v, depth -1) .. ')')
                end
                return '{' .. table.concat(elementArray, ', ') .. '}'
            end
            return tostring(object)
        end

        ---Creates a list of all (key,value)-pairs from the specified table. Also lists subtable entries up to the specified depth.
        ---Major differences to concise print are:
        --- * Format: Linebreak-formatted instead of one-liner, uses "[key] = value" instead of "(key,value)"
        --- * Will also unpack tables used as keys
        --- * Also includes the table's memory position as returned by tostring(table).
        --- * Tables referenced multiple times will only be unpacked upon first encounter and abbreviated on subsequent encounters
        --- * As a consequence, pretty version can be executed with unlimited depth on self-referential tables.
        ---@param object any
        ---@param depth? integer default: unlimited.
        ---@param constTable table
        ---@param indent string
        ---@return string
        prettyTostring = function(object, depth, constTable, indent)
            depth = depth or -1
            local objType = type(object)
            if objType == "string" then
                return '"'..object..'"' --wrap the string in quotes.
            elseif objType == 'table' and depth ~= 0 then
                if not constTable[object] then
                    constTable[object] = tostring(object):gsub(":","")
                    if next(object)==nil then
                        return constTable[object]..": {}"
                    else
                        local mappedKV = {}
                        for k,v in pairs(object) do
                            table.insert(mappedKV, '\n  ' .. indent ..'[' .. prettyTostring(k, depth - 1, constTable, indent .. "  ") .. '] = ' .. prettyTostring(v, depth - 1, constTable, indent .. "  "))
                        end
                        return constTable[object]..': {'.. table.concat(mappedKV, ',') .. '\n'..indent..'}'
                    end
                end
            end
            return constTable[object] or tostring(object)
        end

        ---Creates a list of all (key,value)-pairs from the specified table. Also lists subtable entries up to the specified depth.
        ---Supports concise style and pretty style.
        ---Concise will display {"a", 5, {7}} as '{(1, "a"), (2, 5), (3, {(1, 7)})}'.
        ---Pretty is linebreak-separated, so consider table size before converting. Pretty also abbreviates tables referenced multiple times.
        ---Can be called like table.tostring(T), table.tostring(T, depth), table.tostring(T, pretty_yn) or table.tostring(T, depth, pretty_yn).
        ---table.tostring is not multiplayer-synced.
        ---@param whichTable table
        ---@param depth? integer default: unlimited
        ---@param pretty_yn? boolean default: false (concise)
        ---@return string
        ---@overload fun(whichTable:table, pretty_yn?:boolean):string
        function table.tostring(whichTable, depth, pretty_yn)
            --reassign input params, if function was called as table.tostring(whichTable, pretty_yn)
            if type(depth) == 'boolean' then
                pretty_yn = depth
                depth = -1
            end
            return pretty_yn and prettyTostring(whichTable, depth, {}, "") or conciseTostring(whichTable, depth)
        end

        ---Prints a list of (key,value)-pairs contained in the specified table and its subtables up to the specified depth.
        ---Supports concise style and pretty style. Pretty is linebreak-separated, so consider table size before printing.
        ---Can be called like table.print(T), table.print(T, depth), table.print(T, pretty_yn) or table.print(T, depth, pretty_yn).
        ---@param whichTable table
        ---@param depth? integer default: unlimited
        ---@param pretty_yn? boolean default: false (concise)
        ---@overload fun(whichTable:table, pretty_yn?:boolean)
        function table.print(whichTable, depth, pretty_yn)
            print(table.tostring(whichTable, depth, pretty_yn))
        end
    end
end
Debug.endFile()

Lua:
if Debug and Debug.beginFile then Debug.beginFile("IngameConsole") end
--[[

--------------------------
----| Ingame Console |----
--------------------------

/**********************************************
* Allows you to use the following ingame commands:
* "-exec <code>" to execute any code ingame.
* "-console" to start an ingame console interpreting any further chat input as code and showing both return values of function calls and error messages. Furthermore, the print function will print
*    directly to the console after it got started. You can still look up all print messages in the F12-log.
***********************
* -------------------
* |Using the console|
* -------------------
* Any (well, most) chat input by any player after starting the console is interpreted as code and directly executed. You can enter terms (like 4+5 or just any variable name), function calls (like print("bla"))
* and set-statements (like y = 5). If the code has any return values, all of them are printed to the console. Erroneous code will print an error message.
* Chat input starting with a hyphen is being ignored by the console, i.e. neither executed as code nor printed to the console. This allows you to still use other chat commands like "-exec" without prompting errors.
***********************
* ------------------
* |Multiline-Inputs|
* ------------------
* You can prevent a chat input from being immediately executed by preceeding it with the '>' character. All lines entered this way are halted, until any line not starting with '>' is being entered.
* The first input without '>' will execute all halted lines (and itself) in one chunk.
* Example of a chat input (the console will add an additional '>' to every line):
* >function a(x)
* >return x
* end
***********************
* Note that multiline inputs don't accept pure term evaluations, e.g. the following input is not supported and will prompt an error, while the same lines would have worked as two single-line inputs:
* >x = 5
* x
***********************
* -------------------
* |Reserved Keywords|
* -------------------
* The following keywords have a reserved functionality, i.e. are direct commands for the console and will not be interpreted as code:
* - 'help'          - will show a list of all reserved keywords along very short explanations.
* - 'exit'          - will shut down the console
* - 'share'         - will share the players console with every other player, allowing others to read and write into it. Will force-close other players consoles, if they have one active.
* - 'clear'         - will clear all text from the console, except the word 'clear'
* - 'lasttrace'     - will show the stack trace of the latest error that occured within IngameConsole
* - 'show'          - will show the console, after it was accidently hidden (you can accidently hide it by showing another multiboard, while the console functionality is still up and running).
* - 'printtochat'   - will let the print function return to normal behaviour (i.e. print to the chat instead of the console).
* - 'printtoconsole'- will let the print function print to the console (which is default behaviour).
* - 'autosize on'   - will enable automatic console resize depending on the longest string in the display. This is turned on by default.
* - 'autosize off'  - will disable automatic console resize and instead linebreak long strings into multiple lines.
* - 'textlang eng'  - lets the console use english Wc3 text language font size to compute linebreaks (look in your Blizzard launcher settings to find out)
* - 'textlang ger'  - lets the console use german Wc3 text language font size to compute linebreaks (look in your Blizzard launcher settings to find out)
***********************
* --------------
* |Paste Helper|
* --------------
* @Luashine has created a tool that simplifies pasting multiple lines of code from outside Wc3 into the IngameConsole.
* This is particularly useful, when you want to execute a large chunk of testcode containing several linebreaks.
* Goto: https://github.com/Luashine/wc3-debug-console-paste-helper#readme
*
*************************************************/
--]]

----------------
--| Settings |--
----------------

---@class IngameConsole
IngameConsole = {
    --Settings
    numRows = 20                        ---@type integer Number of Rows of the console (multiboard), excluding the title row. So putting 20 here will show 21 rows, first being the title row.
    ,   autosize = true                 ---@type boolean Defines, whether the width of the main Column automatically adjusts with the longest string in the display.
    ,   currentWidth = 0.5              ---@type number Current and starting Screen Share of the console main column.
    ,   mainColMinWidth = 0.3           ---@type number Minimum Screen share of the console main column.
    ,   mainColMaxWidth = 0.8           ---@type number Maximum Scren share of the console main column.
    ,   tsColumnWidth = 0.06            ---@type number Screen Share of the Timestamp Column
    ,   linebreakBuffer = 0.008         ---@type number Screen Share that is added to longest string in display to calculate the screen share for the console main column. Compensates for the small inaccuracy of the String Width function.
    ,   maxLinebreaks = 8               ---@type integer Defines the maximum amount of linebreaks, before the remaining output string will be cut and not further displayed.
    ,   printToConsole = true           ---@type boolean defines, if the print function should print to the console or to the chat
    ,   sharedConsole = false           ---@type boolean defines, if the console is displayed to each player at the same time (accepting all players input) or if all players much start their own console.
    ,   showTraceOnError = false        ---@type boolean defines, if the console shows a trace upon printing errors. Usually not too useful within console, because you have just initiated the erroneous call.
    ,   textLanguage = 'eng'            ---@type string text language of your Wc3 installation, which influences font size (look in the settings of your Blizzard launcher). Currently only supports 'eng' and 'ger'.
    ,   colors = {
        timestamp = "bbbbbb"            ---@type string Timestamp Color
        ,   singleLineInput = "ffffaa"  ---@type string Color to be applied to single line console inputs
        ,   multiLineInput = "ffcc55"   ---@type string Color to be applied to multi line console inputs
        ,   returnValue = "00ffff"      ---@type string Color applied to return values
        ,   error = "ff5555"            ---@type string Color to be applied to errors resulting of function calls
        ,   keywordInput = "ff00ff"     ---@type string Color to be applied to reserved keyword inputs (console reserved keywords)
        ,   info = "bbbbbb"             ---@type string Color to be applied to info messages from the console itself (for instance after creation or after printrestore)
    }
    --Privates
    ,   numCols = 2                     ---@type integer Number of Columns of the console (multiboard). Adjusting this requires further changes on code base.
    ,   player = nil                    ---@type player player for whom the console is being created
    ,   currentLine = 0                 ---@type integer Current Output Line of the console.
    ,   inputload = ''                  ---@type string Input Holder for multi-line-inputs
    ,   output = {}                     ---@type string[] Array of all output strings
    ,   outputTimestamps = {}           ---@type string[] Array of all output string timestamps
    ,   outputWidths = {}               ---@type number[] remembers all string widths to allow for multiboard resize
    ,   trigger = nil                   ---@type trigger trigger processing all inputs during console lifetime
    ,   multiboard = nil                ---@type multiboard
    ,   timer = nil                     ---@type timer gets started upon console creation to measure timestamps
    ,   errorHandler = nil              ---@type fun(errorMsg:string):string error handler to be used within xpcall. We create one per console to make it compatible with console-specific settings.
    ,   lastTrace = ''                  ---@type string trace of last error occured within console. To be printed via reserved keyword "lasttrace"
    --Statics
    ,   keywords = {}                   ---@type table<string,function> saves functions to be executed for all reserved keywords
    ,   playerConsoles = {}             ---@type table<player,IngameConsole> Consoles currently being active. up to one per player.
    ,   originalPrint = print           ---@type function original print function to restore, after the console gets closed.
}
IngameConsole.__index = IngameConsole
IngameConsole.__name = 'IngameConsole'

------------------------
--| Console Creation |--
------------------------

---Creates and opens up a new console.
---@param consolePlayer player player for whom the console is being created
---@return IngameConsole
function IngameConsole.create(consolePlayer)
    local new = {} ---@type IngameConsole
    setmetatable(new, IngameConsole)
    ---setup Object data
    new.player = consolePlayer
    new.output = {}
    new.outputTimestamps = {}
    new.outputWidths = {}
    --Timer
    new.timer = CreateTimer()
    TimerStart(new.timer, 3600., true, nil) --just to get TimeElapsed for printing Timestamps.
    --Trigger to be created after short delay, because otherwise it would fire on "-console" input immediately and lead to stack overflow.
    new:setupTrigger()
    --Multiboard
    new:setupMultiboard()
    --Create own error handler per console to be compatible with console-specific settings
    new:setupErrorHandler()
    --Share, if settings say so
    if IngameConsole.sharedConsole then
        new:makeShared() --we don't have to exit other players consoles, because we look for the setting directly in the class and there just logically can't be other active consoles.
    end
    --Welcome Message
    new:out('info', 0, false, "Console started. Any further chat input will be executed as code, except when beginning with \x22-\x22.")
    return new
end

---Creates the multiboard used for console display.
function IngameConsole:setupMultiboard()
    self.multiboard = CreateMultiboard()
    MultiboardSetRowCount(self.multiboard, self.numRows + 1) --title row adds 1
    MultiboardSetColumnCount(self.multiboard, self.numCols)
    MultiboardSetTitleText(self.multiboard, "Console")
    local mbitem
    for col = 1, self.numCols do
        for row = 1, self.numRows + 1 do --Title row adds 1
            mbitem = MultiboardGetItem(self.multiboard, row -1, col -1)
            MultiboardSetItemStyle(mbitem, true, false)
            MultiboardSetItemValueColor(mbitem, 255, 255, 255, 255)    -- Colors get applied via text color code
            MultiboardSetItemWidth(mbitem, (col == 1 and self.tsColumnWidth) or self.currentWidth )
            MultiboardReleaseItem(mbitem)
        end
    end
    mbitem = MultiboardGetItem(self.multiboard, 0, 0)
    MultiboardSetItemValue(mbitem, "|cffffcc00Timestamp|r")
    MultiboardReleaseItem(mbitem)
    mbitem = MultiboardGetItem(self.multiboard, 0, 1)
    MultiboardSetItemValue(mbitem, "|cffffcc00Line|r")
    MultiboardReleaseItem(mbitem)
    self:showToOwners()
end

---Creates the trigger that responds to chat events.
function IngameConsole:setupTrigger()
    self.trigger = CreateTrigger()
    TriggerRegisterPlayerChatEvent(self.trigger, self.player, "", false) --triggers on any input of self.player
    TriggerAddCondition(self.trigger, Condition(function() return string.sub(GetEventPlayerChatString(),1,1) ~= '-' end)) --console will not react to entered stuff starting with '-'. This still allows to use other chat orders like "-exec".
    TriggerAddAction(self.trigger, function() self:processInput(GetEventPlayerChatString()) end)
end

---Creates an Error Handler to be used by xpcall below.
---Adds stack trace plus formatting to the message.
function IngameConsole:setupErrorHandler()
    self.errorHandler = function(errorMsg)
        errorMsg = Debug.getLocalErrorMsg(errorMsg)
        local _, tracePiece, lastFile = nil, "", errorMsg:match("^.-:") or "<unknown>" -- errors on objects created within Ingame Console don't have a file and linenumber. Consider "x = {}; x[nil] = 5".
        local fullMsg = errorMsg .. "\nTraceback (most recent call first):\n" .. (errorMsg:match("^.-:\x25d+") or "<unknown>")
        --Get Stack Trace. Starting at depth 5 ensures that "error", "messageHandler", "xpcall" and the input error message are not included.
        for loopDepth = 5, 50 do --get trace on depth levels up to 50
            ---@diagnostic disable-next-line: cast-local-type, assign-type-mismatch
            _, tracePiece = pcall(error, "", loopDepth) ---@type boolean, string
            tracePiece = Debug.getLocalErrorMsg(tracePiece)
            if #tracePiece > 0 then --some trace pieces can be empty, but there can still be valid ones beyond that
                fullMsg = fullMsg .. " <- " .. ((tracePiece:match("^.-:") == lastFile) and tracePiece:match(":\x25d+"):sub(2,-1) or tracePiece:match("^.-:\x25d+"))
                lastFile = tracePiece:match("^.-:")
            end
        end
        self.lastTrace = fullMsg
        return "ERROR: " .. (self.showTraceOnError and fullMsg or errorMsg)
    end
end

---Shares this console with all players.
function IngameConsole:makeShared()
    local player
    for i = 0, GetBJMaxPlayers() -1 do
        player = Player(i)
        if (GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING) and (IngameConsole.playerConsoles[player] ~= self) then --second condition ensures that the player chat event is not added twice for the same player.
            IngameConsole.playerConsoles[player] = self
            TriggerRegisterPlayerChatEvent(self.trigger, player, "", false) --triggers on any input
        end
    end
    self.sharedConsole = true
end

---------------------
--|      In       |--
---------------------

---Processes a chat string. Each input will be printed. Incomplete multiline-inputs will be halted until completion. Completed inputs will be converted to a function and executed. If they have an output, it will be printed.
---@param inputString string
function IngameConsole:processInput(inputString)
    --if the input is a reserved keyword, conduct respective actions and skip remaining actions.
    if IngameConsole.keywords[inputString] then --if the input string is a reserved keyword
        self:out('keywordInput', 1, false, inputString)
        IngameConsole.keywords[inputString](self) --then call the method with the same name. IngameConsole.keywords["exit"](self) is just self.keywords:exit().
        return
    end
    --if the input is a multi-line-input, queue it into the string buffer (inputLoad), but don't yet execute anything
    if string.sub(inputString, 1, 1) == '>' then --multiLineInput
        inputString = string.sub(inputString, 2, -1)
        self:out('multiLineInput',2, false, inputString)
        self.inputload = self.inputload .. inputString .. '\n'
    else --if the input is either singleLineInput OR the last line of multiLineInput, execute the whole thing.
        self:out(self.inputload == '' and 'singleLineInput' or 'multiLineInput', 1, false, inputString)
        self.inputload = self.inputload .. inputString
        local loadedFunc, errorMsg = load("return " .. self.inputload) --adds return statements, if possible (works for term statements)
        if loadedFunc == nil then
            loadedFunc, errorMsg = load(self.inputload)
        end
        self.inputload = '' --empty inputload before execution of pcall. pcall can break (rare case, can for example be provoked with metatable.__tostring = {}), which would corrupt future console inputs.
        --manually catch case, where the input did not define a proper Lua statement (i.e. loadfunc is nil)
        local results = loadedFunc and table.pack(xpcall(loadedFunc, self.errorHandler)) or {false, "Input is not a valid Lua-statement: " .. errorMsg}
        --output error message (unsuccessful case) or return values (successful case)
        if not results[1] then --results[1] is the error status that pcall always returns. False stands for: error occured.
            self:out('error', 0, true, results[2]) -- second result of pcall is the error message in case an error occured
        elseif results.n > 1 then --Check, if there was at least one valid output argument. We check results.n instead of results[2], because we also get nil as a proper return value this way.
            self:out('returnValue', 0, true, table.unpack(results, 2, results.n))
        end
    end
end

----------------------
--|      Out       |--
----------------------

-- split color codes, split linebreaks, print lines separately, print load-errors, update string width, update text, error handling with stack trace.

---Duplicates Color coding around linebreaks to make each line printable separately.
---Operates incorrectly on lookalike color codes invalidated by preceeding escaped vertical bar (like "||cffffcc00bla|r").
---Also operates incorrectly on multiple color codes, where the first is missing the end sequence (like "|cffffcc00Hello |cff0000ffWorld|r")
---@param inputString string
---@return string, integer
function IngameConsole.spreadColorCodes(inputString)
    local replacementTable = {} --remembers all substrings to be replaced and their replacements.
    for foundInstance, color in inputString:gmatch("((|c\x25x\x25x\x25x\x25x\x25x\x25x\x25x\x25x).-|r)") do
        replacementTable[foundInstance] = foundInstance:gsub("(\r?\n)", "|r\x251" .. color)
    end
    return inputString:gsub("((|c\x25x\x25x\x25x\x25x\x25x\x25x\x25x\x25x).-|r)", replacementTable)
end

---Concatenates all inputs to one string, spreads color codes around line breaks and prints each line to the console separately.
---@param colorTheme? '"timestamp"'| '"singleLineInput"' | '"multiLineInput"' | '"result"' | '"keywordInput"' | '"info"' | '"error"' | '"returnValue"' Decides about the color to be applied. Currently accepted: 'timestamp', 'singleLineInput', 'multiLineInput', 'result', nil. (nil equals no colorTheme, i.e. white color)
---@param numIndentations integer Number of '>' chars that shall preceed the output
---@param hideTimestamp boolean Set to false to hide the timestamp column and instead show a "->" symbol.
---@param ... any the things to be printed in the console.
function IngameConsole:out(colorTheme, numIndentations, hideTimestamp, ...)
    local inputs = table.pack(...)
    for i = 1, inputs.n do
        inputs[i] = tostring(inputs[i]) --apply tostring on every input param in preparation for table.concat
    end
    --Concatenate all inputs (4-space-separated)
    local printOutput = table.concat(inputs, '    ', 1, inputs.n)
    printOutput = printOutput:find("(\r?\n)") and IngameConsole.spreadColorCodes(printOutput) or printOutput
    local substrStart, substrEnd = 1, 1
    local numLinebreaks, completePrint = 0, true
    repeat
        substrEnd = (printOutput:find("(\r?\n)", substrStart) or 0) - 1
        numLinebreaks, completePrint = self:lineOut(colorTheme, numIndentations, hideTimestamp, numLinebreaks, printOutput:sub(substrStart, substrEnd))
        hideTimestamp = true
        substrStart = substrEnd + 2
    until substrEnd == -1 or numLinebreaks > self.maxLinebreaks
    if substrEnd ~= -1 or not completePrint then
        self:lineOut('info', 0, false, 0, "Previous value not entirely printed after exceeding maximum number of linebreaks. Consider adjusting 'IngameConsole.maxLinebreaks'.")
    end
    self:updateMultiboard()
end

---Prints the given string to the console with the specified colorTheme and the specified number of indentations.
---Only supports one-liners (no \n) due to how multiboards work. Will add linebreaks though, if the one-liner doesn't fit into the given multiboard space.
---@param colorTheme? '"timestamp"'| '"singleLineInput"' | '"multiLineInput"' | '"result"' | '"keywordInput"' | '"info"' | '"error"' | '"returnValue"' Decides about the color to be applied. Currently accepted: 'timestamp', 'singleLineInput', 'multiLineInput', 'result', nil. (nil equals no colorTheme, i.e. white color)
---@param numIndentations integer Number of greater '>' chars that shall preceed the output
---@param hideTimestamp boolean Set to false to hide the timestamp column and instead show a "->" symbol.
---@param numLinebreaks integer
---@param printOutput string the line to be printed in the console.
---@return integer numLinebreaks, boolean hasPrintedEverything returns true, if everything could be printed. Returns false otherwise (can happen for very long strings).
function IngameConsole:lineOut(colorTheme, numIndentations, hideTimestamp, numLinebreaks, printOutput)
    --add preceeding greater chars
    printOutput = ('>'):rep(numIndentations) .. printOutput
    --Print a space instead of the empty string. This allows the console to identify, if the string has already been fully printed (see while-loop below).
    if printOutput == '' then
        printOutput = ' '
    end
    --Compute Linebreaks.
    local linebreakWidth = ((self.autosize and self.mainColMaxWidth) or self.currentWidth )
    local partialOutput = nil
    local maxPrintableCharPosition
    local printWidth
    while string.len(printOutput) > 0  and numLinebreaks <= self.maxLinebreaks do --break, if the input string has reached length 0 OR when the maximum number of linebreaks would be surpassed.
        --compute max printable substring (in one multiboard line)
        maxPrintableCharPosition, printWidth = IngameConsole.getLinebreakData(printOutput, linebreakWidth - self.linebreakBuffer, self.textLanguage)
        --adds timestamp to the first line of any output
        if numLinebreaks == 0 then
            partialOutput = printOutput:sub(1, numIndentations) .. ((IngameConsole.colors[colorTheme] and "|cff" .. IngameConsole.colors[colorTheme] .. printOutput:sub(numIndentations + 1, maxPrintableCharPosition) .. "|r") or printOutput:sub(numIndentations + 1, maxPrintableCharPosition)) --Colorize the output string, if a color theme was specified. IngameConsole.colors[colorTheme] can be nil.
            table.insert(self.outputTimestamps, "|cff" .. IngameConsole.colors['timestamp'] .. ((hideTimestamp and '            ->') or IngameConsole.formatTimerElapsed(TimerGetElapsed(self.timer))) .. "|r")
        else
            partialOutput = (IngameConsole.colors[colorTheme] and "|cff" .. IngameConsole.colors[colorTheme] .. printOutput:sub(1, maxPrintableCharPosition) .. "|r") or printOutput:sub(1, maxPrintableCharPosition) --Colorize the output string, if a color theme was specified. IngameConsole.colors[colorTheme] can be nil.
            table.insert(self.outputTimestamps, '            ..') --need a dummy entry in the timestamp list to make it line-progress with the normal output.
        end
        numLinebreaks = numLinebreaks + 1
        --writes output string and width to the console tables.
        table.insert(self.output, partialOutput)
        table.insert(self.outputWidths, printWidth + self.linebreakBuffer) --remember the Width of this printed string to adjust the multiboard size in case. 0.5 percent is added to avoid the case, where the multiboard width is too small by a tiny bit, thus not showing some string without spaces.
        --compute remaining string to print
        printOutput = string.sub(printOutput, maxPrintableCharPosition + 1, -1) --remaining string until the end. Returns empty string, if there is nothing left
    end
    self.currentLine = #self.output
    return numLinebreaks, string.len(printOutput) == 0 --printOutput is the empty string, if and only if everything has been printed
end

---Lets the multiboard show the recently printed lines.
function IngameConsole:updateMultiboard()
    local startIndex = math.max(self.currentLine - self.numRows, 0) --to be added to loop counter to get to the index of output table to print
    local outputIndex = 0
    local maxWidth = 0.
    local mbitem
    for i = 1, self.numRows do --doesn't include title row (index 0)
        outputIndex = i + startIndex
        mbitem = MultiboardGetItem(self.multiboard, i, 0)
        MultiboardSetItemValue(mbitem, self.outputTimestamps[outputIndex] or '')
        MultiboardReleaseItem(mbitem)
        mbitem = MultiboardGetItem(self.multiboard, i, 1)
        MultiboardSetItemValue(mbitem, self.output[outputIndex] or '')
        MultiboardReleaseItem(mbitem)
        maxWidth = math.max(maxWidth, self.outputWidths[outputIndex] or 0.) --looping through non-defined widths, so need to coalesce with 0
    end
    --Adjust Multiboard Width, if necessary.
    maxWidth = math.min(math.max(maxWidth, self.mainColMinWidth), self.mainColMaxWidth)
    if self.autosize and self.currentWidth ~= maxWidth then
        self.currentWidth = maxWidth
        for i = 1, self.numRows +1 do
            mbitem = MultiboardGetItem(self.multiboard, i-1, 1)
            MultiboardSetItemWidth(mbitem, maxWidth)
            MultiboardReleaseItem(mbitem)
        end
        self:showToOwners() --reshow multiboard to update item widths on the frontend
    end
end

---Shows the multiboard to all owners (one or all players)
function IngameConsole:showToOwners()
    if self.sharedConsole or GetLocalPlayer() == self.player then
        MultiboardDisplay(self.multiboard, true)
        MultiboardMinimize(self.multiboard, false)
    end
end

---Formats the elapsed time as "mm: ss. hh" (h being a hundreds of a sec)
function IngameConsole.formatTimerElapsed(elapsedInSeconds)
    return string.format("\x2502d: \x2502.f. \x2502.f", elapsedInSeconds // 60, math.fmod(elapsedInSeconds, 60.) // 1, math.fmod(elapsedInSeconds, 1) * 100)
end

---Computes the max printable substring for a given string and a given linebreakWidth (regarding a single line of console).
---Returns both the substrings last char position and its total width in the multiboard.
---@param stringToPrint string the string supposed to be printed in the multiboard console.
---@param linebreakWidth number the maximum allowed width in one line of the console, before a string must linebreak
---@param textLanguage string 'ger' or 'eng'
---@return integer maxPrintableCharPosition, number printWidth
function IngameConsole.getLinebreakData(stringToPrint, linebreakWidth, textLanguage)
    local loopWidth = 0.
    local bytecodes = table.pack(string.byte(stringToPrint, 1, -1))
    for i = 1, bytecodes.n do
        loopWidth = loopWidth + string.charMultiboardWidth(bytecodes[i], textLanguage)
        if loopWidth > linebreakWidth then
            return i-1, loopWidth - string.charMultiboardWidth(bytecodes[i], textLanguage)
        end
    end
    return bytecodes.n, loopWidth
end

-------------------------
--| Reserved Keywords |--
-------------------------

---Exits the Console
---@param self IngameConsole
function IngameConsole.keywords.exit(self)
    DestroyMultiboard(self.multiboard)
    DestroyTrigger(self.trigger)
    DestroyTimer(self.timer)
    IngameConsole.playerConsoles[self.player] = nil
    if next(IngameConsole.playerConsoles) == nil then --set print function back to original, when no one has an active console left.
        print = IngameConsole.originalPrint
    end
end

---Lets the console print to chat
---@param self IngameConsole
function IngameConsole.keywords.printtochat(self)
    self.printToConsole = false
    self:out('info', 0, false, "The print function will print to the normal chat.")
end

---Lets the console print to itself (default)
---@param self IngameConsole
function IngameConsole.keywords.printtoconsole(self)
    self.printToConsole = true
    self:out('info', 0, false, "The print function will print to the console.")
end

---Shows the console in case it was hidden by another multiboard before
---@param self IngameConsole
function IngameConsole.keywords.show(self)
    self:showToOwners() --might be necessary to do, if another multiboard has shown up and thereby hidden the console.
    self:out('info', 0, false, "Console is showing.")
end

---Prints all available reserved keywords plus explanations.
---@param self IngameConsole
function IngameConsole.keywords.help(self)
    self:out('info', 0, false, "The Console currently reserves the following keywords:")
    self:out('info', 0, false, "'help' shows the text you are currently reading.")
    self:out('info', 0, false, "'exit' closes the console.")
    self:out('info', 0, false, "'lasttrace' shows the stack trace of the latest error that occured within IngameConsole.")
    self:out('info', 0, false, "'share' allows other players to read and write into your console, but also force-closes their own consoles.")
    self:out('info', 0, false, "'clear' clears all text from the console.")
    self:out('info', 0, false, "'show' shows the console. Sensible to use, when displaced by another multiboard.")
    self:out('info', 0, false, "'printtochat' lets Wc3 print text to normal chat again.")
    self:out('info', 0, false, "'printtoconsole' lets Wc3 print text to the console (default).")
    self:out('info', 0, false, "'autosize on' enables automatic console resize depending on the longest line in the display.")
    self:out('info', 0, false, "'autosize off' retains the current console size.")
    self:out('info', 0, false, "'textlang eng' will use english text installation font size to compute linebreaks (default).")
    self:out('info', 0, false, "'textlang ger' will use german text installation font size to compute linebreaks.")
    self:out('info', 0, false, "Preceeding a line with '>' prevents immediate execution, until a line not starting with '>' has been entered.")
end

---Clears the display of the console.
---@param self IngameConsole
function IngameConsole.keywords.clear(self)
    self.output = {}
    self.outputTimestamps = {}
    self.outputWidths = {}
    self.currentLine = 0
    self:out('keywordInput', 1, false, 'clear') --we print 'clear' again. The keyword was already printed by self:processInput, but cleared immediately after.
end

---Shares the console with other players in the same game.
---@param self IngameConsole
function IngameConsole.keywords.share(self)
    for _, console in pairs(IngameConsole.playerConsoles) do
        if console ~= self then
            IngameConsole.keywords['exit'](console) --share was triggered during console runtime, so there potentially are active consoles of others players that need to exit.
        end
    end
    self:makeShared()
    self:showToOwners() --showing it to the other players.
    self:out('info', 0,false, "The console of player " .. GetConvertedPlayerId(self.player) .. " is now shared with all players.")
end

---Enables auto-sizing of console (will grow and shrink together with text size)
---@param self IngameConsole
IngameConsole.keywords["autosize on"] = function(self)
    self.autosize = true
    self:out('info', 0,false, "The console will now change size depending on its content.")
end

---Disables auto-sizing of console
---@param self IngameConsole
IngameConsole.keywords["autosize off"] = function(self)
    self.autosize = false
    self:out('info', 0,false, "The console will retain the width that it currently has.")
end

---Lets linebreaks be computed by german font size
---@param self IngameConsole
IngameConsole.keywords["textlang ger"] = function(self)
    self.textLanguage = 'ger'
    self:out('info', 0,false, "Linebreaks will now compute with respect to german text installation font size.")
end

---Lets linebreaks be computed by english font size
---@param self IngameConsole
IngameConsole.keywords["textlang eng"] = function(self)
    self.textLanguage = 'eng'
    self:out('info', 0,false, "Linebreaks will now compute with respect to english text installation font size.")
end

---Prints the stack trace of the latest error that occured within IngameConsole.
---@param self IngameConsole
IngameConsole.keywords["lasttrace"] = function(self)
    self:out('error', 0,false, self.lastTrace)
end

--------------------
--| Main Trigger |--
--------------------

do
    --Actions to be executed upon typing -exec
    local function execCommand_Actions()
        local input = string.sub(GetEventPlayerChatString(),7,-1)
        print("Executing input: |cffffff44" .. input .. "|r")
        --try preceeding the input by a return statement (preparation for printing below)
        local loadedFunc, errorMsg = load("return ".. input)
        if not loadedFunc then --if that doesn't produce valid code, try without return statement
            loadedFunc, errorMsg = load(input)
        end
        --execute loaded function in case the string defined a valid function. Otherwise print error.
        if errorMsg then
            print("|cffff5555Invalid Lua-statement: " .. Debug.getLocalErrorMsg(errorMsg) .. "|r")
        else
            ---@diagnostic disable-next-line: param-type-mismatch
            local results = table.pack(Debug.try(loadedFunc))
            if results[1] ~= nil or results.n > 1 then
                for i = 1, results.n do
                    results[i] = tostring(results[i])
                end
                --concatenate all function return values to one colorized string
                print("|cff00ffff" .. table.concat(results, '    ', 1, results.n) .. "|r")
            end
        end
    end

    local function execCommand_Condition()
        return string.sub(GetEventPlayerChatString(), 1, 6) == "-exec "
    end

    local function startIngameConsole()
        --if the triggering player already has a console, show that console and stop executing further actions
        if IngameConsole.playerConsoles[GetTriggerPlayer()] then
            IngameConsole.playerConsoles[GetTriggerPlayer()]:showToOwners()
            return
        end
        --create Ingame Console object
        IngameConsole.playerConsoles[GetTriggerPlayer()] = IngameConsole.create(GetTriggerPlayer())
        --overwrite print function
        print = function(...)
            IngameConsole.originalPrint(...) --the new print function will also print "normally", but clear the text immediately after. This is to add the message to the F12-log.
            if IngameConsole.playerConsoles[GetLocalPlayer()] and IngameConsole.playerConsoles[GetLocalPlayer()].printToConsole then
                ClearTextMessages() --clear text messages for all players having an active console
            end
            for player, console in pairs(IngameConsole.playerConsoles) do
                if console.printToConsole and (player == console.player) then --player == console.player ensures that the console only prints once, even if the console was shared among all players
                    console:out(nil, 0, false, ...)
                end
            end
        end
    end

    ---Creates the triggers listening to "-console" and "-exec" chat input.
    ---Being executed within DebugUtils (MarkGameStart overwrite).
    function IngameConsole.createTriggers()
        --Exec
        local execTrigger = CreateTrigger()
        TriggerAddCondition(execTrigger, Condition(execCommand_Condition))
        TriggerAddAction(execTrigger, execCommand_Actions)
        --Real Console
        local consoleTrigger = CreateTrigger()
        TriggerAddAction(consoleTrigger, startIngameConsole)
        --Events
        for i = 0, GetBJMaxPlayers() -1 do
            TriggerRegisterPlayerChatEvent(execTrigger, Player(i), "-exec ", false)
            TriggerRegisterPlayerChatEvent(consoleTrigger, Player(i), "-console", true)
        end
    end
end

--[[
    used by Ingame Console to determine multiboard size
    every unknown char will be treated as having default width (see constants below)
--]]

do
    ----------------------------
    ----| String Width API |----
    ----------------------------

    local multiboardCharTable = {}                        ---@type table  -- saves the width in screen percent (on 1920 pixel width resolutions) that each char takes up, when displayed in a multiboard.
    local DEFAULT_MULTIBOARD_CHAR_WIDTH = 1. / 128.        ---@type number    -- used for unknown chars (where we didn't define a width in the char table)
    local MULTIBOARD_TO_PRINT_FACTOR = 1. / 36.            ---@type number    -- 36 is actually the lower border (longest width of a non-breaking string only consisting of the letter "i")

    ---Returns the width of a char in a multiboard, when inputting a char (string of length 1) and 0 otherwise.
    ---also returns 0 for non-recorded chars (like ` and ´ and ß and § and €)
    ---@param char string | integer integer bytecode representations of chars are also allowed, i.e. the results of string.byte().
    ---@param textlanguage? '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
    ---@return number
    function string.charMultiboardWidth(char, textlanguage)
        return multiboardCharTable[textlanguage or 'eng'][char] or DEFAULT_MULTIBOARD_CHAR_WIDTH
    end

    ---returns the width of a string in a multiboard (i.e. output is in screen percent)
    ---unknown chars will be measured with default width (see constants above)
    ---@param multichar string
    ---@param textlanguage? '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
    ---@return number
    function string.multiboardWidth(multichar, textlanguage)
        local chartable = table.pack(multichar:byte(1,-1)) --packs all bytecode char representations into a table
        local charWidth = 0.
        for i = 1, chartable.n do
            charWidth = charWidth + string.charMultiboardWidth(chartable[i], textlanguage)
        end
        return charWidth
    end

    ---The function should match the following criteria: If the value returned by this function is smaller than 1.0, than the string fits into a single line on screen.
    ---The opposite is not necessarily true (but should be true in the majority of cases): If the function returns bigger than 1.0, the string doesn't necessarily break.
    ---@param char string | integer integer bytecode representations of chars are also allowed, i.e. the results of string.byte().
    ---@param textlanguage? '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
    ---@return number
    function string.charPrintWidth(char, textlanguage)
        return string.charMultiboardWidth(char, textlanguage) * MULTIBOARD_TO_PRINT_FACTOR
    end

    ---The function should match the following criteria: If the value returned by this function is smaller than 1.0, than the string fits into a single line on screen.
    ---The opposite is not necessarily true (but should be true in the majority of cases): If the function returns bigger than 1.0, the string doesn't necessarily break.
    ---@param multichar string
    ---@param textlanguage? '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
    ---@return number
    function string.printWidth(multichar, textlanguage)
        return string.multiboardWidth(multichar, textlanguage) * MULTIBOARD_TO_PRINT_FACTOR
    end

    ----------------------------------
    ----| String Width Internals |----
    ----------------------------------

    ---@param charset '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
    ---@param char string|integer either the char or its bytecode
    ---@param lengthInScreenWidth number
    local function setMultiboardCharWidth(charset, char, lengthInScreenWidth)
        multiboardCharTable[charset] = multiboardCharTable[charset] or {}
        multiboardCharTable[charset][char] = lengthInScreenWidth
    end

    ---numberPlacements says how often the char can be placed in a multiboard column, before reaching into the right bound.
    ---@param charset '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
    ---@param char string|integer either the char or its bytecode
    ---@param numberPlacements integer
    local function setMultiboardCharWidthBase80(charset, char, numberPlacements)
        setMultiboardCharWidth(charset, char, 0.8 / numberPlacements) --1-based measure. 80./numberPlacements would result in Screen Percent.
        setMultiboardCharWidth(charset, char:byte(1,-1), 0.8 / numberPlacements)
    end

    -- Set Char Width for all printable ascii chars in screen width (1920 pixels). Measured on a 80percent screen width multiboard column by counting the number of chars that fit into it.
    -- Font size differs by text install language and patch (1.32- vs. 1.33+)
    if BlzGetUnitOrderCount then --identifies patch 1.33+
        --German font size for patch 1.33+
        setMultiboardCharWidthBase80('ger', "a", 144)
        setMultiboardCharWidthBase80('ger', "b", 131)
        setMultiboardCharWidthBase80('ger', "c", 144)
        setMultiboardCharWidthBase80('ger', "d", 120)
        setMultiboardCharWidthBase80('ger', "e", 131)
        setMultiboardCharWidthBase80('ger', "f", 240)
        setMultiboardCharWidthBase80('ger', "g", 120)
        setMultiboardCharWidthBase80('ger', "h", 131)
        setMultiboardCharWidthBase80('ger', "i", 288)
        setMultiboardCharWidthBase80('ger', "j", 288)
        setMultiboardCharWidthBase80('ger', "k", 144)
        setMultiboardCharWidthBase80('ger', "l", 288)
        setMultiboardCharWidthBase80('ger', "m", 85)
        setMultiboardCharWidthBase80('ger', "n", 131)
        setMultiboardCharWidthBase80('ger', "o", 120)
        setMultiboardCharWidthBase80('ger', "p", 120)
        setMultiboardCharWidthBase80('ger', "q", 120)
        setMultiboardCharWidthBase80('ger', "r", 206)
        setMultiboardCharWidthBase80('ger', "s", 160)
        setMultiboardCharWidthBase80('ger', "t", 206)
        setMultiboardCharWidthBase80('ger', "u", 131)
        setMultiboardCharWidthBase80('ger', "v", 131)
        setMultiboardCharWidthBase80('ger', "w", 96)
        setMultiboardCharWidthBase80('ger', "x", 144)
        setMultiboardCharWidthBase80('ger', "y", 131)
        setMultiboardCharWidthBase80('ger', "z", 144)
        setMultiboardCharWidthBase80('ger', "A", 103)
        setMultiboardCharWidthBase80('ger', "B", 120)
        setMultiboardCharWidthBase80('ger', "C", 111)
        setMultiboardCharWidthBase80('ger', "D", 103)
        setMultiboardCharWidthBase80('ger', "E", 144)
        setMultiboardCharWidthBase80('ger', "F", 160)
        setMultiboardCharWidthBase80('ger', "G", 96)
        setMultiboardCharWidthBase80('ger', "H", 96)
        setMultiboardCharWidthBase80('ger', "I", 240)
        setMultiboardCharWidthBase80('ger', "J", 240)
        setMultiboardCharWidthBase80('ger', "K", 120)
        setMultiboardCharWidthBase80('ger', "L", 144)
        setMultiboardCharWidthBase80('ger', "M", 76)
        setMultiboardCharWidthBase80('ger', "N", 96)
        setMultiboardCharWidthBase80('ger', "O", 90)
        setMultiboardCharWidthBase80('ger', "P", 131)
        setMultiboardCharWidthBase80('ger', "Q", 90)
        setMultiboardCharWidthBase80('ger', "R", 120)
        setMultiboardCharWidthBase80('ger', "S", 131)
        setMultiboardCharWidthBase80('ger', "T", 144)
        setMultiboardCharWidthBase80('ger', "U", 103)
        setMultiboardCharWidthBase80('ger', "V", 120)
        setMultiboardCharWidthBase80('ger', "W", 76)
        setMultiboardCharWidthBase80('ger', "X", 111)
        setMultiboardCharWidthBase80('ger', "Y", 120)
        setMultiboardCharWidthBase80('ger', "Z", 120)
        setMultiboardCharWidthBase80('ger', "1", 144)
        setMultiboardCharWidthBase80('ger', "2", 120)
        setMultiboardCharWidthBase80('ger', "3", 120)
        setMultiboardCharWidthBase80('ger', "4", 120)
        setMultiboardCharWidthBase80('ger', "5", 120)
        setMultiboardCharWidthBase80('ger', "6", 120)
        setMultiboardCharWidthBase80('ger', "7", 131)
        setMultiboardCharWidthBase80('ger', "8", 120)
        setMultiboardCharWidthBase80('ger', "9", 120)
        setMultiboardCharWidthBase80('ger', "0", 120)
        setMultiboardCharWidthBase80('ger', ":", 288)
        setMultiboardCharWidthBase80('ger', ";", 288)
        setMultiboardCharWidthBase80('ger', ".", 288)
        setMultiboardCharWidthBase80('ger', "#", 120)
        setMultiboardCharWidthBase80('ger', ",", 288)
        setMultiboardCharWidthBase80('ger', " ", 286) --space
        setMultiboardCharWidthBase80('ger', "'", 180)
        setMultiboardCharWidthBase80('ger', "!", 180)
        setMultiboardCharWidthBase80('ger', "$", 131)
        setMultiboardCharWidthBase80('ger', "&", 90)
        setMultiboardCharWidthBase80('ger', "/", 180)
        setMultiboardCharWidthBase80('ger', "(", 240)
        setMultiboardCharWidthBase80('ger', ")", 240)
        setMultiboardCharWidthBase80('ger', "=", 120)
        setMultiboardCharWidthBase80('ger', "?", 144)
        setMultiboardCharWidthBase80('ger', "^", 144)
        setMultiboardCharWidthBase80('ger', "<", 144)
        setMultiboardCharWidthBase80('ger', ">", 144)
        setMultiboardCharWidthBase80('ger', "-", 180)
        setMultiboardCharWidthBase80('ger', "+", 120)
        setMultiboardCharWidthBase80('ger', "*", 180)
        setMultiboardCharWidthBase80('ger', "|", 287) --2 vertical bars in a row escape to one. So you could print 960 ones in a line, 480 would display. Maybe need to adapt to this before calculating string width.
        setMultiboardCharWidthBase80('ger', "~", 111)
        setMultiboardCharWidthBase80('ger', "{", 240)
        setMultiboardCharWidthBase80('ger', "}", 240)
        setMultiboardCharWidthBase80('ger', "[", 240)
        setMultiboardCharWidthBase80('ger', "]", 240)
        setMultiboardCharWidthBase80('ger', "_", 144)
        setMultiboardCharWidthBase80('ger', "\x25", 103) --percent
        setMultiboardCharWidthBase80('ger', "\x5C", 205) --backslash
        setMultiboardCharWidthBase80('ger', "\x22", 120) --double quotation mark
        setMultiboardCharWidthBase80('ger', "\x40", 90) --at sign
        setMultiboardCharWidthBase80('ger', "\x60", 144) --Gravis (Accent)

        --English font size for patch 1.33+
        setMultiboardCharWidthBase80('eng', "a", 144)
        setMultiboardCharWidthBase80('eng', "b", 120)
        setMultiboardCharWidthBase80('eng', "c", 131)
        setMultiboardCharWidthBase80('eng', "d", 120)
        setMultiboardCharWidthBase80('eng', "e", 120)
        setMultiboardCharWidthBase80('eng', "f", 240)
        setMultiboardCharWidthBase80('eng', "g", 120)
        setMultiboardCharWidthBase80('eng', "h", 120)
        setMultiboardCharWidthBase80('eng', "i", 288)
        setMultiboardCharWidthBase80('eng', "j", 288)
        setMultiboardCharWidthBase80('eng', "k", 144)
        setMultiboardCharWidthBase80('eng', "l", 288)
        setMultiboardCharWidthBase80('eng', "m", 80)
        setMultiboardCharWidthBase80('eng', "n", 120)
        setMultiboardCharWidthBase80('eng', "o", 111)
        setMultiboardCharWidthBase80('eng', "p", 111)
        setMultiboardCharWidthBase80('eng', "q", 111)
        setMultiboardCharWidthBase80('eng', "r", 206)
        setMultiboardCharWidthBase80('eng', "s", 160)
        setMultiboardCharWidthBase80('eng', "t", 206)
        setMultiboardCharWidthBase80('eng', "u", 120)
        setMultiboardCharWidthBase80('eng', "v", 144)
        setMultiboardCharWidthBase80('eng', "w", 90)
        setMultiboardCharWidthBase80('eng', "x", 131)
        setMultiboardCharWidthBase80('eng', "y", 144)
        setMultiboardCharWidthBase80('eng', "z", 144)
        setMultiboardCharWidthBase80('eng', "A", 103)
        setMultiboardCharWidthBase80('eng', "B", 120)
        setMultiboardCharWidthBase80('eng', "C", 103)
        setMultiboardCharWidthBase80('eng', "D", 96)
        setMultiboardCharWidthBase80('eng', "E", 131)
        setMultiboardCharWidthBase80('eng', "F", 160)
        setMultiboardCharWidthBase80('eng', "G", 96)
        setMultiboardCharWidthBase80('eng', "H", 90)
        setMultiboardCharWidthBase80('eng', "I", 240)
        setMultiboardCharWidthBase80('eng', "J", 240)
        setMultiboardCharWidthBase80('eng', "K", 120)
        setMultiboardCharWidthBase80('eng', "L", 131)
        setMultiboardCharWidthBase80('eng', "M", 76)
        setMultiboardCharWidthBase80('eng', "N", 90)
        setMultiboardCharWidthBase80('eng', "O", 85)
        setMultiboardCharWidthBase80('eng', "P", 120)
        setMultiboardCharWidthBase80('eng', "Q", 85)
        setMultiboardCharWidthBase80('eng', "R", 120)
        setMultiboardCharWidthBase80('eng', "S", 131)
        setMultiboardCharWidthBase80('eng', "T", 144)
        setMultiboardCharWidthBase80('eng', "U", 96)
        setMultiboardCharWidthBase80('eng', "V", 120)
        setMultiboardCharWidthBase80('eng', "W", 76)
        setMultiboardCharWidthBase80('eng', "X", 111)
        setMultiboardCharWidthBase80('eng', "Y", 120)
        setMultiboardCharWidthBase80('eng', "Z", 111)
        setMultiboardCharWidthBase80('eng', "1", 103)
        setMultiboardCharWidthBase80('eng', "2", 111)
        setMultiboardCharWidthBase80('eng', "3", 111)
        setMultiboardCharWidthBase80('eng', "4", 111)
        setMultiboardCharWidthBase80('eng', "5", 111)
        setMultiboardCharWidthBase80('eng', "6", 111)
        setMultiboardCharWidthBase80('eng', "7", 111)
        setMultiboardCharWidthBase80('eng', "8", 111)
        setMultiboardCharWidthBase80('eng', "9", 111)
        setMultiboardCharWidthBase80('eng', "0", 111)
        setMultiboardCharWidthBase80('eng', ":", 288)
        setMultiboardCharWidthBase80('eng', ";", 288)
        setMultiboardCharWidthBase80('eng', ".", 288)
        setMultiboardCharWidthBase80('eng', "#", 103)
        setMultiboardCharWidthBase80('eng', ",", 288)
        setMultiboardCharWidthBase80('eng', " ", 286) --space
        setMultiboardCharWidthBase80('eng', "'", 360)
        setMultiboardCharWidthBase80('eng', "!", 288)
        setMultiboardCharWidthBase80('eng', "$", 131)
        setMultiboardCharWidthBase80('eng', "&", 120)
        setMultiboardCharWidthBase80('eng', "/", 180)
        setMultiboardCharWidthBase80('eng', "(", 206)
        setMultiboardCharWidthBase80('eng', ")", 206)
        setMultiboardCharWidthBase80('eng', "=", 111)
        setMultiboardCharWidthBase80('eng', "?", 180)
        setMultiboardCharWidthBase80('eng', "^", 144)
        setMultiboardCharWidthBase80('eng', "<", 111)
        setMultiboardCharWidthBase80('eng', ">", 111)
        setMultiboardCharWidthBase80('eng', "-", 160)
        setMultiboardCharWidthBase80('eng', "+", 111)
        setMultiboardCharWidthBase80('eng', "*", 144)
        setMultiboardCharWidthBase80('eng', "|", 479) --2 vertical bars in a row escape to one. So you could print 960 ones in a line, 480 would display. Maybe need to adapt to this before calculating string width.
        setMultiboardCharWidthBase80('eng', "~", 144)
        setMultiboardCharWidthBase80('eng', "{", 160)
        setMultiboardCharWidthBase80('eng', "}", 160)
        setMultiboardCharWidthBase80('eng', "[", 206)
        setMultiboardCharWidthBase80('eng', "]", 206)
        setMultiboardCharWidthBase80('eng', "_", 120)
        setMultiboardCharWidthBase80('eng', "\x25", 103) --percent
        setMultiboardCharWidthBase80('eng', "\x5C", 180) --backslash
        setMultiboardCharWidthBase80('eng', "\x22", 180) --double quotation mark
        setMultiboardCharWidthBase80('eng', "\x40", 85) --at sign
        setMultiboardCharWidthBase80('eng', "\x60", 206) --Gravis (Accent)
    else
        --German font size up to patch 1.32
        setMultiboardCharWidthBase80('ger', "a", 144)
        setMultiboardCharWidthBase80('ger', "b", 144)
        setMultiboardCharWidthBase80('ger', "c", 144)
        setMultiboardCharWidthBase80('ger', "d", 131)
        setMultiboardCharWidthBase80('ger', "e", 144)
        setMultiboardCharWidthBase80('ger', "f", 240)
        setMultiboardCharWidthBase80('ger', "g", 120)
        setMultiboardCharWidthBase80('ger', "h", 144)
        setMultiboardCharWidthBase80('ger', "i", 360)
        setMultiboardCharWidthBase80('ger', "j", 288)
        setMultiboardCharWidthBase80('ger', "k", 144)
        setMultiboardCharWidthBase80('ger', "l", 360)
        setMultiboardCharWidthBase80('ger', "m", 90)
        setMultiboardCharWidthBase80('ger', "n", 144)
        setMultiboardCharWidthBase80('ger', "o", 131)
        setMultiboardCharWidthBase80('ger', "p", 131)
        setMultiboardCharWidthBase80('ger', "q", 131)
        setMultiboardCharWidthBase80('ger', "r", 206)
        setMultiboardCharWidthBase80('ger', "s", 180)
        setMultiboardCharWidthBase80('ger', "t", 206)
        setMultiboardCharWidthBase80('ger', "u", 144)
        setMultiboardCharWidthBase80('ger', "v", 131)
        setMultiboardCharWidthBase80('ger', "w", 96)
        setMultiboardCharWidthBase80('ger', "x", 144)
        setMultiboardCharWidthBase80('ger', "y", 131)
        setMultiboardCharWidthBase80('ger', "z", 144)
        setMultiboardCharWidthBase80('ger', "A", 103)
        setMultiboardCharWidthBase80('ger', "B", 131)
        setMultiboardCharWidthBase80('ger', "C", 120)
        setMultiboardCharWidthBase80('ger', "D", 111)
        setMultiboardCharWidthBase80('ger', "E", 144)
        setMultiboardCharWidthBase80('ger', "F", 180)
        setMultiboardCharWidthBase80('ger', "G", 103)
        setMultiboardCharWidthBase80('ger', "H", 103)
        setMultiboardCharWidthBase80('ger', "I", 288)
        setMultiboardCharWidthBase80('ger', "J", 240)
        setMultiboardCharWidthBase80('ger', "K", 120)
        setMultiboardCharWidthBase80('ger', "L", 144)
        setMultiboardCharWidthBase80('ger', "M", 80)
        setMultiboardCharWidthBase80('ger', "N", 103)
        setMultiboardCharWidthBase80('ger', "O", 96)
        setMultiboardCharWidthBase80('ger', "P", 144)
        setMultiboardCharWidthBase80('ger', "Q", 90)
        setMultiboardCharWidthBase80('ger', "R", 120)
        setMultiboardCharWidthBase80('ger', "S", 144)
        setMultiboardCharWidthBase80('ger', "T", 144)
        setMultiboardCharWidthBase80('ger', "U", 111)
        setMultiboardCharWidthBase80('ger', "V", 120)
        setMultiboardCharWidthBase80('ger', "W", 76)
        setMultiboardCharWidthBase80('ger', "X", 111)
        setMultiboardCharWidthBase80('ger', "Y", 120)
        setMultiboardCharWidthBase80('ger', "Z", 120)
        setMultiboardCharWidthBase80('ger', "1", 288)
        setMultiboardCharWidthBase80('ger', "2", 131)
        setMultiboardCharWidthBase80('ger', "3", 144)
        setMultiboardCharWidthBase80('ger', "4", 120)
        setMultiboardCharWidthBase80('ger', "5", 144)
        setMultiboardCharWidthBase80('ger', "6", 131)
        setMultiboardCharWidthBase80('ger', "7", 144)
        setMultiboardCharWidthBase80('ger', "8", 131)
        setMultiboardCharWidthBase80('ger', "9", 131)
        setMultiboardCharWidthBase80('ger', "0", 131)
        setMultiboardCharWidthBase80('ger', ":", 480)
        setMultiboardCharWidthBase80('ger', ";", 360)
        setMultiboardCharWidthBase80('ger', ".", 480)
        setMultiboardCharWidthBase80('ger', "#", 120)
        setMultiboardCharWidthBase80('ger', ",", 360)
        setMultiboardCharWidthBase80('ger', " ", 288) --space
        setMultiboardCharWidthBase80('ger', "'", 480)
        setMultiboardCharWidthBase80('ger', "!", 360)
        setMultiboardCharWidthBase80('ger', "$", 160)
        setMultiboardCharWidthBase80('ger', "&", 96)
        setMultiboardCharWidthBase80('ger', "/", 180)
        setMultiboardCharWidthBase80('ger', "(", 288)
        setMultiboardCharWidthBase80('ger', ")", 288)
        setMultiboardCharWidthBase80('ger', "=", 160)
        setMultiboardCharWidthBase80('ger', "?", 180)
        setMultiboardCharWidthBase80('ger', "^", 144)
        setMultiboardCharWidthBase80('ger', "<", 160)
        setMultiboardCharWidthBase80('ger', ">", 160)
        setMultiboardCharWidthBase80('ger', "-", 144)
        setMultiboardCharWidthBase80('ger', "+", 160)
        setMultiboardCharWidthBase80('ger', "*", 206)
        setMultiboardCharWidthBase80('ger', "|", 480) --2 vertical bars in a row escape to one. So you could print 960 ones in a line, 480 would display. Maybe need to adapt to this before calculating string width.
        setMultiboardCharWidthBase80('ger', "~", 144)
        setMultiboardCharWidthBase80('ger', "{", 240)
        setMultiboardCharWidthBase80('ger', "}", 240)
        setMultiboardCharWidthBase80('ger', "[", 240)
        setMultiboardCharWidthBase80('ger', "]", 288)
        setMultiboardCharWidthBase80('ger', "_", 144)
        setMultiboardCharWidthBase80('ger', "\x25", 111) --percent
        setMultiboardCharWidthBase80('ger', "\x5C", 206) --backslash
        setMultiboardCharWidthBase80('ger', "\x22", 240) --double quotation mark
        setMultiboardCharWidthBase80('ger', "\x40", 103) --at sign
        setMultiboardCharWidthBase80('ger', "\x60", 240) --Gravis (Accent)

        --English Font size up to patch 1.32
        setMultiboardCharWidthBase80('eng', "a", 144)
        setMultiboardCharWidthBase80('eng', "b", 120)
        setMultiboardCharWidthBase80('eng', "c", 131)
        setMultiboardCharWidthBase80('eng', "d", 120)
        setMultiboardCharWidthBase80('eng', "e", 131)
        setMultiboardCharWidthBase80('eng', "f", 240)
        setMultiboardCharWidthBase80('eng', "g", 120)
        setMultiboardCharWidthBase80('eng', "h", 131)
        setMultiboardCharWidthBase80('eng', "i", 360)
        setMultiboardCharWidthBase80('eng', "j", 288)
        setMultiboardCharWidthBase80('eng', "k", 144)
        setMultiboardCharWidthBase80('eng', "l", 360)
        setMultiboardCharWidthBase80('eng', "m", 80)
        setMultiboardCharWidthBase80('eng', "n", 131)
        setMultiboardCharWidthBase80('eng', "o", 120)
        setMultiboardCharWidthBase80('eng', "p", 120)
        setMultiboardCharWidthBase80('eng', "q", 120)
        setMultiboardCharWidthBase80('eng', "r", 206)
        setMultiboardCharWidthBase80('eng', "s", 160)
        setMultiboardCharWidthBase80('eng', "t", 206)
        setMultiboardCharWidthBase80('eng', "u", 131)
        setMultiboardCharWidthBase80('eng', "v", 144)
        setMultiboardCharWidthBase80('eng', "w", 90)
        setMultiboardCharWidthBase80('eng', "x", 131)
        setMultiboardCharWidthBase80('eng', "y", 144)
        setMultiboardCharWidthBase80('eng', "z", 144)
        setMultiboardCharWidthBase80('eng', "A", 103)
        setMultiboardCharWidthBase80('eng', "B", 120)
        setMultiboardCharWidthBase80('eng', "C", 103)
        setMultiboardCharWidthBase80('eng', "D", 103)
        setMultiboardCharWidthBase80('eng', "E", 131)
        setMultiboardCharWidthBase80('eng', "F", 160)
        setMultiboardCharWidthBase80('eng', "G", 103)
        setMultiboardCharWidthBase80('eng', "H", 96)
        setMultiboardCharWidthBase80('eng', "I", 288)
        setMultiboardCharWidthBase80('eng', "J", 240)
        setMultiboardCharWidthBase80('eng', "K", 120)
        setMultiboardCharWidthBase80('eng', "L", 131)
        setMultiboardCharWidthBase80('eng', "M", 76)
        setMultiboardCharWidthBase80('eng', "N", 96)
        setMultiboardCharWidthBase80('eng', "O", 85)
        setMultiboardCharWidthBase80('eng', "P", 131)
        setMultiboardCharWidthBase80('eng', "Q", 85)
        setMultiboardCharWidthBase80('eng', "R", 120)
        setMultiboardCharWidthBase80('eng', "S", 131)
        setMultiboardCharWidthBase80('eng', "T", 144)
        setMultiboardCharWidthBase80('eng', "U", 103)
        setMultiboardCharWidthBase80('eng', "V", 120)
        setMultiboardCharWidthBase80('eng', "W", 76)
        setMultiboardCharWidthBase80('eng', "X", 111)
        setMultiboardCharWidthBase80('eng', "Y", 120)
        setMultiboardCharWidthBase80('eng', "Z", 111)
        setMultiboardCharWidthBase80('eng', "1", 206)
        setMultiboardCharWidthBase80('eng', "2", 131)
        setMultiboardCharWidthBase80('eng', "3", 131)
        setMultiboardCharWidthBase80('eng', "4", 111)
        setMultiboardCharWidthBase80('eng', "5", 131)
        setMultiboardCharWidthBase80('eng', "6", 120)
        setMultiboardCharWidthBase80('eng', "7", 131)
        setMultiboardCharWidthBase80('eng', "8", 111)
        setMultiboardCharWidthBase80('eng', "9", 120)
        setMultiboardCharWidthBase80('eng', "0", 111)
        setMultiboardCharWidthBase80('eng', ":", 360)
        setMultiboardCharWidthBase80('eng', ";", 360)
        setMultiboardCharWidthBase80('eng', ".", 360)
        setMultiboardCharWidthBase80('eng', "#", 103)
        setMultiboardCharWidthBase80('eng', ",", 360)
        setMultiboardCharWidthBase80('eng', " ", 288) --space
        setMultiboardCharWidthBase80('eng', "'", 480)
        setMultiboardCharWidthBase80('eng', "!", 360)
        setMultiboardCharWidthBase80('eng', "$", 131)
        setMultiboardCharWidthBase80('eng', "&", 120)
        setMultiboardCharWidthBase80('eng', "/", 180)
        setMultiboardCharWidthBase80('eng', "(", 240)
        setMultiboardCharWidthBase80('eng', ")", 240)
        setMultiboardCharWidthBase80('eng', "=", 111)
        setMultiboardCharWidthBase80('eng', "?", 180)
        setMultiboardCharWidthBase80('eng', "^", 144)
        setMultiboardCharWidthBase80('eng', "<", 131)
        setMultiboardCharWidthBase80('eng', ">", 131)
        setMultiboardCharWidthBase80('eng', "-", 180)
        setMultiboardCharWidthBase80('eng', "+", 111)
        setMultiboardCharWidthBase80('eng', "*", 180)
        setMultiboardCharWidthBase80('eng', "|", 480) --2 vertical bars in a row escape to one. So you could print 960 ones in a line, 480 would display. Maybe need to adapt to this before calculating string width.
        setMultiboardCharWidthBase80('eng', "~", 144)
        setMultiboardCharWidthBase80('eng', "{", 240)
        setMultiboardCharWidthBase80('eng', "}", 240)
        setMultiboardCharWidthBase80('eng', "[", 240)
        setMultiboardCharWidthBase80('eng', "]", 240)
        setMultiboardCharWidthBase80('eng', "_", 120)
        setMultiboardCharWidthBase80('eng', "\x25", 103) --percent
        setMultiboardCharWidthBase80('eng', "\x5C", 180) --backslash
        setMultiboardCharWidthBase80('eng', "\x22", 206) --double quotation mark
        setMultiboardCharWidthBase80('eng', "\x40", 96) --at sign
        setMultiboardCharWidthBase80('eng', "\x60", 206) --Gravis (Accent)
    end
end

if Debug and Debug.endFile then Debug.endFile() end

Deinstallation

Debug Utils is meant to provide debugging utility and as such, shall be removed or invalidated from the map closely before release. Optimally delete the whole Debug library. If that isn't suitable (because you have used library functions at too many places), you can instead remove all Debug functionality by replacing the Debug Utils with this short code:
Lua:
Debug = setmetatable({try = function(...) return select(2,pcall(...)) end}, {__index = function(t,k) return DoNothing end})
try = Debug.try
If that is also not suitable for you (because your systems rely on the Debug functionality to some degree), at least set ALLOW_INGAME_CODE_EXECUTION to false in the configuration and delete the IngameConsole file.

21.02.2021, v1.0 (Initial release)

  • Ingame Console and -exec command
  • Automatic warnings upon accessing undeclared globals
  • Definition files for common.lua and blizzard.lua to use with VS Code and sumneko extension.
  • Library functions try() for error handling, table.print() for taking quick looks into data structures and Wc3Type() to receive the Warcraft type of userdata objects.

12.09.2021, v1.1:
  • Console can now deal with pcall breaks (yes, even pcall can break, if you know how to )
  • Fixed a rare bug only occuring for long multiline inputs, where parts of the input were not displayed properly (but still executed properly)
  • The output only consisting of the empty string will now properly display (as... well, an empty line)
  • Improved annotations
03.10.2021, v1.1a:
  • The try-function will now properly display an error message on screen upon using it with erroneous function calls. This fixes a bug invented with version 1.1.
28.01.2022, v1.2:
  • Added a variant of section 1.2 (warnings for undeclared globals) that excludes bj-variables. Also added some explanation about variables that were intentionally nilled.
  • Added automatic error handling: the code snippet in section 2.1 will replace TriggerAddAction by a version using try to monitor code entry points. Thanks @Jampion for the suggestion.
  • Added an installation section to the main post, including a copy-paste option for all parts of this resource in one.
24.02.2022, v1.2a:
  • Ingame Console now manually recognizes erroneous Lua statements and will throw an "Invalid Lua-statement"-error instead of the non-sensible "Attempt to call a nil value".
  • Fixed an IngameConsole error, where inputting function calls separated by semicolons was not properly executed.
  • Fixed an IngameConsole error, where inputting statements including raw strings (such as load('a=5')) was sometimes not properly executed.
08.08.2022, v1.3:
  • Added Chapter 2.6. about stack traces and Chapter 2.7 about Logging additional information.
  • Included GetStackTrace to be able to print traces of erroneous function calls.
    Credits to @HerlySQR for the original resource and permission to use it in Debug Utils.
  • Included LogStackTrace for logging the function trace provided by GetStackTrace plus further information that the try-function could decide to print upon encountering errors.
07.01.2023, v2.0:

Major update including complete rewrite of both code and resource main page.

General Changes
  • All API-functions are now part of the Debug-library (capital D, not to be mistaken with Lua's native debug library, which is disabled in Reforged). I.e., try becomes Debug.try and so on.
  • DebugUtils now needs to be the topmost script in your trigger editor (because local file support, print caching and Lua root error handling via Debug.try rely on that).
  • DebugUtils now includes a settings section.
  • Attached up-to-date definition files for Wc3 v1.33 common.lua, blizzard.lua and HiddenNatives.lua.
Improved Error Handling
  • Automatic error handling extended to Timer callbacks, Trigger Conditions and Coroutines (previously only Trigger Actions). I.e. Debug Utils now also overrides Condition, Filter, TimerStart, coroutine.create and coroutine.wrap.
  • Catched errors now print a stack trace in addition to their error message.
  • You can now convert war3map.lua-references into local file references via Debug.beginFile() and Debug.endFile()
  • New and changed library functions Debug.log (replaced LogStackTrace), Debug.assert, Debug.throwError, Debug.traceback (renamed from GetStackTrace) and Debug.wc3Type (renamed from Wc3Type).
Ingame Console and -exec
  • Now calculates linebreaks based on Wc3 version 1.33 font size, IF used in Wc3 1.33+ (i.e. old version will still apply old font size).
  • Improved both the code and its documentation.
  • Ingame Console can now properly display strings including linebreaks (by splitting them into different lines and reproducing color codes).
  • The "Invalid Lua-statement" error has been further improved, delivering details on what was actually wrong with the input-string.
  • Added a setting to turn on stack traces for its error messages.
  • Doesn't need a GUI-trigger anymore for first start up.
  • The -exec command now prints both executed statement and return values for better feedback. Error Messages now include stack trace.
Name Caching
  • Debug Utils will now build a name cache, which lets tostring show globals by their string name instead of their memory position. E.g. print(CreateUnit) will now print "function: CreateUnit" instead of "function: 0063A698".
  • Globals added during map runtime and entries within subtables of global scope are automatically added to the name cache.
  • Library functions Debug.registerName(object,name) and Debug.registerNamesFrom(table,[tableName], [depth]) for manually adding names.
Other Changes
  • table.print now has a pretty version.
  • Prints during loading screen are now delayed to after game start (so you can debug with prints during loading screen).
  • You can set the print duration in the configuration.
  • Warnings for undeclared globals now begin after game start to prevent them from triggering for library dependency checks (which typically happen during loading screen).
13.01.2023, v2.0a:
  • Fixed a bug, where the coroutine returned by the overridden version of coroutine.create and coroutine.wrap would not pass parameters to its function.
  • Added document & line reference to undeclared global warnings
  • Added setting SHOW_TRACE_FOR_UNDECLARED_GLOBALS (default false) that also enables stack trace for undeclared global warnings.
  • Added third parameter lastLine to Debug.beginFile() for compatibility reasons with Total Initialization.
23.06.2024, v2.1:
  • Automatic error handling extended to Warcraft's native enum functions, i.e. Debug Utils now also overrides ForGroup, ForForce, EnumItemsInRect and EnumDestructablesInRect. Thanks to @Antares for spotting that this wasn't already the case.
  • IngameConsole and StringWidth have been merged to a single file to simplify resource installation, as the second was just used within the first anyway.
  • Disabled undeclared global warnings for bj-variables (because it was more annoying than useful).
06.07.2024, v2.1a:
  • Fixed a bug, where errors and warnings resulting from multiline-inputs could sometimes visually overlap with other lines within the debug console. Thanks to @Luashine for reporting this.
08.09.2024, v2.2:
  • Nil-Warnings are now disabled for global variables after first initialization by default. This even holds, if you initialize a global with a nil-value.
    You can re-enable warnings for initialized globals by setting EXCLUDE_INITIALIZED_GLOBALS_FROM_NIL_WARNINGS = false (default: true).
    Thanks to @Macielos for the suggestion.
  • Added the function Debug.disableNilWarningsFor("MyGlobalVariable") to manually exclude any particular global from nil-warnings.
    Thanks to @Antares for the suggestion.
  • Re-enabled nil-warnings for bj-variables, but added a setting EXCLUDE_BJ_GLOBALS_FROM_NIL_WARNINGS (default: false) to let the user decide whether to exclude them or not.
  • Rephrased the code and documentation in a way that these warnings are only being referred to as nil-warnings instead of undeclared-global-warnings. This should emphasize the true functionality to the user and prevent some confusion that I observed in the past.
Contents

Lua Debug Utils (Map)

Reviews
Antares
To quote myself from the Code Archive thread: "I haven't got the slightest idea how you would even write something like this... pretty sure it works with magic! 🧙‍♂️" Absolutely indispensable when coding in Lua. Highly recommended for anyone making...
To quote myself from the Code Archive thread:

"I haven't got the slightest idea how you would even write something like this... pretty sure it works with magic! 🧙‍♂️"

Absolutely indispensable when coding in Lua. Highly recommended for anyone making the switch.

Director's Cut
 
Last edited:
I was debugging a code that crashed on map initialization (OnInit.final) and I was back to putting random print messages in my script because I didn't get an error message from your system. You have an idea what could have been going on there?
I'm afraid not. I've just tested OnInit.final with erroneous example code and got error messages as expected. Would you kindly provide information on how to reproduce your issue?
 
@Antares Thanks for your bug report and PM'ing me the hint to EnumItemsInRect.
It seems that Lua's pcall and xpcall aren't working as expected on enum natives (also including the popular ForGroup and ForForce).

The below update of Debug Utils should solve the issue and provide error handling for these natives.
I will make it official as soon as I find time.

Lua:
do; local _, codeLoc = pcall(error, "", 2) --get line number where DebugUtils begins.
--[[
 -------------------------
 -- | Debug Utils 2.1 | --
 -------------------------

 --> https://www.hiveworkshop.com/threads/lua-debug-utils-incl-ingame-console.353720/

 - by Eikonium, with special thanks to:
    - @Bribe, for pretty table print, showing that xpcall's message handler executes before the stack unwinds and useful suggestions like name caching and stack trace improvements.
    - @Jampion, for useful suggestions like print caching and applying Debug.try to all code entry points
    - @Luashine, for useful feedback and building "WC3 Debug Console Paste Helper" (https://github.com/Luashine/wc3-debug-console-paste-helper#readme)
    - @HerlySQR, for showing a way to get a stack trace in Wc3 (https://www.hiveworkshop.com/threads/lua-getstacktrace.340841/)
    - @Macadamia, for showing a way to print warnings upon accessing undeclared globals, where this all started with (https://www.hiveworkshop.com/threads/lua-very-simply-trick-to-help-lua-users-track-syntax-errors.326266/)

-----------------------------------------------------------------------------------------------------------------------------
| Provides debugging utility for Wc3-maps using Lua.                                                                        |
|                                                                                                                           |
| Including:                                                                                                                |
|   1. Automatic ingame error messages upon running erroneous code from triggers or timers.                                 |
|   2. Ingame Console that allows you to execute code via Wc3 ingame chat.                                                  |
|   3. Automatic warnings upon reading undeclared globals (which also triggers after misspelling globals)                   |
|   4. Debug-Library functions for manual error handling.                                                                   |
|   5. Caching of loading screen print messages until game start (which simplifies error handling during loading screen)    |
|   6. Overwritten tostring/print-functions to show the actual string-name of an object instead of the memory position.     |
|   7. Conversion of war3map.lua-error messages to local file error messages.                                               |
|   8. Other useful debug utility (table.print and Debug.wc3Type)                                                           |
-----------------------------------------------------------------------------------------------------------------------------

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Installation:                                                                                                                                                             |
|                                                                                                                                                                           |
|   1. Copy the code (DebugUtils.lua, StringWidth.lua and IngameConsole.lua) into your map. Use script files (Ctrl+U) in your trigger editor, not text-based triggers!      |
|   2. Order the files: DebugUtils above StringWidth above IngameConsole. Make sure they are above ALL other scripts (crucial for local line number feature).               |
|   3. Adjust the settings in the settings-section further below to receive the debug environment that fits your needs.                                                     |
|                                                                                                                                                                           |
| Deinstallation:                                                                                                                                                           |
|                                                                                                                                                                           |
|  - Debug Utils is meant to provide debugging utility and as such, shall be removed or invalidated from the map closely before release.                                    |
|  - Optimally delete the whole Debug library. If that isn't suitable (because you have used library functions at too many places), you can instead replace Debug Utils     |
|    by the following line of code that will invalidate all Debug functionality (without breaking your code):                                                               |
|    Debug = setmetatable({try = function(...) return select(2,pcall(...)) end}, {__index = function(t,k) return DoNothing end}); try = Debug.try                           |
|  - If that is also not suitable for you (because your systems rely on the Debug functionality to some degree), at least set ALLOW_INGAME_CODE_EXECUTION to false.         |
|  - Be sure to test your map thoroughly after removing Debug Utils.                                                                                                        |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
* Documentation and API-Functions:
*
*       - All automatic functionality provided by Debug Utils can be deactivated using the settings directly below the documentation.
*
* -------------------------
* | Ingame Code Execution |
* -------------------------
*       - Debug Utils provides the ability to run code via chat command from within Wc3, if you have conducted step 3 from the installation section.
*       - You can either open the ingame console by typing "-console" into the chat, or directly execute code by typing "-exec <code>".
*       - See IngameConsole script for further documentation.
*
* ------------------
* | Error Handling |
* ------------------
*        - Debug Utils automatically applies error handling (i.e. Debug.try) to code executed by your triggers and timers (error handling means that error messages are printed on screen, if anything doesn't run properly).
*        - You can still use the below library functions for manual debugging.
*
*    Debug.try(funcToExecute, ...) / try(funcToExecute, ...) -> ...
*        - Calls the specified function with the specified parameters in protected mode (i.e. code after Debug.try will continue to run even if the function fails to execute).
*        - If the call is successful, returns the specified function's original return values (so p1 = Debug.try(Player, 0) will work fine).
*        - If the call is unsuccessful, prints an error message on screen (including stack trace and parameters you have potentially logged before the error occured)
*        - By default, the error message consists of a line-reference to war3map.lua (which you can look into by forcing a syntax error in WE or by exporting it from your map via File -> Export Script).
*          You can get more helpful references to local script files instead, see section about "Local script references".
*        - Example: Assume you have a code line like "func(param1,param2)", which doesn't work and you want to know why.
*           Option 1: Change it to "Debug.try(func, param1, param2)", i.e. separate the function from the parameters.
*           Option 2: Change it to "Debug.try(function() return func(param1, param2) end)", i.e. pack it into an anonymous function (optionally skip the return statement).
*    Debug.log(...)
*        - Logs the specified parameters to the Debug-log. The Debug-log will be printed upon the next error being catched by Debug.try, Debug.assert or Debug.throwError.
*        - The Debug-log will only hold one set of parameters per code-location. That means, if you call Debug.log() inside any function, only the params saved within the latest call of that function will be kept.
*    Debug.throwError(...)
*        - Prints an error message including document, line number, stack trace, previously logged parameters and all specified parameters on screen. Parameters can have any type.
*        - In contrast to Lua's native error function, this can be called outside of protected mode and doesn't halt code execution.
*    Debug.assert(condition:boolean, errorMsg:string, ...) -> ...
*        - Prints the specified error message including document, line number, stack trace and previously logged parameters on screen, IF the specified condition fails (i.e. resolves to false/nil).
*        - Returns ..., IF the specified condition holds.
*        - This works exactly like Lua's native assert, except that it also works outside of protected mode and does not halt code execution.
*    Debug.traceback() -> string
*        - Returns the stack trace at the position where this is called. You need to manually print it.
*    Debug.getLine([depth: integer]) -> integer?
*        - Returns the line in war3map.lua, where this function is executed.
*        - You can specify a depth d >= 1 to instead return the line, where the d-th function in the stack trace was called. I.e. depth = 2 will return the line of execution of the function that calls Debug.getLine.
*        - Due to Wc3's limited stack trace ability, this might sometimes return nil for depth >= 3, so better apply nil-checks on the result.
*    Debug.getLocalErrorMsg(errorMsg:string) -> string
*        - Takes an error message containing a file and a linenumber and converts war3map.lua-lines to local document lines as defined by uses of Debug.beginFile() and Debug.endFile().
*        - Error Msg must be formatted like "<document>:<linenumber><Rest>".
*
* -----------------------------------
* | Warnings for undeclared globals |
* -----------------------------------
*        - DebugUtils will print warnings on screen, if you read an undeclared global variable.
*        - This is technically the case, when you misspelled on a function name, like calling CraeteUnit instead of CreateUnit.
*        - Keep in mind though that the same warning will pop up after reading a global that was intentionally nilled. If you don't like this, turn of this feature in the settings.
*
* -----------------
* | Print Caching |
* -----------------
*        - DebugUtils caches print()-calls occuring during loading screen and delays them to after game start.
*        - This also applies to loading screen error messages, so you can wrap erroneous parts of your Lua root in Debug.try-blocks and see the message after game start.
*
* -------------------------
* | Local File Stacktrace |
* -------------------------
*        - By default, error messages and stack traces printed by the error handling functionality of Debug Utils contain references to war3map.lua (a big file just appending all your local scripts).
*        - The Debug-library provides the two functions below to index your local scripts, activating local file names and line numbers (matching those in your IDE) instead of the war3map.lua ones.
*        - This allows you to inspect errors within your IDE (VSCode) instead of the World Editor.
*
*    Debug.beginFile(fileName: string [, depth: integer])
*        - Tells the Debug library that the specified file begins exactly here (i.e. in the line, where this is called).
*        - Using this improves stack traces of error messages. "war3map.lua"-references between <here> and the next Debug.endFile() will be converted to file-specific references.
*        - All war3map.lua-lines located between the call of Debug.beginFile(fileName) and the next call of Debug.beginFile OR Debug.endFile are treated to be part of "fileName".
*        - !!! To be called in the Lua root in Line 1 of every document you wish to track. Line 1 means exactly line 1, before any comment! This way, the line shown in the trace will exactly match your IDE.
*        - Depth can be ignored, except if you want to use a custom wrapper around Debug.beginFile(), in which case you need to set the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.beginFile().
*    Debug.endFile([depth: integer])
*        - Ends the current file that was previously begun by using Debug.beginFile(). War3map.lua-lines after this will not be converted until the next instance of Debug.beginFile().
*        - The next call of Debug.beginFile() will also end the previous one, so using Debug.endFile() is optional. Mainly recommended to use, if you prefer to have war3map.lua-references in a certain part of your script (such as within GUI triggers).
*        - Depth can be ignored, except if you want to use a custom wrapper around Debug.endFile(), you need to increase the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.endFile().
*
* ----------------
* | Name Caching |
* ----------------
*        - DebugUtils overwrites the tostring-function so that it prints the name of a non-primitive object (if available) instead of its memory position. The same applies to print().
*        - For instance, print(CreateUnit) will show "function: CreateUnit" on screen instead of "function: 0063A698".
*        - The table holding all those names is referred to as "Name Cache".
*        - All names of objects in global scope will automatically be added to the Name Cache both within Lua root and again at game start (to get names for overwritten natives and your own objects).
*        - New names entering global scope will also automatically be added, even after game start. The same applies to subtables of _G up to a depth of Debug.settings.NAME_CACHE_DEPTH.
*        - Objects within subtables will be named after their parent tables and keys. For instance, the name of the function within T = {{bla = function() end}} is "T[1].bla".
*        - The automatic adding doesn't work for objects saved into existing variables/keys after game start (because it's based on __newindex metamethod which simply doesn't trigger)
*        - You can manually add names to the name cache by using the following API-functions:
*
*    Debug.registerName(whichObject:any, name:string)
*        - Adds the specified object under the specified name to the name cache, letting tostring and print output "<type>: <name>" going foward.
*        - The object must be non-primitive, i.e. this won't work on strings, numbers and booleans.
*        - This will overwrite existing names for the specified object with the specified name.
*    Debug.registerNamesFrom(parentTable:table [, parentTableName:string] [, depth])
*        - Adds names for all values from within the specified parentTable to the name cache.
*        - Names for entries will be like "<parentTableName>.<key>" or "<parentTableName>[<key>]" (depending on the key type), using the existing name of the parentTable from the name cache.
*        - You can optionally specify a parentTableName to use that for the entry naming instead of the existing name. Doing so will also register that name for the parentTable, if it doesn't already has one.
*        - Specifying the empty string as parentTableName will suppress it in the naming and just register all values as "<key>". Note that only string keys will be considered this way.
*        - In contrast to Debug.registerName(), this function will NOT overwrite existing names, but just add names for new objects.
*    Debug.oldTostring(object:any) -> string
*        - The old tostring-function in case you still need outputs like "function: 0063A698".
*
* -----------------
* | Other Utility |
* -----------------
*
*    Debug.wc3Type(object:any) -> string
*        - Returns the Warcraft3-type of the input object. E.g. Debug.wc3Type(Player(0)) will return "player".
*        - Returns type(object), if used on Lua-objects.
*    table.tostring(whichTable [, depth:integer] [, pretty_yn:boolean])
*        - Creates a list of all (key,value)-pairs from the specified table. Also lists subtable entries up to the specified depth (unlimited, if not specified).
*        - E.g. for T = {"a", 5, {7}}, table.tostring(T) would output '{(1, "a"), (2, 5), (3, {(1, 7)})}' (if using concise style, i.e. pretty_yn being nil or false).
*        - Not specifying a depth can potentially lead to a stack overflow for self-referential tables (e.g X = {}; X[1] = X). Choose a sensible depth to prevent this (in doubt start with 1 and test upwards).
*        - Supports pretty style by setting pretty_yn to true. Pretty style is linebreak-separated, uses indentations and has other visual improvements. Use it on small tables only, because Wc3 can't show that many linebreaks at once.
*        - All of the following is valid syntax: table.tostring(T), table.tostring(T, depth), table.tostring(T, pretty_yn) or table.tostring(T, depth, pretty_yn).
*        - table.tostring is not multiplayer-synced.
*    table.print(whichTable [, depth:integer] [, pretty_yn:boolean])
*        - Prints table.tostring(...).
*
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------]]

    -- disable sumneko extension warnings for imported resource
    ---@diagnostic disable

    ----------------
    --| Settings |--
    ----------------

    Debug = {
        --BEGIN OF SETTINGS--
        settings = {
                SHOW_TRACE_ON_ERROR = true                      ---Set to true to show a stack trace on every error in addition to the regular message (msg sources: automatic error handling, Debug.try, Debug.throwError, ...)
            ,   INCLUDE_DEBUGUTILS_INTO_TRACE = true            ---Set to true to include lines from Debug Utils into the stack trace. Those show the source of error handling, which you might consider redundant.
            ,   USE_TRY_ON_TRIGGERADDACTION = true              ---Set to true for automatic error handling on TriggerAddAction (applies Debug.try on every trigger action).
            ,   USE_TRY_ON_CONDITION = true                     ---Set to true for automatic error handling on boolexpressions created via Condition() or Filter() (essentially applies Debug.try on every trigger condition).
            ,   USE_TRY_ON_TIMERSTART = true                    ---Set to true for automatic error handling on TimerStart (applies Debug.try on every timer callback).
            ,   USE_TRY_ON_ENUMFUNCS = true                     ---Set to true for automatic error handling on ForGroup, ForForce, EnumItemsInRect and EnumDestructablesInRect (applies Debug.try on every enum callback)
            ,   USE_TRY_ON_COROUTINES = true                    ---Set to true for improved stack traces on errors within coroutines (applies Debug.try on coroutine.create and coroutine.wrap). This lets stack traces point to the erroneous function executed within the coroutine (instead of the function creating the coroutine).
            ,   ALLOW_INGAME_CODE_EXECUTION = true              ---Set to true to enable IngameConsole and -exec command.
            ,   WARNING_FOR_UNDECLARED_GLOBALS = true           ---Set to true to print warnings upon accessing undeclared globals (i.e. globals with nil-value). This is technically the case after having misspelled on a function name (like CraeteUnit instead of CreateUnit).
            ,   SHOW_TRACE_FOR_UNDECLARED_GLOBALS = false       ---Set to true to include a stack trace into undeclared global warnings. Only takes effect, if WARNING_FOR_UNDECLARED_GLOBALS is also true.
            ,   USE_PRINT_CACHE = true                          ---Set to true to let print()-calls during loading screen be cached until the game starts.
            ,   PRINT_DURATION = nil                            ---Adjust the duration in seconds that values printed by print() last on screen. Set to nil to use default duration (which depends on string length).
            ,   USE_NAME_CACHE = true                           ---Set to true to let tostring/print output the string-name of an object instead of its memory location (except for booleans/numbers/strings). E.g. print(CreateUnit) will output "function: CreateUnit" instead of "function: 0063A698".
            ,   AUTO_REGISTER_NEW_NAMES = true                  ---Automatically adds new names from global scope (and subtables of _G up to NAME_CACHE_DEPTH) to the name cache by adding metatables with the __newindex metamethod to ALL tables accessible from global scope.
            ,   NAME_CACHE_DEPTH = 4                            ---Set to 0 to only affect globals. Experimental feature: Set to an integer > 0 to also cache names for subtables of _G (up to the specified depth). Warning: This will alter the __newindex metamethod of subtables of _G (but not break existing functionality).
        }
        --END OF SETTINGS--
        --START OF CODE--
        ,   data = {
                nameCache = {}                                  ---@type table<any,string> contains the string names of any object in global scope (random for objects that have multiple names)
            ,   nameCacheMirror = {}                            ---@type table<string,any> contains the (name,object)-pairs of all objects in the name cache. Used to prevent name duplicates that might otherwise occur upon reassigning globals.
            ,   nameDepths = {}                                 ---@type table<any,integer> contains the depth of the name used by by any object in the name cache (i.e. the depth within the parentTable).
            ,   autoIndexedTables = {}                          ---@type table<table,boolean> contains (t,true), if DebugUtils already set a __newindex metamethod for name caching in t. Prevents double application.
            ,   paramLog = {}                                   ---@type table<string,string> saves logged information per code location. to be filled by Debug.log(), to be printed by Debug.try()
            ,   sourceMap = {{firstLine= 1,file='DebugUtils'}}  ---@type table<integer,{firstLine:integer,file:string,lastLine?:integer}> saves lines and file names of all documents registered via Debug.beginFile().
            ,   printCache = {n=0}                              ---@type string[] contains the strings that were attempted to print during loading screen.
        }
    }
    --localization
    local settings, paramLog, nameCache, nameDepths, autoIndexedTables, nameCacheMirror, sourceMap, printCache = Debug.settings, Debug.data.paramLog, Debug.data.nameCache, Debug.data.nameDepths, Debug.data.autoIndexedTables, Debug.data.nameCacheMirror, Debug.data.sourceMap, Debug.data.printCache

    --Write DebugUtils first line number to sourceMap:
    ---@diagnostic disable-next-line
    Debug.data.sourceMap[1].firstLine = tonumber(codeLoc:match(":\x25d+"):sub(2,-1))

    -------------------------------------------------
    --| File Indexing for local Error Msg Support |--
    -------------------------------------------------

    -- Functions for war3map.lua -> local file conversion for error messages.

    ---Returns the line number in war3map.lua, where this is called (for depth = 0).
    ---Choose a depth d > 0 to instead return the line, where the d-th function in the stack leading to this call is executed.
    ---@param depth? integer default: 0.
    ---@return number?
    function Debug.getLine(depth)
        depth = depth or 0
        local _, location = pcall(error, "", depth + 3) ---@diagnostic disable-next-line
        local line = location:match(":\x25d+") --extracts ":1000" from "war3map.lua:1000:..."
        return tonumber(line and line:sub(2,-1)) --check if line is nil before applying string.sub to prevent errors (nil can result from string.match above, although it should never do so in our case)
    end

    ---Tells the Debug library that the specified file begins exactly here (i.e. in the line, where this is called).
    ---
    ---Using this improves stack traces of error messages. Stack trace will have "war3map.lua"-references between this and the next Debug.endFile() converted to file-specific references.
    ---
    ---To be called in the Lua root in Line 1 of every file you wish to track! Line 1 means exactly line 1, before any comment! This way, the line shown in the trace will exactly match your IDE.
    ---
    ---If you want to use a custom wrapper around Debug.beginFile(), you need to increase the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.beginFile().
    ---@param fileName string
    ---@param depth? integer default: 0. Set to 1, if you call this from a wrapper (and use the wrapper in line 1 of every document).
    ---@param lastLine? integer Ignore this. For compatibility with Total Initialization.
    function Debug.beginFile(fileName, depth, lastLine)
        depth, fileName = depth or 0, fileName or '' --filename is not actually optional, we just default to '' to prevent crashes.
        local line = Debug.getLine(depth + 1)
        if line then --for safety reasons. we don't want to add a non-existing line to the sourceMap
            table.insert(sourceMap, {firstLine = line, file = fileName, lastLine = lastLine}) --automatically sorted list, because calls of Debug.beginFile happen logically in the order of the map script.
        end
    end

    ---Tells the Debug library that the file previously started with Debug.beginFile() ends here.
    ---This is in theory optional to use, as the next call of Debug.beginFile will also end the previous. Still good practice to always use this in the last line of every file.
    ---If you want to use a custom wrapper around Debug.endFile(), you need to increase the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.endFile().
    ---@param depth? integer
    function Debug.endFile(depth)
        depth = depth or 0
        local line = Debug.getLine(depth + 1)
        sourceMap[#sourceMap].lastLine = line
    end

    ---Takes an error message containing a file and a linenumber and converts both to local file and line as saved to Debug.sourceMap.
    ---@param errorMsg string must be formatted like "<document>:<linenumber><RestOfMsg>".
    ---@return string convertedMsg a string of the form "<localDocument>:<localLinenumber><RestOfMsg>"
    function Debug.getLocalErrorMsg(errorMsg)
        local startPos, endPos = errorMsg:find(":\x25d*") --start and end position of line number. The part before that is the document, part after the error msg.
        if startPos and endPos then --can be nil, if input string was not of the desired form "<document>:<linenumber><RestOfMsg>".
            local document, line, rest = errorMsg:sub(1, startPos), tonumber(errorMsg:sub(startPos+1, endPos)), errorMsg:sub(endPos+1, -1) --get error line in war3map.lua
            if document == 'war3map.lua:' and line then --only convert war3map.lua-references to local position. Other files such as Blizzard.j.lua are not converted (obiously).
                for i = #sourceMap, 1, -1 do --find local file containing the war3map.lua error line.
                    if line >= sourceMap[i].firstLine then --war3map.lua line is part of sourceMap[i].file
                        if not sourceMap[i].lastLine or line <= sourceMap[i].lastLine then --if lastLine is given, we must also check for it
                            return sourceMap[i].file .. ":" .. (line - sourceMap[i].firstLine + 1) .. rest
                        else --if line is larger than firstLine and lastLine of sourceMap[i], it is not part of a tracked file -> return global war3map.lua position.
                            break --prevent return within next step of the loop ("line >= sourceMap[i].firstLine" would be true again, but wrong file)
                        end
                    end
                end
            end
        end
        return errorMsg
    end
    local convertToLocalErrorMsg = Debug.getLocalErrorMsg

    ----------------------
    --| Error Handling |--
    ----------------------

    local concat
    ---Applies tostring() on all input params and concatenates them 4-space-separated.
    ---@param firstParam any
    ---@param ... any
    ---@return string
    concat = function(firstParam, ...)
        if select('#', ...) == 0 then
            return tostring(firstParam)
        end
        return tostring(firstParam) .. '    ' .. concat(...)
    end

    ---Returns the stack trace between the specified startDepth and endDepth.
    ---The trace lists file names and line numbers. File name is only listed, if it has changed from the previous traced line.
    ---The previous file can also be specified as an input parameter to suppress the first file name in case it's identical.
    ---@param startDepth integer
    ---@param endDepth integer
    ---@return string trace
    local function getStackTrace(startDepth, endDepth)
        local trace, separator = "", ""
        local _, currentFile, lastFile, tracePiece, lastTracePiece
        for loopDepth = startDepth, endDepth do --get trace on different depth level
            _, tracePiece = pcall(error, "", loopDepth) ---@type boolean, string
            tracePiece = convertToLocalErrorMsg(tracePiece)
            if #tracePiece > 0 and lastTracePiece ~= tracePiece then --some trace pieces can be empty, but there can still be valid ones beyond that
                currentFile = tracePiece:match("^.-:")
                --Hide DebugUtils in the stack trace (except main reference), if settings.INCLUDE_DEBUGUTILS_INTO_TRACE is set to true.
                if settings.INCLUDE_DEBUGUTILS_INTO_TRACE or (loopDepth == startDepth) or currentFile ~= "DebugUtils:" then
                    trace = trace .. separator .. ((currentFile == lastFile) and tracePiece:match(":\x25d+"):sub(2,-1) or tracePiece:match("^.-:\x25d+"))
                    lastFile, lastTracePiece, separator = currentFile, tracePiece, " <- "
                end
            end
        end
        return trace
    end

    ---Message Handler to be used by the try-function below.
    ---Adds stack trace plus formatting to the message and prints it.
    ---@param errorMsg string
    ---@param startDepth? integer default: 4 for use in xpcall
    local function errorHandler(errorMsg, startDepth)
        startDepth = startDepth or 4 --xpcall doesn't specify this param, so it must default to 4 for this case
        errorMsg = convertToLocalErrorMsg(errorMsg)
        --Print original error message and stack trace.
        print("|cffff5555ERROR at " .. errorMsg .. "|r")
        if settings.SHOW_TRACE_ON_ERROR then
            print("|cffff5555Traceback (most recent call first):|r")
            print("|cffff5555" .. getStackTrace(startDepth,200) .. "|r")
        end
        --Also print entries from param log, if there are any.
        for location, loggedParams in pairs(paramLog) do
            print("|cff888888Logged at " .. convertToLocalErrorMsg(location) .. loggedParams .. "|r")
            paramLog[location] = nil
        end
    end

    ---Tries to execute the specified function with the specified parameters in protected mode and prints an error message (including stack trace), if unsuccessful.
    ---
    ---Example use: Assume you have a code line like "CreateUnit(0,1,2)", which doesn't work and you want to know why.
    ---* Option 1: Change it to "Debug.try(CreateUnit, 0, 1, 2)", i.e. separate the function from the parameters.
    ---* Option 2: Change it to "Debug.try(function() return CreateUnit(0,1,2) end)", i.e. pack it into an anonymous function. You can skip the "return", if you don't need the return values.
    ---When no error occured, the try-function will return all values returned by the input function.
    ---When an error occurs, try will print the resulting error and stack trace.
    ---@param funcToExecute function the function to call in protected mode
    ---@param ... any params for the input-function
    ---@return ... any
    function Debug.try(funcToExecute, ...)
        return select(2, xpcall(funcToExecute, errorHandler,...))
    end
    ---@diagnostic disable-next-line lowercase-global
    try = Debug.try

    ---Prints "ERROR:" and the specified error objects on the Screen. Also prints the stack trace leading to the error. You can specify as many arguments as you wish.
    ---
    ---In contrast to Lua's native error function, this can be called outside of protected mode and doesn't halt code execution.
    ---@param ... any objects/errormessages to be printed (doesn't have to be strings)
    function Debug.throwError(...)
        errorHandler(getStackTrace(4,4) .. ": " .. concat(...), 5)
    end

    ---Prints the specified error message, if the specified condition fails (i.e. if it resolves to false or nil).
    ---
    ---Returns all specified arguments after the errorMsg, if the condition holds.
    ---
    ---In contrast to Lua's native assert function, this can be called outside of protected mode and doesn't halt code execution (even in case of condition failure).
    ---@param condition any actually a boolean, but you can use any object as a boolean.
    ---@param errorMsg string the message to be printed, if the condition fails
    ---@param ... any will be returned, if the condition holds
    function Debug.assert(condition, errorMsg, ...)
        if condition then
            return ...
        else
            errorHandler(getStackTrace(4,4) .. ": " .. errorMsg, 5)
        end
    end

    ---Returns the stack trace at the code position where this function is called.
    ---The returned string includes war3map.lua/blizzard.j.lua code positions of all functions from the stack trace in the order of execution (most recent call last). It does NOT include function names.
    ---@return string
    function Debug.traceback()
        return getStackTrace(3,200)
    end

    ---Saves the specified parameters to the debug log at the location where this function is called. The Debug-log will be printed for all affected locations upon the try-function catching an error.
    ---The log is unique per code location: Parameters logged at code line x will overwrite the previous ones logged at x. Parameters logged at different locations will all persist and be printed.
    ---@param ... any save any information, for instance the parameters of the function call that you are logging.
    function Debug.log(...)
        local _, location = pcall(error, "", 3) ---@diagnostic disable-next-line: need-check-nil
        paramLog[location or ''] = concat(...)
    end

    ------------------------------------
    --| Name Caching (API-functions) |--
    ------------------------------------

    --Help-table. The registerName-functions below shall not work on call-by-value-types, i.e. booleans, strings and numbers (renaming a value of any primitive type doesn't make sense).
    local skipType = {boolean = true, string = true, number = true, ['nil'] = true}
    --Set weak keys to nameCache and nameDepths and weak values for nameCacheMirror to prevent garbage collection issues
    setmetatable(nameCache, {__mode = 'k'})
    setmetatable(nameDepths, getmetatable(nameCache))
    setmetatable(nameCacheMirror, {__mode = 'v'})

    ---Removes the name from the name cache, if already used for any object (freeing it for the new object). This makes sure that a name is always unique.
    ---This doesn't solve the 
    ---@param name string
    local function removeNameIfNecessary(name)
        if nameCacheMirror[name] then
            nameCache[nameCacheMirror[name]] = nil
            nameCacheMirror[name] = nil
        end
    end

    ---Registers a name for the specified object, which will be the future output for tostring(whichObject).
    ---You can overwrite existing names for whichObject by using this.
    ---@param whichObject any
    ---@param name string
    function Debug.registerName(whichObject, name)
        if not skipType[type(whichObject)] then
            removeNameIfNecessary(name)
            nameCache[whichObject] = name
            nameCacheMirror[name] = whichObject
            nameDepths[name] = 0
        end
    end

    ---Registers a new name to the nameCache as either just <key> (if parentTableName is the empty string), <table>.<key> (if parentTableName is given and string key doesn't contain whitespace) or <name>[<key>] notation (for other keys in existing tables).
    ---Only string keys without whitespace support <key>- and <table>.<key>-notation. All other keys require a parentTableName.
    ---@param parentTableName string | '""' empty string suppresses <table>-affix.
    ---@param key any
    ---@param object any only call-be-ref types allowed
    ---@param parentTableDepth? integer
    local function addNameToCache(parentTableName, key, object, parentTableDepth)
        parentTableDepth = parentTableDepth or -1
        --Don't overwrite existing names for the same object, don't add names for primitive types.
        if nameCache[object] or skipType[type(object)] then
            return
        end
        local name
        --apply dot-syntax for string keys without whitespace
        if type(key) == 'string' and not string.find(key, "\x25s") then
            if parentTableName == "" then
                name = key
                nameDepths[object] = 0
            else
                name =  parentTableName .. "." .. key
                nameDepths[object] = parentTableDepth + 1
            end
        --apply bracket-syntax for all other keys. This requires a parentTableName.
        elseif parentTableName ~= "" then
            name = type(key) == 'string' and ('"' .. key .. '"') or key
            name = parentTableName .. "[" .. tostring(name) .. "]"
            nameDepths[object] = parentTableDepth + 1
        end
        --Stop in cases without valid name (like parentTableName = "" and key = [1])
        if name then
            removeNameIfNecessary(name)
            nameCache[object] = name
            nameCacheMirror[name] = object
        end
    end

    ---Registers all call-by-reference objects in the given parentTable to the nameCache.
    ---Automatically filters out primitive objects and already registed Objects.
    ---@param parentTable table
    ---@param parentTableName? string
    local function registerAllObjectsInTable(parentTable, parentTableName)
        parentTableName = parentTableName or nameCache[parentTable] or ""
        --Register all call-by-ref-objects in parentTable
        for key, object in pairs(parentTable) do
            addNameToCache(parentTableName, key, object, nameDepths[parentTable])
        end
    end

    ---Adds names for all values of the specified parentTable to the name cache. Names will be "<parentTableName>.<key>" or "<parentTableName>[<key>]", depending on the key type.
    ---
    ---Example: Given a table T = {f = function() end, [1] = {}}, tostring(T.f) and tostring(T[1]) will output "function: T.f" and "table: T[1]" respectively after running Debug.registerNamesFrom(T).
    ---The name of T itself must either be specified as an input parameter OR have previously been registered. It can also be suppressed by inputting the empty string (so objects will just display by their own names).
    ---The names of objects in global scope are automatically registered during loading screen.
    ---@param parentTable table base table of which all entries shall be registered (in the Form parentTableName.objectName).
    ---@param parentTableName? string|'""' Nil: takes <parentTableName> as previously registered. Empty String: Skips <parentTableName> completely. String <s>: Objects will show up as "<s>.<objectName>".
    ---@param depth? integer objects within sub-tables up to the specified depth will also be added. Default: 1 (only elements of whichTable). Must be >= 1.
    ---@overload fun(parentTable:table, depth:integer)
    function Debug.registerNamesFrom(parentTable, parentTableName, depth)
        --Support overloaded definition fun(parentTable:table, depth:integer)
        if type(parentTableName) == 'number' then
            depth = parentTableName
            parentTableName = nil
        end
        --Apply default values
        depth = depth or 1
        parentTableName = parentTableName or nameCache[parentTable] or ""
        --add name of T in case it hasn't already
        if not nameCache[parentTable] and parentTableName ~= "" then
            Debug.registerName(parentTable, parentTableName)
        end
        --Register all call-by-ref-objects in parentTable. To be preferred over simple recursive approach to ensure that top level names are preferred.
        registerAllObjectsInTable(parentTable, parentTableName)
        --if depth > 1 was specified, also register Names from subtables.
        if depth > 1 then
            for _, object in pairs(parentTable) do
                if type(object) == 'table' then
                    Debug.registerNamesFrom(object, nil, depth - 1)
                end
            end
        end
    end

    -------------------------------------------
    --| Name Caching (Loading Screen setup) |--
    -------------------------------------------

    ---Registers all existing object names from global scope and Lua incorporated libraries to be used by tostring() overwrite below.
    local function registerNamesFromGlobalScope()
        --Add all names from global scope to the name cache.
        Debug.registerNamesFrom(_G, "")
        --Add all names of Warcraft-enabled Lua libraries as well:
        --Could instead add a depth to the function call above, but we want to ensure that these libraries are added even if the user has chosen depth 0.
        for _, lib in ipairs({coroutine, math, os, string, table, utf8, Debug}) do
            Debug.registerNamesFrom(lib)
        end
        --Add further names that are not accessible from global scope:
        --Player(i)
        for i = 0, GetBJMaxPlayerSlots() - 1 do
            Debug.registerName(Player(i), "Player(" .. i .. ")")
        end
    end

    --Set empty metatable to _G. __index is added when game starts (for "attempt to read undeclared global"-errors), __newindex is added right below (for building the name cache).
    setmetatable(_G, getmetatable(_G) or {}) --getmetatable(_G) should always return nil provided that DebugUtils is the topmost script file in the trigger editor, but we still include this for safety-

    -- Save old tostring into Debug Library before overwriting it.
    Debug.oldTostring = tostring

    if settings.USE_NAME_CACHE then
        local oldTostring = tostring
        tostring = function(obj) --new tostring(CreateUnit) prints "function: CreateUnit"
            --tostring of non-primitive object is NOT guaranteed to be like "<type>:<hex>", because it might have been changed by some __tostring-metamethod.
            if settings.USE_NAME_CACHE then --return names from name cache only if setting is enabled. This allows turning it off during runtime (via Ingame Console) to revert to old tostring.
                return nameCache[obj] and ((oldTostring(obj):match("^.-: ") or (oldTostring(obj) .. ": ")) .. nameCache[obj]) or oldTostring(obj)
            end
            return Debug.oldTostring(obj)
        end
        --Add names to Debug.data.objectNames within Lua root. Called below the other Debug-stuff to get the overwritten versions instead of the original ones.
        registerNamesFromGlobalScope()

        --Prepare __newindex-metamethod to automatically add new names to the name cache
        if settings.AUTO_REGISTER_NEW_NAMES then
            local nameRegisterNewIndex
            ---__newindex to be used for _G (and subtables up to a certain depth) to automatically register new names to the nameCache.
            ---Tables in global scope will use their own name. Subtables of them will use <parentName>.<childName> syntax.
            ---Global names don't support container[key]-notation (because "_G[...]" is probably not desired), so we only register string type keys instead of using prettyTostring.
            ---@param t table
            ---@param k any
            ---@param v any
            ---@param skipRawset? boolean set this to true when combined with another __newindex. Suppresses rawset(t,k,v) (because the other __newindex is responsible for that).
            nameRegisterNewIndex = function(t,k,v, skipRawset)
                local parentDepth = nameDepths[t] or 0
                --Make sure the parent table has an existing name before using it as part of the child name
                if t == _G or nameCache[t] then
                    local existingName = nameCache[v]
                    if not existingName then
                        addNameToCache((t == _G and "") or nameCache[t], k, v, parentDepth)
                    end
                    --If v is a table and the parent table has a valid name, inherit __newindex to v's existing metatable (or create a new one), if that wasn't already done.
                    if type(v) == 'table' and nameDepths[v] < settings.NAME_CACHE_DEPTH then
                        if not existingName then
                            --If v didn't have a name before, also add names for elements contained in v by construction (like v = {x = function() end} ).
                            Debug.registerNamesFrom(v, settings.NAME_CACHE_DEPTH - nameDepths[v])
                        end
                        --Apply __newindex to new tables.
                        if not autoIndexedTables[v] then
                            autoIndexedTables[v] = true
                            local mt = getmetatable(v)
                            if not mt then
                                mt = {}
                                setmetatable(v, mt) --only use setmetatable when we are sure there wasn't any before to prevent issues with "__metatable"-metamethod.
                            end
                            ---@diagnostic disable-next-line: assign-type-mismatch
                            local existingNewIndex = mt.__newindex
                            local isTable_yn = (type(existingNewIndex) == 'table')
                            --If mt has an existing __newindex, add the name-register effect to it (effectively create a new __newindex using the old)
                            if existingNewIndex then
                                mt.__newindex = function(t,k,v)
                                    nameRegisterNewIndex(t,k,v, true) --setting t[k] = v might not be desired in case of existing newindex. Skip it and let existingNewIndex make the decision.
                                    if isTable_yn then
                                        existingNewIndex[k] = v
                                    else
                                        return existingNewIndex(t,k,v)
                                    end
                                end
                            else
                            --If mt doesn't have an existing __newindex, add one that adds the object to the name cache.
                                mt.__newindex = nameRegisterNewIndex
                            end
                        end
                    end
                end
                --Set t[k] = v.
                if not skipRawset then
                    rawset(t,k,v)
                end
            end

            --Apply metamethod to _G.
            local existingNewIndex = getmetatable(_G).__newindex --should always be nil provided that DebugUtils is the topmost script in your trigger editor. Still included for safety.
            local isTable_yn = (type(existingNewIndex) == 'table')
            if existingNewIndex then
                getmetatable(_G).__newindex = function(t,k,v)
                    nameRegisterNewIndex(t,k,v, true)
                    if isTable_yn then
                        existingNewIndex[k] = v
                    else
                        existingNewIndex(t,k,v)
                    end
                end
            else
                getmetatable(_G).__newindex = nameRegisterNewIndex
            end
        end
    end

    ------------------------------------------------------
    --| Native Overwrite for Automatic Error Handling  |--
    ------------------------------------------------------

    --A table to store the try-wrapper for each function. This avoids endless re-creation of wrapper functions within the hooks below.
    --Weak keys ensure that garbage collection continues as normal.
    local tryWrappers = setmetatable({}, {__mode = 'k'}) ---@type table<function,function>
    local try = Debug.try

    ---Takes a function and returns a wrapper executing the same function within Debug.try.
    ---Wrappers are permanently stored (until the original function is garbage collected) to ensure that they don't have to be created twice for the same function.
    ---@param func? function
    ---@return function
    local function getTryWrapper(func)
        if func then
            tryWrappers[func] = tryWrappers[func] or function(...) return try(func, ...) end
        end
        return tryWrappers[func] --returns nil for func = nil (important for TimerStart overwrite below)
    end

    --Overwrite TriggerAddAction, TimerStart, Condition, Filter and Enum natives to let them automatically apply Debug.try.
    --Also overwrites coroutine.create and coroutine.wrap to let stack traces point to the function executed within instead of the function creating the coroutine.
    if settings.USE_TRY_ON_TRIGGERADDACTION then
        local originalTriggerAddAction = TriggerAddAction
        TriggerAddAction = function(whichTrigger, actionFunc)
            return originalTriggerAddAction(whichTrigger, getTryWrapper(actionFunc))
        end
    end
    if settings.USE_TRY_ON_TIMERSTART then
        local originalTimerStart = TimerStart
        TimerStart = function(whichTimer, timeout, periodic, handlerFunc)
            originalTimerStart(whichTimer, timeout, periodic, getTryWrapper(handlerFunc))
        end
    end
    if settings.USE_TRY_ON_CONDITION then
        local originalCondition = Condition
        Condition = function(func)
            return originalCondition(getTryWrapper(func))
        end
        Filter = Condition
    end
    if settings.USE_TRY_ON_ENUMFUNCS then
        local originalForGroup = ForGroup
        ForGroup = function(whichGroup, callback)
            originalForGroup(whichGroup, getTryWrapper(callback))
        end
        local originalForForce = ForForce
        ForForce = function(whichForce, callback)
            originalForForce(whichForce, getTryWrapper(callback))
        end
        local originalEnumItemsInRect = EnumItemsInRect
        EnumItemsInRect = function(r, filter, actionfunc)
            originalEnumItemsInRect(r, filter, getTryWrapper(actionfunc))
        end
        local originalEnumDestructablesInRect = EnumDestructablesInRect
        EnumDestructablesInRect = function(r, filter, actionFunc)
            originalEnumDestructablesInRect(r, filter, getTryWrapper(actionFunc))
        end
    end
    if settings.USE_TRY_ON_COROUTINES then
        local originalCoroutineCreate = coroutine.create
        ---@diagnostic disable-next-line: duplicate-set-field
        coroutine.create = function(f)
            return originalCoroutineCreate(getTryWrapper(f))
        end
        local originalCoroutineWrap = coroutine.wrap
        ---@diagnostic disable-next-line: duplicate-set-field
        coroutine.wrap = function(f)
            return originalCoroutineWrap(getTryWrapper(f))
        end
    end

    ------------------------------------------
    --| Cache prints during Loading Screen |--
    ------------------------------------------

    -- Apply the duration as specified in the settings.
    if settings.PRINT_DURATION then
        local display, getLocalPlayer, dur = DisplayTimedTextToPlayer, GetLocalPlayer, settings.PRINT_DURATION
        print = function(...) ---@diagnostic disable-next-line: param-type-mismatch
            display(getLocalPlayer(), 0, 0, dur, concat(...))
        end
    end

    -- Delay loading screen prints to after game start.
    if settings.USE_PRINT_CACHE then
        local oldPrint = print
        --loading screen print will write the values into the printCache
        print = function(...)
            if bj_gameStarted then
                oldPrint(...)
            else --during loading screen only: concatenate input arguments 4-space-separated, implicitely apply tostring on each, cache to table
                ---@diagnostic disable-next-line
                printCache.n = printCache.n + 1
                printCache[printCache.n] = concat(...)
            end
        end
    end

    -------------------------
    --| Modify Game Start |--
    -------------------------

    local originalMarkGameStarted = MarkGameStarted
    --Hook certain actions into the start of the game.
    MarkGameStarted = function()
        originalMarkGameStarted()
        if settings.WARNING_FOR_UNDECLARED_GLOBALS then
            local existingIndex = getmetatable(_G).__index
            local isTable_yn = (type(existingIndex) == 'table')
            getmetatable(_G).__index = function(t, k) --we made sure that _G has a metatable further above.
                if string.sub(tostring(k),1,3) ~= 'bj_' then --prevents intentionally nilled bj-variables from triggering the check within Blizzard.j-functions, like bj_cineFadeFinishTimer.
                    print("Trying to read undeclared global at " .. getStackTrace(4,4) .. ": " .. tostring(k)
                        .. (settings.SHOW_TRACE_FOR_UNDECLARED_GLOBALS and "\nTraceback (most recent call first):\n" .. getStackTrace(4,200) or ""))
                end
                if existingIndex then
                    if isTable_yn then
                        return existingIndex[k]
                    end
                    return existingIndex(t,k)
                end
                return rawget(t,k)
            end
        end

        --Add names to Debug.data.objectNames again to ensure that overwritten natives also make it to the name cache.
        --Overwritten natives have a new value, but the old key, so __newindex didn't trigger. But we can be sure that objectNames[v] doesn't yet exist, so adding again is safe.
        if settings.USE_NAME_CACHE then
            for _,v in pairs(_G) do
                nameCache[v] = nil
            end
            registerNamesFromGlobalScope()
        end

        --Print messages that have been cached during loading screen.
        if settings.USE_PRINT_CACHE then
            --Note that we don't restore the old print. The overwritten variant only applies caching behaviour to loading screen prints anyway and "unhooking" always adds other risks.
            for _, str in ipairs(printCache) do
                print(str)
            end ---@diagnostic disable-next-line: cast-local-type
            XXX = printCache
            printCache = nil --frees reference for the garbage collector
        end

        --Create triggers listening to "-console" and "-exec" chat input.
        if settings.ALLOW_INGAME_CODE_EXECUTION and IngameConsole then
            IngameConsole.createTriggers()
        end
    end

    ---------------------
    --| Other Utility |--
    ---------------------

    do
        ---Returns the type of a warcraft object as string, e.g. "unit" upon inputting a unit.
        ---@param input any
        ---@return string
        function Debug.wc3Type(input)
            local typeString = type(input)
            if typeString == 'userdata' then
                typeString = tostring(input) --tostring returns the warcraft type plus a colon and some hashstuff.
                return typeString:sub(1, (typeString:find(":", nil, true) or 0) -1) --string.find returns nil, if the argument is not found, which would break string.sub. So we need to replace by 0.
            else
                return typeString
            end
        end
        Wc3Type = Debug.wc3Type --for backwards compatibility

        local conciseTostring, prettyTostring

        ---Translates a table into a comma-separated list of its (key,value)-pairs. Also translates subtables up to the specified depth.
        ---E.g. {"a", 5, {7}} will display as '{(1, "a"), (2, 5), (3, {(1, 7)})}'.
        ---@param object any
        ---@param depth? integer default: unlimited. Unlimited depth will throw a stack overflow error on self-referential tables.
        ---@return string
        conciseTostring = function (object, depth)
            depth = depth or -1
            if type(object) == 'string' then
                return '"' .. object .. '"'
            elseif depth ~= 0 and type(object) == 'table' then
                local elementArray = {}
                local keyAsString
                for k,v in pairs(object) do
                    keyAsString = type(k) == 'string' and ('"' .. tostring(k) .. '"') or tostring(k)
                    table.insert(elementArray, '(' .. keyAsString .. ', ' .. conciseTostring(v, depth -1) .. ')')
                end
                return '{' .. table.concat(elementArray, ', ') .. '}'
            end
            return tostring(object)
        end

        ---Creates a list of all (key,value)-pairs from the specified table. Also lists subtable entries up to the specified depth.
        ---Major differences to concise print are:
        --- * Format: Linebreak-formatted instead of one-liner, uses "[key] = value" instead of "(key,value)"
        --- * Will also unpack tables used as keys
        --- * Also includes the table's memory position as returned by tostring(table).
        --- * Tables referenced multiple times will only be unpacked upon first encounter and abbreviated on subsequent encounters
        --- * As a consequence, pretty version can be executed with unlimited depth on self-referential tables.
        ---@param object any
        ---@param depth? integer default: unlimited.
        ---@param constTable table
        ---@param indent string
        ---@return string
        prettyTostring = function(object, depth, constTable, indent)
            depth = depth or -1
            local objType = type(object)
            if objType == "string" then
                return '"'..object..'"' --wrap the string in quotes.
            elseif objType == 'table' and depth ~= 0 then
                if not constTable[object] then
                    constTable[object] = tostring(object):gsub(":","")
                    if next(object)==nil then
                        return constTable[object]..": {}"
                    else
                        local mappedKV = {}
                        for k,v in pairs(object) do
                            table.insert(mappedKV, '\n  ' .. indent ..'[' .. prettyTostring(k, depth - 1, constTable, indent .. "  ") .. '] = ' .. prettyTostring(v, depth - 1, constTable, indent .. "  "))
                        end
                        return constTable[object]..': {'.. table.concat(mappedKV, ',') .. '\n'..indent..'}'
                    end
                end
            end
            return constTable[object] or tostring(object)
        end

        ---Creates a list of all (key,value)-pairs from the specified table. Also lists subtable entries up to the specified depth.
        ---Supports concise style and pretty style.
        ---Concise will display {"a", 5, {7}} as '{(1, "a"), (2, 5), (3, {(1, 7)})}'.
        ---Pretty is linebreak-separated, so consider table size before converting. Pretty also abbreviates tables referenced multiple times.
        ---Can be called like table.tostring(T), table.tostring(T, depth), table.tostring(T, pretty_yn) or table.tostring(T, depth, pretty_yn).
        ---table.tostring is not multiplayer-synced.
        ---@param whichTable table
        ---@param depth? integer default: unlimited
        ---@param pretty_yn? boolean default: false (concise)
        ---@return string
        ---@overload fun(whichTable:table, pretty_yn?:boolean):string
        function table.tostring(whichTable, depth, pretty_yn)
            --reassign input params, if function was called as table.tostring(whichTable, pretty_yn)
            if type(depth) == 'boolean' then
                pretty_yn = depth
                depth = -1
            end
            return pretty_yn and prettyTostring(whichTable, depth, {}, "") or conciseTostring(whichTable, depth)
        end

        ---Prints a list of (key,value)-pairs contained in the specified table and its subtables up to the specified depth.
        ---Supports concise style and pretty style. Pretty is linebreak-separated, so consider table size before printing.
        ---Can be called like table.print(T), table.print(T, depth), table.print(T, pretty_yn) or table.print(T, depth, pretty_yn).
        ---@param whichTable table
        ---@param depth? integer default: unlimited
        ---@param pretty_yn? boolean default: false (concise)
        ---@overload fun(whichTable:table, pretty_yn?:boolean)
        function table.print(whichTable, depth, pretty_yn)
            print(table.tostring(whichTable, depth, pretty_yn))
        end
    end
end
Debug.endFile()

(Ingame Console and StringWidth files not changed, so not provided)
 
Update to version 2.1:
  • Automatic error handling extended to Warcraft's enum functions, i.e. Debug Utils now also overrides ForGroup, ForForce, EnumItemsInRect and EnumDestructablesInRect.
  • IngameConsole and StringWidth have been merged into a single file to simplify resource installation, as the second was just used within the first anyway.
  • Disabled undeclared global warnings for bj-variables (because it was more annoying than useful).
 
Last edited:
Level 20
Joined
Jan 3, 2022
Messages
349
Lua:
nilglobal = nil
function helloworld()
    if nilglobal == nil then
        print("boo! its nil!")
    end
    print("hello")
    print("world")
    print("!")
end

helloworld()
Map version 2.0a

Results in overlapping multiboard text (1.36.2) see below. Is it because the game tries to fit a line that's too long in a single cell? Or does that error text contain multiple lines?
console-overlap.gif


PS:
It seems that Lua's pcall and xpcall aren't working as expected on enum natives (also including the popular ForGroup and ForForce).
Yes because the game calls the passed functions itself.
1. I assume you did pcall(ForGroup...) before. There is almost nothing that can go wrong with this call (unless you give a string into a number parameter)
2. The game on its end then attempts to call the passed Filter/Enum/For-Unit function. Now if that call fails, all you'll see is an error message in game's own log file. That's all.

This means it's not sufficient to only wrap a few functions. All functions that are passed as functions to natives must be wrapped as you can assume the game will call these functions on its own later.
 
Last edited:
Results in overlapping multiboard text (1.36.2) see below.
Haha, this took me 2 hours to debug 😅 . Like so often, the underlying issue was a rather simple one. I seem to have accidently used a single carriage return (instead of line feed) to separate lines in a multiline-input statement in IngameConsole. I think I was unsure whether to use LF or CRLF in the past, tried CRLF first (which showed up as two line breaks in Wc3) and deleted the wrong half...
Lua:
--IngameConsole line 229
self.inputload = self.inputload .. inputString .. '\r'
Which wouldn't be too troublesome, if the same console didn't also use the pattern "(\r?\n)" to detect and split line breaks (meaning that CR is optional and LF is mandatory, which doesn't detect a single "\r").

-> Replacing '\r' by '\n' in line 229 solves the issue.
Thanks for the report :) I post a resource update next weekend.

Yes because the game calls the passed functions itself.
1. I assume you did pcall(ForGroup...) before. There is almost nothing that can go wrong with this call (unless you give a string into a number parameter)
2. The game on its end then attempts to call the passed Filter/Enum/For-Unit function. Now if that call fails, all you'll see is an error message in game's own log file. That's all.

This means it's not sufficient to only wrap a few functions. All functions that are passed as functions to natives must be wrapped as you can assume the game will call these functions on its own later.
I expected this for all natives, where callbacks would be executed at a later point in time (trigger actions, boolexpr, timer callbacks, coroutines). Didn't expect it for the Wc3 widget iterators (which are instantly executed), but I am smarter now :plol:.

Regarding which natives to override, it is sufficient to only override the "code entry points". For instance, many natives take a boolexpr callback, but you can simply override Condition and Filter, which are the only ways to create one from a Lua function.

Debug Utils is currently overriding the following code entry points. Feel free to check if I am missing any :)
  • TriggerAddAction
  • TimerStart
  • Condition and Filter
  • ForGroup and ForForce
  • EnumItemsInRect and EnumDestructablesInRect
  • coroutine.create and coroutine.wrap (although this just moves the stack trace to the inside of the coroutine)
 
Level 20
Joined
Jan 3, 2022
Messages
349
it is sufficient to only override the "code entry points".
smart :) you are right, these are all. I double checked all with jassdoc search. Speaking of jassdoc, I don't think the pace I am testing all this at would've been possible without your debug console! Thank you. Even if I ended up with hot code load using Preload files, it wouldn't be as convenient for quick one off tests that I rely on when exploring natives for jassdoc.

Haha, this took me 2 hours to debug 😅 .
:goblin_wtf: Usually I use the pattern \r*\n because I consider carriage return abnormal. But if a single \r was used somewhere... tough. Only a two step conversion could have helped: \r[^\n] -> \n and then \r+\n -> \n.
Hence my advice going forward: stick to Unix-way and use \n everywhere. Everywhere! Like utf-8. On Windows too :D
 
I was wondering if you can improve the undeclared globals feature. Right now, the feature isn't really useful in my opinion, because you always have to end up disabling it. I think you could make it only show a message for an undeclared global once per global and add a function to declare globals which are intentionally nilled and would be exempt from the warning messages.
 
I was wondering if you can improve the undeclared globals feature. Right now, the feature isn't really useful in my opinion, because you always have to end up disabling it. I think you could make it only show a message for an undeclared global once per global and add a function to declare globals which are intentionally nilled and would be exempt from the warning messages.
Good idea. It will take a few weeks until I find time to work on it, but will implement your suggestion asap.
 

Macielos

Hosted Project: W3E
Level 20
Joined
Jul 9, 2010
Messages
264
I've recently started using this lib and my impressions are great. There are too many features to praise, I didn't check many of them, so let's just mention the most important one to me - automatic stacktrace print. Previously to get errors I wrapped all my functions called from triggers with an utility method calling pcall and printing error. So with this lib my code will definitely be simplified.

So far I only have one minor complaint - I have a variable of type Unit which is set to "no unit" (nil in LUA), and when I try to access it, e.g. "if udg_myunit == nil then return end", debugger spams errors even though it's a perfectly valid scenario. It even does when I read it by _G["udg_myunit"].
 
I've recently started using this lib and my impressions are great. There are too many features to praise, I didn't check many of them, so let's just mention the most important one to me - automatic stacktrace print. Previously to get errors I wrapped all my functions called from triggers with an utility method calling pcall and printing error. So with this lib my code will definitely be simplified.

So far I only have one minor complaint - I have a variable of type Unit which is set to "no unit" (nil in LUA), and when I try to access it, e.g. "if udg_myunit == nil then return end", debugger spams errors even though it's a perfectly valid scenario. It even does when I read it by _G["udg_myunit"].
Thanks for your constructive feedback. That's a fair point and in line with what @Antares had critizised in his previous post. The feature for undeclared-global-warnings can currently be disabled in the settings, but I will try to make it more useful in the next update instead, enabling warning-exception configuration for the user. This will require explicit code per global though (which in your case would look similiar to Debug.dontThrowWarningsFor("udg_myunit")), so it might not exactly be what new users like you need from the get-go.

Maybe I should disable the feature per default. It's not the most important one anyway :)
 

Macielos

Hosted Project: W3E
Level 20
Joined
Jul 9, 2010
Messages
264
Thanks for your constructive feedback. That's a fair point and in line with what @Antares had critizised in his previous post. The feature for undeclared-global-warnings can currently be disabled in the settings, but I will try to make it more useful in the next update instead, enabling warning-exception configuration for the user. This will require explicit code per global though (which in your case would look similiar to Debug.dontThrowWarningsFor("udg_myunit")), so it might not exactly be what new users like you need from the get-go.

Maybe I should disable the feature per default. It's not the most important one anyway :)
It would be great if it was able to distinguish between nil values and undeclared variables. Detecting undeclared references is a very useful feature. But I see that it's not trivial, given that LUA mostly doesn't distinguish between nils and undeclared variables, so there's no simple way to call e.g. containsKey(_G, 'mykey'). Iterating pairs(_G) every time is definitely too expensive. You could override set index operator so that whenever something writes to _G, it would also write to some other global e.g. VARIABLES[mykey] = true. Then you could easily check if this variable is indeed undeclared or just null. Not sure if worth the effort, just leavinig it for consideration.
 
Update to version 2.2:
  • Nil-Warnings are now disabled for global variables by default after initializing them. This even holds, if you initialize a global with a nil-value.
    You can re-enable warnings for initialized globals by setting EXCLUDE_INITIALIZED_GLOBALS_FROM_NIL_WARNINGS = false(default: true).
    Thanks to @Macielos for the suggestion.
  • Added the function Debug.disableNilWarningsFor("MyGlobalVariable") to manually exclude any particular global from nil-warnings.
    Thanks to @Antares for the suggestion.
  • Re-enabled nil-warnings for bj-variables, but added a setting EXCLUDE_BJ_GLOBALS_FROM_NIL_WARNINGS (default: false) to let the user decide whether to exclude them or not.
  • Rephrased the code and documentation in a way that these warnings are only being referred to as nil-warnings instead of undeclared-global-warnings. This should emphasize the true functionality to the user and prevent some confusion that I observed in the past.
 
Top