Name | Type | is_array | initial_value |
wsdebug | boolean | No |
do; local _, codeLoc = pcall(error, "", 2) --get line number where DebugUtils begins.
--[[
-------------------------
-- | Debug Utils 2.4 | --
-------------------------
--> 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, original = _G}, {__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.first()
* - Re-prints the very first error on screen that was thrown during map runtime and prevents further error messages and nil-warnings from being printed afterwards.
* - Useful, if a constant stream of error messages hinders you from reading any of them.
* - IngameConsole will still output error messages afterwards for code executed in it.
* 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". If you still need the second, use Debug.original.tostring instead.
* - 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.
*
* -----------------
* | 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(...).
*
* ----------------------
* | Original Functions |
* ----------------------
* - DebugUtils overrides several functions to add error handling or related functionality to them. This involves both Wc3 natives and Lua functions.
* - The following functions have been overridden: tostring, print, coroutine.create, coroutine.wrap, TimerStart, TriggerAddAction, Condition, Filter, ForGroup, ForForce, EnumItemsInRect, EnumDestructablesInRect
* - DebugUtils still provides the original unaltered functions in the 'original' subtable. E.g.: Debug.original.tostring, Debug.original.coroutine.create.
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------]]
-- 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 = false ---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 ---@type float Adjusts 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).
--Colors
, colors = {
error = 'ff5555' ---@type string Color to be applied to error messages
, log = '888888' ---@type string Color to be applied to logged messages through Debug.log().
, nilWarning = 'ffffff' ---@type string Color to be applied to nil-warnings.
}
}
--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.
, printErrors_yn = true ---@type boolean Set to false to disable error messages. Used by Debug.first.
, firstError = nil ---@type string? contains the first error that was thrown. Used by Debug.first.
}
--provide references to the unaltered functions before DebugUtils overrides them.
, original = {
coroutine = {create = coroutine.create, wrap = coroutine.wrap}
, tostring = tostring
, print = print
, TimerStart = TimerStart
, TriggerAddAction = TriggerAddAction
, Condition = Condition
, Filter = Filter
, ForGroup = ForGroup
, ForForce = ForForce
, EnumItemsInRect = EnumItemsInRect
, EnumDestructablesInRect = EnumDestructablesInRect
}
}
--localization
local settings, paramLog, nameCache, nameDepths, autoIndexedTables, nameCacheMirror, sourceMap, printCache, data = Debug.settings, Debug.data.paramLog, Debug.data.nameCache, Debug.data.nameDepths, Debug.data.autoIndexedTables, Debug.data.nameCacheMirror, Debug.data.sourceMap, Debug.data.printCache, Debug.data
--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):gsub('\x25[string "(.-)"\x25]', "\x251")
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:gsub('\x25[string "(.-)"\x25]', "\x251")
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
---@return string
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)
--Original error message and stack trace.
local toPrint = "|cff" .. settings.colors.error .. "ERROR at " .. errorMsg .. "|r"
if settings.SHOW_TRACE_ON_ERROR then
toPrint = toPrint .. "\n|cff" .. settings.colors.error .. "Traceback (most recent call first):|r\n|cff" .. settings.colors.error .. getStackTrace(startDepth,200) .. "|r"
end
--Also print entries from param log, if there are any.
for location, loggedParams in pairs(paramLog) do
toPrint = toPrint .. "\n|cff" .. settings.colors.log .. "Logged at " .. convertToLocalErrorMsg(location) .. loggedParams .. "|r"
paramLog[location] = nil
end
--toPrint = toPrint:gsub('\x25[string "(.-)"\x25]', "\x251")
data.firstError = data.firstError or toPrint
if data.printErrors_yn then --don't print error, if execution of Debug.firstError() has disabled it.
print(toPrint)
end
return toPrint
end
Debug.errorHandler = errorHandler
---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)
---@return string
function Debug.throwError(...)
return 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 --don't return the value from errorHandler below, as it would create return value ambiguity.
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
---Re-prints the very first error that occured during map runtime and prevents any further error messages and nil-warnings from being printed afterwards (for the rest of the game).
---Use this, if a constant stream of error messages hinders you from reading any of them.
---IngameConsole will still output error messages afterwards for code executed in it.
function Debug.first()
data.printErrors_yn = false
print(data.firstError)
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 next, parentTable do --use native pairs even if a custom __pairs metamethod has been defined. We only want to add names for true subtables, not pseudo-ones. This should also prevent bugs with multi-parameter implementations of pairs() like in MDTable.
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 next, parentTable do --use native pairs even if a custom __pairs metamethod has been defined. We only want to add names for true subtables, not pseudo-ones. This should also prevent bugs with multi-parameter implementations of pairs() like in MDTable.
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
--------------------------------
--| Warnings for Nil Globals |--
--------------------------------
--Exclude initialized globals from warnings, even if initialized with nil.
--These warning exclusions take effect immediately, while the warnings take effect at game start (see code after MarkGameStarted below).
if settings.WARNING_FOR_NIL_GLOBALS and 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
-------------------------
--| 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 data.printErrors_yn and (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("|cff" .. settings.colors.nilWarning .. "Trying to read nil global at " .. getStackTrace(4,4) .. ": " .. tostring(k) .. "|r"
.. (settings.SHOW_TRACE_FOR_NIL_WARNINGS and "\n|cff" .. settings.colors.nilWarning .. "Traceback (most recent call first):|r\n|cff" .. settings.colors.nilWarning .. getStackTrace(4,200) .. "|r" 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
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()
---@diagnostic disable: undefined-global
if Debug then Debug.beginFile 'TotalInitialization' end
--[[ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Total Initialization version 5.3.1
Created by: Bribe
Contributors: Eikonium, HerlySQR, Tasyen, Luashine, Forsakn
Inspiration: Almia, ScorpioT1000, Troll-Brain
Hosted at: https://github.com/BribeFromTheHive/Lua/blob/master/TotalInitialization.lua
Debug library hosted at: https://www.hiveworkshop.com/threads/debug-utils-ingame-console-etc.330758/
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ]]
---Calls the user's initialization function during the map's loading process. The first argument should either be the init function,
---or it should be the string to give the initializer a name (works similarly to a module name/identically to a vJass library name).
---
---To use requirements, call `Require.strict 'LibraryName'` or `Require.optional 'LibraryName'`. Alternatively, the OnInit callback
---function can take the `Require` table as a single parameter: `OnInit(function(import) import.strict 'ThisIsTheSameAsRequire' end)`.
---
-- - `OnInit.global` or just `OnInit` is called after InitGlobals and is the standard point to initialize.
-- - `OnInit.trig` is called after InitCustomTriggers, and is useful for removing hooks that should only apply to GUI events.
-- - `OnInit.map` is the last point in initialization before the loading screen is completed.
-- - `OnInit.final` occurs immediately after the loading screen has disappeared, and the game has started.
---@class OnInit
--
--Simple Initialization without declaring a library name:
---@overload async fun(initCallback: Initializer.Callback)
--
--Advanced initialization with a library name and an optional third argument to signal to Eikonium's DebugUtils that the file has ended.
---@overload async fun(libraryName: string, initCallback: Initializer.Callback, debugLineNum?: integer)
--
--A way to yield your library to allow other libraries in the same initialization sequence to load, then resume once they have loaded.
---@overload async fun(customInitializerName: string)
OnInit = {}
---@alias Initializer.Callback fun(require?: Requirement | {[string]: Requirement}):...?
---@alias Requirement async fun(reqName: string, source?: table): unknown
-- `Require` will yield the calling `OnInit` initialization function until the requirement (referenced as a string) exists. It will check the
-- global API (for example, does 'GlobalRemap' exist) and then check for any named OnInit resources which might use that same string as its name.
--
-- Due to the way Sumneko's syntax highlighter works, the return value will only be linted for defined @class objects (and doesn't work for regular
-- globals like `TimerStart`). I tried to request the functionality here: https://github.com/sumneko/lua-language-server/issues/1792 , however it
-- was closed. Presumably, there are other requests asking for it, but I wouldn't count on it.
--
-- To declare a requirement, use: `Require.strict 'SomeLibrary'` or (if you don't care about the missing linting functionality) `Require 'SomeLibrary'`
--
-- To optionally require something, use any other suffix (such as `.optionally` or `.nonstrict`): `Require.optional 'SomeLibrary'`
--
---@class Require: { [string]: Requirement }
---@overload async fun(reqName: string, source?: table): any
Require = {}
do
local library = {} --You can change this to false if you don't use `Require` nor the `OnInit.library` API.
--CONFIGURABLE LEGACY API FUNCTION:
---@param _ENV table
---@param OnInit any
local function assignLegacyAPI(_ENV, OnInit)
OnGlobalInit = OnInit; OnTrigInit = OnInit.trig; OnMapInit = OnInit.map; OnGameStart = OnInit.final --Global Initialization Lite API
--OnMainInit = OnInit.main; OnLibraryInit = OnInit.library; OnGameInit = OnInit.final --short-lived experimental API
--onGlobalInit = OnInit; onTriggerInit = OnInit.trig; onInitialization = OnInit.map; onGameStart = OnInit.final --original Global Initialization API
--OnTriggerInit = OnInit.trig; OnInitialization = OnInit.map --Forsakn's Ordered Indices API
end
--END CONFIGURABLES
local _G, rawget, insert =
_G, rawget, table.insert
local initFuncQueue = {}
---@param name string
---@param continue? function
local function runInitializers(name, continue)
--print('running:', name, tostring(initFuncQueue[name]))
if initFuncQueue[name] then
for _,func in ipairs(initFuncQueue[name]) do
coroutine.wrap(func)(Require)
end
initFuncQueue[name] = nil
end
if library then
library:resume()
end
if continue then
continue()
end
end
local function initEverything()
---@param hookName string
---@param continue? function
local function hook(hookName, continue)
local hookedFunc = rawget(_G, hookName)
if hookedFunc then
rawset(_G, hookName,
function()
hookedFunc()
runInitializers(hookName, continue)
end
)
else
runInitializers(hookName, continue)
end
end
hook(
'InitGlobals',
function()
hook(
'InitCustomTriggers',
function()
hook('RunInitializationTriggers')
end
)
end
)
hook(
'MarkGameStarted',
function()
if library then
for _,func in ipairs(library.queuedInitializerList) do
func(nil, true) --run errors for missing requirements.
end
for _,func in pairs(library.yieldedModuleMatrix) do
func(true) --run errors for modules that aren't required.
end
end
OnInit = nil
Require = nil
end
)
end
---@param initName string
---@param libraryName string | Initializer.Callback
---@param func? Initializer.Callback
---@param debugLineNum? integer
---@param incDebugLevel? boolean
local function addUserFunc(initName, libraryName, func, debugLineNum, incDebugLevel)
if not func then
---@cast libraryName Initializer.Callback
func = libraryName
else
assert(type(libraryName) == 'string')
if debugLineNum and Debug then
Debug.beginFile(libraryName, incDebugLevel and 3 or 2)
Debug.data.sourceMap[#Debug.data.sourceMap].lastLine = debugLineNum
end
if library then
func = library:create(libraryName, func)
end
end
assert(type(func) == 'function')
--print('adding user func: ' , initName , libraryName, debugLineNum, incDebugLevel)
initFuncQueue[initName] = initFuncQueue[initName] or {}
insert(initFuncQueue[initName], func)
if initName == 'root' or initName == 'module' then
runInitializers(initName)
end
end
---@param name string
local function createInit(name)
---@async
---@param libraryName string --Assign your callback a unique name, allowing other OnInit callbacks can use it as a requirement.
---@param userInitFunc Initializer.Callback --Define a function to be called at the chosen point in the initialization process. It can optionally take the `Require` object as a parameter. Its optional return value(s) are passed to a requiring library via the `Require` object (defaults to `true`).
---@param debugLineNum? integer --If the Debug library is present, you can call Debug.getLine() for this parameter (which should coincide with the last line of your script file). This will neatly tie-in with OnInit's built-in Debug library functionality to define a starting line and an ending line for your module.
---@overload async fun(userInitFunc: Initializer.Callback)
return function(libraryName, userInitFunc, debugLineNum)
addUserFunc(name, libraryName, userInitFunc, debugLineNum)
end
end
OnInit.global = createInit 'InitGlobals' -- Called after InitGlobals, and is the standard point to initialize.
OnInit.trig = createInit 'InitCustomTriggers' -- Called after InitCustomTriggers, and is useful for removing hooks that should only apply to GUI events.
OnInit.map = createInit 'RunInitializationTriggers' -- Called last in the script's loading screen sequence. Runs after the GUI "Map Initialization" events have run.
OnInit.final = createInit 'MarkGameStarted' -- Called immediately after the loading screen has disappeared, and the game has started.
do
---@param self table
---@param libraryNameOrInitFunc function | string
---@param userInitFunc function
---@param debugLineNum number
local function __call(
self,
libraryNameOrInitFunc,
userInitFunc,
debugLineNum
)
if userInitFunc or type(libraryNameOrInitFunc) == 'function' then
addUserFunc(
'InitGlobals', --Calling OnInit directly defaults to OnInit.global (AKA OnGlobalInit)
libraryNameOrInitFunc,
userInitFunc,
debugLineNum,
true
)
elseif library then
library:declare(libraryNameOrInitFunc) --API handler for OnInit "Custom initializer"
else
error(
"Bad OnInit args: "..
tostring(libraryNameOrInitFunc) .. ", " ..
tostring(userInitFunc)
)
end
end
setmetatable(OnInit --[[@as table]], { __call = __call })
end
do --if you don't need the initializers for 'root', 'config' and 'main', you can delete this do...end block.
local gmt = getmetatable(_G) or
getmetatable(setmetatable(_G, {}))
local rawIndex = gmt.__newindex or rawset
local hookMainAndConfig
---@param _G table
---@param key string
---@param fnOrDiscard unknown
function hookMainAndConfig(_G, key, fnOrDiscard)
if key == 'main' or key == 'config' then
---@cast fnOrDiscard function
if key == 'main' then
runInitializers 'root'
end
rawIndex(_G, key, function()
if key == 'config' then
fnOrDiscard()
elseif gmt.__newindex == hookMainAndConfig then
gmt.__newindex = rawIndex --restore the original __newindex if no further hooks on __newindex exist.
end
runInitializers(key)
if key == 'main' then
fnOrDiscard()
end
end)
else
rawIndex(_G, key, fnOrDiscard)
end
end
gmt.__newindex = hookMainAndConfig
OnInit.root = createInit 'root' -- Runs immediately during the Lua root, but is yieldable (allowing requirements) and pcalled.
OnInit.config = createInit 'config' -- Runs when `config` is called. Credit to @Luashine: https://www.hiveworkshop.com/threads/inject-main-config-from-we-trigger-code-like-jasshelper.338201/
OnInit.main = createInit 'main' -- Runs when `main` is called. Idea from @Tasyen: https://www.hiveworkshop.com/threads/global-initialization.317099/post-3374063
end
if library then
library.queuedInitializerList = {}
library.customDeclarationList = {}
library.yieldedModuleMatrix = {}
library.moduleValueMatrix = {}
function library:pack(name, ...)
self.moduleValueMatrix[name] = table.pack(...)
end
function library:resume()
if self.queuedInitializerList[1] then
local continue, tempQueue, forceOptional
::initLibraries::
repeat
continue=false
self.queuedInitializerList, tempQueue =
{}, self.queuedInitializerList
for _,func in ipairs(tempQueue) do
if func(forceOptional) then
continue=true --Something was initialized; therefore further systems might be able to initialize.
else
insert(self.queuedInitializerList, func) --If the queued initializer returns false, that means its requirement wasn't met, so we re-queue it.
end
end
until not continue or not self.queuedInitializerList[1]
if self.customDeclarationList[1] then
self.customDeclarationList, tempQueue =
{}, self.customDeclarationList
for _,func in ipairs(tempQueue) do
func() --unfreeze any custom initializers.
end
elseif not forceOptional then
forceOptional = true
else
return
end
goto initLibraries
end
end
local function declareName(name, initialValue)
assert(type(name) == 'string')
assert(library.moduleValueMatrix[name] == nil)
library.moduleValueMatrix[name] =
initialValue and { true, n = 1 }
end
function library:create(name, userFunc)
assert(type(userFunc) == 'function')
declareName(name, false) --declare itself as a non-loaded library.
return function()
self:pack(name, userFunc(Require)) --pack return values to allow multiple values to be communicated.
if self.moduleValueMatrix[name].n == 0 then
self:pack(name, true) --No values were returned; therefore simply package the value as `true`
end
end
end
---@async
function library:declare(name)
declareName(name, true) --declare itself as a loaded library.
local co = coroutine.running()
insert(
self.customDeclarationList,
function()
coroutine.resume(co)
end
)
coroutine.yield() --yields the calling function until after all currently-queued initializers have run.
end
local processRequirement
---@async
function processRequirement(
optional,
requirement,
explicitSource
)
if type(optional) == 'string' then
optional, requirement, explicitSource =
true, optional, requirement --optional requirement (processed by the __index method)
else
optional = false --strict requirement (processed by the __call method)
end
local source = explicitSource or _G
assert(type(source)=='table')
assert(type(requirement)=='string')
::reindex::
local subSource, subReq =
requirement:match("([\x25w_]+)\x25.(.+)") --Check if user is requiring using "table.property" syntax
if subSource and subReq then
source,
requirement =
processRequirement(subSource, source), --If the container is nil, yield until it is not.
subReq
if type(source)=='table' then
explicitSource = source
goto reindex --check for further nested properties ("table.property.subProperty.anyOthers").
else
return --The source table for the requirement wasn't found, so disregard the rest (this only happens with optional requirements).
end
end
local function loadRequirement(unpack)
local package = rawget(source, requirement) --check if the requirement exists in the host table.
if not package and not explicitSource then
if library.yieldedModuleMatrix[requirement] then
library.yieldedModuleMatrix[requirement]() --load module if it exists
end
package = library.moduleValueMatrix[requirement] --retrieve the return value from the module.
if unpack and type(package)=='table' then
return table.unpack(package, 1, package.n) --using unpack allows any number of values to be returned by the required library.
end
end
return package
end
local co, loaded
local function checkReqs(forceOptional, printErrors)
if not loaded then
loaded = loadRequirement()
loaded = loaded or optional and
(loaded==nil or forceOptional)
if loaded then
if co then coroutine.resume(co) end --resume only if it was yielded in the first place.
return loaded
elseif printErrors then
coroutine.resume(co, true)
end
end
end
if not checkReqs() then --only yield if the requirement doesn't already exist.
co = coroutine.running()
insert(library.queuedInitializerList, checkReqs)
if coroutine.yield() then
error("Missing Requirement: "..requirement) --handle the error within the user's function to get an accurate stack trace via the `try` function.
end
end
return loadRequirement(true)
end
---@type Requirement
function Require.strict(name, explicitSource)
return processRequirement(nil, name, explicitSource)
end
setmetatable(Require --[[@as table]], {
__call = processRequirement,
__index = function()
return processRequirement
end
})
local module = createInit 'module'
--- `OnInit.module` will only call the OnInit function if the module is required by another resource, rather than being called at a pre-
--- specified point in the loading process. It works similarly to Go, in that including modules in your map that are not actually being
--- required will throw an error message.
---@param name string
---@param func fun(require?: Initializer.Callback):any
---@param debugLineNum? integer
OnInit.module = function(name, func, debugLineNum)
if func then
local userFunc = func
func = function(require)
local co = coroutine.running()
library.yieldedModuleMatrix[name] =
function(failure)
library.yieldedModuleMatrix[name] = nil
coroutine.resume(co, failure)
end
if coroutine.yield() then
error("Module declared but not required: ")
end
return userFunc(require)
end
end
module(name, func, debugLineNum)
end
end
if assignLegacyAPI then --This block handles legacy code.
---Allows packaging multiple requirements into one table and queues the initialization for later.
---@deprecated
---@param initList string | table
---@param userFunc function
function OnInit.library(initList, userFunc)
local typeOf = type(initList)
assert(typeOf=='table' or typeOf=='string')
assert(type(userFunc) == 'function')
local function caller(use)
if typeOf=='string' then
use(initList)
else
for _,initName in ipairs(initList) do
use(initName)
end
if initList.optional then
for _,initName in ipairs(initList.optional) do
use.lazily(initName)
end
end
end
end
if initList[name] then
OnInit(initList[name], caller)
else
OnInit(caller)
end
end
local legacyTable = {}
assignLegacyAPI(legacyTable, OnInit)
for key,func in pairs(legacyTable) do
rawset(_G, key, func)
end
OnInit.final(function()
for key in pairs(legacyTable) do
rawset(_G, key, nil)
end
end)
end
initEverything()
end
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "WarcraftStudioCode" end
do
--[[
=============================================================================================================================================================
Warcraft Studio Code
by Antares
v2.1
An in-game Lua editor, compiler, and debugger.
Requires:
HandleType https://www.hiveworkshop.com/threads/.354436/
World2Screen https://www.hiveworkshop.com/threads/.354017/
DebugUtils (optional) Modified version included in Test Map.
PrecomputedHeightMap(optional) https://www.hiveworkshop.com/threads/.353477/
FileIO (optional) Included in Test Map.
FunctionPreviews (optional) Included in Test Map.
TotalInitialization (optional) https://www.hiveworkshop.com/threads/.317099/
ALICE (optional) https://www.hiveworkshop.com/threads/.353126/
=============================================================================================================================================================
A P I
=============================================================================================================================================================
WSCode.Parse(functionBody) Parses the provided code, searching for function definitions to generate function previews, then executes the
code in chunks delimited by either Debug.beginFile or @beginFile tokens, which must be followed by the script
name. Parsed scripts can be added to the editor with the Pull Script button. A script will automatically be
transpiled into debug form and added to the editor if a @debug token is found anywhere in the script. Designed
to read in the entire map script.
WSCode.AddBreakPoint(whichTab, lineNumber) Adds a breakpoint to a line in the specified tab. The tab can be referenced either by its tab index or its name.
WSCode.BreakHere() Halts exeuction of the code when it reaches this line and switches to the tab it is executed in. Script must be
compiled in debug mode. A breakpoint added this way cannot be disabled.
WSCode.Show(enable) Shows or hides the code editor.
WSCode.IsEnabled() Returns whether the code editor has been created.
WSCode.IsVisible() Returns whether the code editor is visible.
WSCode.IsExpanded() Returns whether the code editor is expanded.
These functions represent the old way of reading in map scripts. They are succeeded by the Parse function, which is in many ways superior.
WSCode.Store(tabName, functionBody) Stores the provided string in the code editor upon map launch.
WSCode.Compile(tabName, functionBody) Stores the provided string in the code editor upon map launch, then executes it.
WSCode.Transpile(tabName, functionBody) Stores the provided string in the code editor upon map launch, then transpiles it into its debug form and
executes it.
WSCode.Execute(tabName, functionBody) Executes the provided code.
=============================================================================================================================================================
C O N F I G
=============================================================================================================================================================
]]
local compiler = {
--If enabled, WSCode will wrap coroutines around everything, allowing it to yield from the parent function on a breakpoint instead of just the function that
--was added to the debugger. This means that if an external function calls a function that is being debugged, the parent function will not continue executing
--when the function being debugged is halted. In addition, the complete traceback will be available on an error. This feature slows down code execution, even
--with all WSCode wrappers removed, so set it to "never" for your release version.
WRAP_ALL_BLIZZARD_API_POINTS = "always" ---@type "always" | "editoropen" | "never"
--If a function is wrapped in a caller function, it remains persistent throughout function redefinitions. When recompiling functions, WSCode will only swap
--out the functions the wrappers call, which means that existing references to the old functions will correctly update to the new ones when you recompile a
--chunk of code. The wrapped function is referenced by its name. Therefore, anonymous functions cannot be wrapped, and you must not rename functions or have
--two functions with the same name in any one script. A script must have been originally compiled in debug mode for wrappers to work.
,WRAP_IN_CALLER_FUNCTIONS = true
--Throw an error when a function returns a framehandle with handle id 0. Requires script to be compiled in debug mode.
,VALIDATE_FRAMES = true
--All functions set in editor.CREATOR_DESTRUCTOR_PAIRS will be wrapped to capture the handles created within scripts that are not transpiled into debug form, so
--that they can be cleaned up when those scripts are eventually recompiled within the editor.
,STORE_HANDLES_IN_NO_DEBUG_MODE = true
--Automatically loads into the editor scripts that have been parsed by WSCode.Parse, but are not currently being debugged, when they produce an error.
,LOAD_EXTERNAL_SCRIPTS_ON_ERROR = true
}
local editor = {
--Enables the "-wscode" command for the players with these names. Uses substring, therefore #XXXX not required and an empty string enables it for all players.
MAP_CREATORS = { ---@constant string[]
""
}
,NEXT_LINE_HOTKEY = "F3" ---@constant string | nil
,CONTINUE_HOTKEY = "F4" ---@constant string | nil
,TOGGLE_HIDE_EDITOR_HOTKEY = "F1" ---@constant string | nil
--0 = None, 1 = Shift, 2 = Ctrl, 3 = Shift + Ctrl
,NEXT_LINE_METAKEY = 0 ---@constant integer
,CONTINUE_METAKEY = 0 ---@constant integer
,TOGGLE_HIDE_EDITOR_METAKEY = 0 ---@constant integer
--Replaces the standard Friz Quadrata font with Consolas. This will ensure that all tab spaces remain aligned. Requires Consolas.ttf.
,USE_MONOSPACE_FONT = true
--The folder to which all text files are exported. The complete path is Warcraft III\CustomMapData\Subfolder\
,EXPORT_SUBFOLDER = "WSCode"
--Automatically initialize the editor on map start so that it can be pulled up with the TOGGLE_HIDE_EDITOR_HOTKEY without having to type -wscode.
,AUTO_INITIALIZE = false
--Since hotkeys do not work because all inputs are intercepted by the editbox frame, you can set characters here that, when entered, will emulate the
--function of those hotkeys.""
,LEFT_ARROW_REPLACEMENT_CHAR = "ΓΆ"
,RIGHT_ARROW_REPLACEMENT_CHAR = "Γ€"
,TAB_REPLACEMENT_CHAR = "Β°"
,DELETE_REPLACEMENT_CHAR = "ΓΌ"
--Automatically close (, {, [, ", and ' when typed.
,AUTO_CLOSE_BRACKETS_AND_QUOTES = true
--These constants determine which flags are automatically set when launching the editor. They can be toggled with the flags button at the bottom of the
--editor.
,DEBUG_MODE_ON_BY_DEFAULT = true
--Run the OnInit functions (TotalInitialization) within a script when recompiled.
,RUN_INIT_ON_BY_DEFAULT = false
--Store all handles created by a script and destroy all handles created by the previous compilation when recompiled.
,CLEAN_HANDLES_ON_BY_DEFAULT = false
--Hide constants (SCREAMING_SNAKE_CASE) from the variable viewer.
,HIDE_CONSTANTS_ON_BY_DEFAULT = false
--Hide global variables from the variable viewer.
,HIDE_GLOBALS_ON_BY_DEFAULT = false
--Inherit all non-function upvalues from the previous compilation of a library when recompiled.
,PERSIST_UPVALUES_ON_BY_DEFAULT = false
--Automatically halt ALICE when stopping at a breakpoint. All units will be paused as well.
,HALT_ALICE_ON_BREAKPOINT_ON_BY_DEFAULT = true
--Saves the code in the current tab to a file named WSCodeAutoSave<TabName>.
,AUTO_SAVE_AFTER_EDIT = true
,AUTO_SAVE_MIN_CODE_LINES = 10
--This function is called when the editor is shown or enabled.
,ON_ENABLE = function()
--Uncomment this to hide the default UI when the editor is opened, giving you more space to work with.
--[[BlzHideOriginFrames(true)
for i = 0, 11 do
BlzFrameSetVisible(BlzGetFrameByName("CommandButton_" .. i, 0), false)
end]]
end
--This function is called when the editor is hidden.
,ON_DISABLE = function()
--[[BlzHideOriginFrames(false)
for i = 0, 11 do
BlzFrameSetVisible(BlzGetFrameByName("CommandButton_" .. i, 0), true)
end]]
end
--This function is called when the script execution halts at a breakpoint.
,ON_BREAK = function(scriptName)
end
--This function is called when the script execution resumes from a breakpoint.
,ON_RESUME = function(scriptName)
end
--Setting a list of legal characters protects you from messing up your code by accidently pressing the key of a character that cannot be displayed,
--leading to compile errors from seemingly valid code. This list contains all utf8 1-byte characters except the grave accents. Set to nil to disable.
,LEGAL_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!$&()[]=?:;,._#*~/{}<>^+-|'\"\\@\x25 1234567890\t"
--If the user presses no key for this long, a new undo step is created.
,MIN_TIME_BETWEEN_UNDO_STEPS = 1.0
,GET_PARENT = function()
--This makes the editor appear above the Console UI
--[[CreateLeaderboardBJ(bj_FORCE_ALL_PLAYERS, "title")
local parent = BlzGetFrameByName("Leaderboard", 0)
BlzFrameSetSize(parent, 0, 0)
BlzFrameSetVisible(BlzGetFrameByName("LeaderboardBackdrop", 0), false)
BlzFrameSetVisible(BlzGetFrameByName("LeaderboardTitle", 0), false)
return parent]]
--This makes the editor appear below the Console UI
return BlzGetFrameByName("ConsoleUIBackdrop", 0)
end
--These parameter control the layout of the code editor.
,COLLAPSED_X = 0.567
,Y_TOP = 0.555
,EXPANDED_X = 0.187
,LINE_SPACING = 0.0085
,MAX_LINES_ON_SCREEN = 39
,COLLAPSED_WIDTH = 0.353
,EXPANDED_WIDTH = 0.733
,CODE_LEFT_INSET = 0.045
,CODE_RIGHT_INSET = 0.025
,CODE_TOP_INSET = 0.025
,CODE_BOTTOM_INSET = 0.013
,LINE_NUMBER_HORIZONTAL_INSET = 0.006
,STOP_BUTTON_HORIZONTAL_INSET = 0.023
,LINE_HIGHLIGHT_HORIZONTAL_INSET = 0.034
,CODE_SCROLLER_HORIZONTAL_INSET = 0.013
,BOTTOM_BUTTON_SIZE = 0.025
,BOTTOM_BUTTON_VERTICAL_SHIFT = -0.022
,CHECKBOX_HORIZONTAL_INSET = 0.032
,CHECKBOX_VERTICAL_SHIFT = -0.007
,CHECKBOX_TEXT_VERTICAL_SHIFT = -0.0015
,CHECKBOX_TEXT_SPACING = 0.002
,TEXT_CHECKBOX_SPACING = 0.008
,DELETE_BUTTON_HORIZONTAL_INSET = 0.007
,DELETE_BUTTON_HIGHLIGHT_INSET = 0.002
,CLOSE_BUTTON_INSET = 0.005
,CLOSE_BUTTON_SIZE = 0.017
,TITLE_VERTICAL_SHIFT = -0.0085
,LINE_NUMBER_SCALE = 0.9
--0-255
,BLACK_BACKDROP_ALPHA = 150
--The colors of syntax highlighting.
,KEYWORD_COLOR = "|cffff9d98"
,FUNCTION_NAME_COLOR = "|cff9aaaee"
,NATIVE_COLOR = "|cfff0a77e"
,COMMENT_COLOR = "|cff8a8a92"
,STRING_COLOR = "|cff77b9c3"
,VALUE_COLOR = "|cff8bbaf7"
--The colors of tab navigators.
,TAB_NAVIGATOR_SELECTED_COLOR = "|cffffffff"
,TAB_NAVIGATOR_UNSELECTED_COLOR = "|cffaaaaaa"
}
--Links the name of a destructor function with the name of a creator function that is used to clean up objects created
--by previous compilations of the code chunk when "Clean Handles" is enabled.
local CREATOR_DESTRUCTOR_PAIRS = { ---@constant table<string, string>
CreateTimer = "DestroyTimer",
CreateGroup = "DestroyGroup",
CreateForce = "DestroyForce",
CreateRegion = "RemoveRegion",
CreateTrigger = "DestroyTrigger",
CreateDestructable = "RemoveDestructable",
CreateDestructableZ = "RemoveDestructable",
CreateDeadDestructable = "RemoveDestructable",
CreateDeadDestructableZ = "RemoveDestructable",
CreateItem = "RemoveItem",
CreateUnit = "RemoveUnit",
CreateUnitByName = "RemoveUnit",
CreateUnitAtLoc = "RemoveUnit",
CreateUnitAtLocByName = "RemoveUnit",
CreateCorpse = "RemoveUnit",
CreateFogModifierRect = "DestroyFogModifier",
CreateFogModifierRadius = "DestroyFogModifier",
CreateFogModifierRadiusLoc = "DestroyFogModifier",
DialogCreate = "DialogDestroy",
CreateUnitPool = "DestroyUnitPool",
CreateItemPool = "DestroyItemPool",
CreateMinimapIconOnUnit = "DestroyMinimapIcon",
CreateMinimapIconAtLoc = "DestroyMinimapIcon",
CreateMinimapIcon = "DestroyMinimapIcon",
CreateTextTag = "DestroyTextTag",
CreateQuest = "DestroyQuest",
CreateDefeatCondition = "DestroyDefeatCondition",
CreateTimerDialog = "DestroyTimerDialog",
CreateLeaderboard = "DestroyLeaderboard",
CreateMultiboard = "DestroyMultiboard",
CreateImage = "DestroyImage",
CreateUbersplat = "DestroyUbersplat",
BlzCreateFrame = "BlzDestroyFrame",
BlzCreateSimpleFrame = "BlzDestroyFrame",
BlzCreateFrameByType = "BlzDestroyFrame",
CreateCommandButtonEffect = "DestroyCommandButtonEffect",
CreateUpgradeCommandButtonEffect = "DestroyCommandButtonEffect",
CreateLearnCommandButtonEffect = "DestroyCommandButtonEffect",
BlzCreateItemWithSkin = "RemoveItem",
BlzCreateUnitWithSkin = "RemoveUnit",
BlzCreateDestructableWithSkin = "RemoveDestructable",
BlzCreateDestructableZWithSkin = "RemoveDestructable",
BlzCreateDeadDestructableWithSkin = "RemoveDestructable",
BlzCreateDeadDestructableZWithSkin = "RemoveDestructable",
AddSpecialEffect = "DestroyEffect",
AddSpecialEffectTarget = "DestroyEffect",
ALICE_Create = "ALICE_Destroy",
ALICE_CallPeriodic = "ALICE_DisableCallback",
ALICE_CallRepeated = "ALICE_DisableCallback",
ALICE_CallDelayed = "ALICE_DisableCallback",
ALICE_PairCallDelayed = "ALICE_DisableCallback"
}
local mouse = {
x = 0,
y = 0
}
--Use this function to define automatic replacements within the Code Editor. The function takes the current line being edited and returns
--the line with the replacements added.
local function HandleTextMacros(line)
if line:find("!COORDS") then
if line:find("!COORDS!") then
return line:gsub("!COORDS!", math.floor(mouse.x) .. ", " .. math.floor(mouse.y))
elseif line:find("!COORDS3D!") then
return line:gsub("!COORDS3D!", math.floor(mouse.x) .. ", " .. math.floor(mouse.y) .. ", " .. math.floor(GetLocZ(mouse.x, mouse.y)))
end
elseif line:find("!SCREEN!") then
local x, y = World2Screen(mouse.x, mouse.y, GetTerrainZ(mouse.x, mouse.y))
return line:gsub("!SCREEN!", string.format("\x25.4f", x) .. ", " .. string.format("\x25.4f", y))
elseif line:find("!UNIT!") then
local unit = ALICE_GetClosestObject(mouse.x, mouse.y, "unit")
local name = GetUnitName(unit)
local i = 0
repeat
i = i + 1
until WSDebug.Vars[name .. i] == nil
WSDebug.Vars[name .. i] = unit
return line:gsub("!UNIT!", "WSDebug.Vars." .. name .. i)
elseif line:find("!CAM!") then
return line:gsub("!CAM!",
"{x = " .. math.floor(GetCameraTargetPositionX()) ..
", y = " .. math.floor(GetCameraTargetPositionY()) ..
", angleOfAttack = " .. string.format("\x25.3f", GetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK)) ..
", rotation = " .. string.format("\x25.3f", GetCameraField(CAMERA_FIELD_ROTATION)) ..
", targetDistance = " .. math.floor(GetCameraField(CAMERA_FIELD_TARGET_DISTANCE)) ..
", zOffset = " .. math.floor(GetCameraField(CAMERA_FIELD_ZOFFSET)) .. "}")
end
end
local tabNavigator = {
VERTICAL_SHIFT = -0.007
,WIDTH = 0.062
,HEIGHT = 0.028
,OVERLAP = 0.007
,TEXT_INSET = 0.008
,CLOSE_BUTTON_INSET = 0.003
,CLOSE_BUTTON_SIZE = 0.01
}
local variableViewer = {
WIDTH = 0.24
,LINE_HORIZONTAL_INSET = 0.01
,LINE_VERTICAL_INSET = 0.025
,BOTTOM_SPACE = 0.012
,VARIABLE_MAX_STRING_LENGTH = 25
,FUNCTION_CALL_SPACING = 0.007
,FUNCTION_PARAM_MAX_STRING_LENGTH = 42
,TEXT_SCALE = 0.9
,GLOBAL_COLOR = "|cffaaaaff"
,LOCAL_COLOR = "|cffffffff"
}
local searchBar = {
HORIZONTAL_INSET = 0.023
,VERTICAL_INSET = 0.023
,WIDTH = 0.15
,HEIGHT = 0.04
,SEARCH_FIELD_LEFT_INSET = 0.0
,SEARCH_FIELD_RIGHT_INSET = 0.0
,SEARCH_FIELD_TOP_INSET = 0.013
,SEARCH_FIELD_BOTTOM_INSET = 0.0
,SEARCH_BUTTON_INSET = 0.005
,BUTTON_SIZE = 0.015
,NUM_FINDS_LEFT_INSET = 0.009
,NUM_FINDS_TOP_INSET = 0.01
}
local nameDialog = {
WIDTH = 0.16
,HEIGHT = 0.06
,TITLE_VERTICAL_SHIFT = 0.012
,ENTER_BOX_HORIZONTAL_INSET = 0.01
,ENTER_BOX_VERTICAL_SHIFT = 0.025
,ENTER_BOX_HEIGHT = 0.027
}
local flagsMenu = {
WIDTH = 0.09
,TEXT_INSET = 0.007
}
--[[
=============================================================================================================================================================
E N D O F C O N F I G
=============================================================================================================================================================
]]
local tab2D = {__index = function(self, key) self[key] = {} return self[key] end}
local codeEditorParent
local enterBox
local cursorFrame
local codeLineFrames = {}
local lineNumbers = {}
local stopButtons = {}
local indexFromFrame = {}
local duplicateHighlightFrames = {}
local flagsMenuParent
local codeScroller
local contextMenu = {}
local meta3D = {
__index = function(parent, parentKey)
parent[parentKey] = setmetatable({}, {
__index = function(self, key)
self[key] = setmetatable({}, {
__index = function(child, childKey)
child[childKey] = ""
return child[childKey]
end
})
return self[key]
end
})
return parent[parentKey]
end
}
local codeLines = setmetatable({}, meta3D)
local coloredCodeLines = setmetatable({}, meta3D)
local isExpanded = false
local variableViewerIsExpanded = true
local cursor = {
pos = nil,
rawLine = nil,
adjustedLine = nil,
x = 0
}
variableViewer.frames = {}
variableViewer.valueFrames = {}
variableViewer.textFrames = {}
variableViewer.functionFrames = {}
variableViewer.functionParamFrames = {}
variableViewer.functionTextFrames = {}
variableViewer.nameFromFrame = {}
variableViewer.linkedWidget = {}
mouse.lastMove = 0
local isShowingVariableTooltip = false
local checkedForVariableDisplay = false
local helperFrame
local helperText
local functionPreviewBox
local widthTestFrame
local variableViewerTitle
local buttons = {}
local currentLineHighlight
local errorHighlight
local hasCurrentLineHighlight
local lastLineNumber = setmetatable({}, {__index = function(self, key) self[key] = 0 return 0 end})
local lastViewedLineNumber = {}
local errorLineNumber = 0
local currentStop = 0
local cursorCounter = 0
local numFunctions = 0
local leftMouseButtonIsPressed = false
local lastMouseClick = 0
local timeOfLastBreakpoint = 0
local hasError = {}
local editBoxFocused = false
local viewedVariable
local viewedValueTrace = {}
local viewedVariableTrace = {}
local globalLookupTable = {}
local localLookupTable = {}
local autoCompleteSuggestions = {}
local lastFunctionCall
local lastViewedFunctionCall
local viewingParamsForFunctionCounter
local functionCallCounter = 0
local functionParams = {}
local concatTable = setmetatable({}, {__concat = function(self, str) self[#self + 1] = str return self end})
local anchor
local enterBoxText = " "
local ignoreMouseClickUntil = 0
local changeMade
local clipboard
local initError
local canJumpToError = true
local user
local coroutineWrapEnabled = false
local coroutineIsDead = {}
local lineByLine
local numVisibleVars = 0
local highestNonEmptyLine = setmetatable({}, {
__index = function(self, key)
self[key] = setmetatable({}, {
__index = function()
return 0
end
})
return self[key]
end
})
local lines = {
endsInLongString = setmetatable({}, tab2D),
endsInLongComment = setmetatable({}, tab2D),
numEqualSigns = {},
debugState = setmetatable({}, {__index = function(self, key) self[key] = setmetatable({}, {__index = function(child, childKey) child[childKey] = 0 return 0 end}) return self[key] end})
}
local flags = {}
local handles = setmetatable({}, {__index = function(self, key) self[key] = setmetatable({}, {__mode = "k"}) return self[key] end})
local files = {}
local fileNames = {}
local orderedFileNames = {}
local currentTab = "Main"
local step = setmetatable({}, {__index = function(self, key) self[key] = 1 return 1 end})
local maxRedo = setmetatable({}, {__index = function(self, key) self[key] = 0 return 0 end})
local lineNumberOffset = setmetatable({}, {__index = function(self, key) self[key] = 0 return 0 end})
local lineNumberOfEdit = setmetatable({}, tab2D)
local posOfEdit = setmetatable({}, tab2D)
local lastKeyStroke = 0
local lastFrame = 0
local timeElapsed = 0
local firstDifferentChar = 0
local aliceIsHalted
local wrapperFunc = setmetatable({}, tab2D)
local wrappedFunc = setmetatable({}, tab2D)
local wrapperGenerationCounter = setmetatable({}, {__index = function(self, key) self[key] = setmetatable({}, {__index = function(child, childKey) child[childKey] = 0 return 0 end}) return self[key] end})
local numTabs = 1
local tabNames = {"Main"}
local tabNumber = {Main = 1}
local addTabFrame
local addTabTitle
local tabNumberFromFrame = {}
local executionTab = "Main"
tabNavigator.frames = {}
tabNavigator.titles = {}
tabNavigator.highlights = {}
tabNavigator.closeButtons = {}
tabNavigator.widths = {tabNavigator.WIDTH}
local varTable = setmetatable({}, tab2D)
local visibleVarsOfLine = setmetatable({}, {__index = function(self, key) self[key] = setmetatable({}, tab2D) return self[key] end})
local varLevelExecution = setmetatable({}, tab2D)
local textHighlights = {}
local highlightTypeOfFrame = {}
local selection = {
lines = {}
}
searchBar.text = ""
searchBar.numFinds = 0
searchBar.highlights = {}
searchBar.lines = {}
searchBar.startPos = {}
searchBar.endPos = {}
searchBar.current = 0
local hasCodeOnInit = false
local sub = string.sub
local find = string.find
local match = string.match
local benchmark = {
results = {}
}
local brackets = {
highlights = {},
lines = {},
pos = {},
IS_BRACKET = {
["("] = true,
[")"] = true,
["["] = true,
["]"] = true,
["{"] = true,
["}"] = true
},
CORRESPONDING_BRACKET = {
["("] = ")",
[")"] = "(",
["["] = "]",
["]"] = "[",
["{"] = "}",
["}"] = "{"
}
}
local function DoNothing() end
local EnableEditor
local UpdateVariableViewer
local ToggleExpand
local ToggleVariableViewerExpand
local SetSelection
local HideSelection
local HandleError
local StoreCode
local RenderSearchHighlights
local FindSearchItems
local Pull
local JumpToError
local SwitchToTab
local Value2String
local CloseTab
local LINE_STATE_ICONS = {
[0] = "transparentmask.blp",
[1] = "spyGlass.blp",
[2] = "stopButton.blp",
}
local LUA_CONTROL_FLOW_STATEMENTS = {
["if"] = true,
["then"] = true,
["else"] = true,
["elseif"] = true,
["end"] = true,
["for"] = true,
["while"] = true,
["repeat"] = true,
["until"] = true,
["return"] = true,
["do"] = true,
["break"] = true
}
local LUA_KEYWORDS = {
["if"] = true,
["then"] = true,
["else"] = true,
["elseif"] = true,
["end"] = true,
["for"] = true,
["while"] = true,
["repeat"] = true,
["until"] = true,
["return"] = true,
["do"] = true,
["break"] = true,
["or"] = true,
["and"] = true,
["not"] = true,
["function"] = true,
["in"] = true,
["local"] = true
}
local LUA_OPERATORS = {
["+"] = true,
["-"] = true,
["*"] = true,
["/"] = true,
["//"] = true,
["\x25"] = true,
["{"] = true,
["}"] = true,
["["] = true,
["]"] = true,
["("] = true,
[")"] = true,
[","] = true,
["^"] = true,
["~="] = true,
["<"] = true,
[">"] = true,
["<="] = true,
[">="] = true,
["="] = true,
["=="] = true,
[".."] = true,
["and"] = true,
["or"] = true,
["not"] = true,
["&"] = true,
["|"] = true,
["~"] = true,
["<<"] = true,
[">>"] = true
}
local LUA_BINARY_OPERATORS = {
["+"] = true,
["-"] = true,
["*"] = true,
["/"] = true,
["//"] = true,
["\x25"] = true,
[","] = true,
["^"] = true,
["<"] = true,
[">"] = true,
["~="] = true,
["=="] = true,
["<="] = true,
[">="] = true,
["="] = true,
[".."] = true,
["and"] = true,
["or"] = true,
["not"] = true,
["["] = true,
["&"] = true,
["|"] = true,
["~"] = true,
["<<"] = true,
[">>"] = true
}
local LUA_ONE_CHAR_OPERATORS = {
["+"] = true,
["-"] = true,
["*"] = true,
["/"] = true,
["\x25"] = true,
["{"] = true,
["}"] = true,
["["] = true,
["]"] = true,
["("] = true,
[")"] = true,
[","] = true,
["^"] = true,
["<"] = true,
[">"] = true,
["="] = true,
["&"] = true,
["|"] = true,
["~"] = true,
}
local LUA_TWO_CHAR_OPERATORS = {
["~="] = true,
["<="] = true,
[">="] = true,
["=="] = true,
[".."] = true,
["::"] = true,
["//"] = true,
["<<"] = true,
[">>"] = true
}
local END_OF_STATEMENT_IGNORED_CHARS = {
["."] = true,
[":"] = true,
["'"] = true,
['"'] = true
}
local INIT_POINTS = {
root = 1,
config = 2,
main = 3,
global = 4,
trig = 5,
map = 6,
final = 7
}
local FRAME_RETURNER_FUNCTIONS = {
BlzCreateFrame = true,
BlzCreateSimpleFrame = true,
BlzCreateFrameByType = true,
BlzGetFrameByName = true,
BlzFrameGetParent = true,
BlzFrameGetChild = true,
BlzGetTriggerFrame = true
}
if compiler.WRAP_ALL_BLIZZARD_API_POINTS ~= "never" then
--Thread recycler
local deadCorots = setmetatable({}, {__index = function(self, key) self[key] = {} return self[key] end})
local unpack = table.unpack
local createCoroutine = Debug and Debug.original and Debug.original.coroutine.create or coroutine.create
local function getCoroutine(whichFunc)
local corot
if #deadCorots[whichFunc] == 0 then
local wrapper = function(...)
local args = {...}
::beginning::
whichFunc(unpack(args))
deadCorots[whichFunc][#deadCorots[whichFunc] + 1] = corot
coroutineIsDead[corot] = true
args = {coroutine.yield()}
coroutineIsDead[corot] = nil
goto beginning
end
corot = createCoroutine(wrapper)
else
corot = deadCorots[whichFunc][#deadCorots[whichFunc]]
deadCorots[whichFunc][#deadCorots[whichFunc]] = nil
end
return corot
end
local tryWrappers = setmetatable({}, {__mode = 'k'})
local function HandleErrorWrapper(err, startDepth)
if sub(err, 1, 15) == '[string "WSCode' then
local executionTabName = executionTab or "Unknown"
local errorFileName = err:match("\"WSCode (.+)\"\x25]")
if executionTabName == errorFileName then
HandleError(err)
else
if Debug then
Debug.throwError(err, startDepth)
end
local fileName = err:match('"(.+)"\x25]'):gsub("WSCode ", "")
local lineNumber = err:match("\x25]:(\x25d+):")
if fileName and lineNumber then
JumpToError(fileName, lineNumber)
end
end
else
if Debug and Debug.errorHandler then
Debug.errorHandler(err, startDepth)
else
print(err)
end
local fileName = err:match('"(.+)"\x25]')
local lineNumber = err:match("\x25]:(\x25d+):")
if fileName and lineNumber then
JumpToError(fileName, lineNumber)
end
end
end
local function getTryWrapper(func)
if func then
tryWrappers[func] = tryWrappers[func] or function(...) return select(2, xpcall(func, HandleErrorWrapper,...)) end
end
return tryWrappers[func]
end
local originals = {
triggerAddAction = TriggerAddAction,
condition = Condition,
filter = Filter,
forGroup = ForGroup,
forForce = ForForce,
enumDestructables = EnumDestructablesInRect,
enumItems = EnumItemsInRect,
timerStart = TimerStart
}
local funcOfTrigger = {}
local function triggerAddActionWrapper()
local whichTrigger = GetTriggeringTrigger()
if funcOfTrigger[whichTrigger] and coroutineWrapEnabled then
return coroutine.resume(getCoroutine(getTryWrapper(funcOfTrigger[whichTrigger])))
else
funcOfTrigger[whichTrigger]()
end
end
TriggerAddAction = function(whichTrigger, actionFunc)
funcOfTrigger[whichTrigger] = actionFunc
return originals.triggerAddAction(whichTrigger, triggerAddActionWrapper)
end
Condition = function(whichFunction)
if whichFunction and coroutineWrapEnabled then
return originals.condition(function()
return coroutine.resume(getCoroutine(getTryWrapper(whichFunction)))
end)
else
return originals.condition(whichFunction)
end
end
Filter = function(whichFunction)
if whichFunction and coroutineWrapEnabled then
return originals.filter(function()
return coroutine.resume(getCoroutine(getTryWrapper(whichFunction)))
end)
else
return originals.filter(whichFunction)
end
end
ForGroup = function(whichGroup, actionFunc)
if actionFunc and coroutineWrapEnabled then
return originals.forGroup(whichGroup, function()
return coroutine.resume(getCoroutine(getTryWrapper(actionFunc)))
end)
else
return originals.forGroup(whichGroup, actionFunc)
end
end
ForForce = function(whichForce, actionFunc)
if actionFunc and coroutineWrapEnabled then
return originals.forForce(whichForce, function()
return coroutine.resume(getCoroutine(getTryWrapper(actionFunc)))
end)
else
return originals.forForce(whichForce, actionFunc)
end
end
EnumDestructablesInRect = function(r, filter, actionFunc)
if actionFunc and coroutineWrapEnabled then
return originals.enumDestructables(r, filter, function()
return coroutine.resume(getCoroutine(getTryWrapper(actionFunc)))
end)
else
return originals.enumDestructables(r, filter, actionFunc)
end
end
EnumItemsInRect = function(r, filter, actionFunc)
if actionFunc and coroutineWrapEnabled then
return originals.enumItems(r, filter, function()
return coroutine.resume(getCoroutine(getTryWrapper(actionFunc)))
end)
else
return originals.enumItems(r, filter, actionFunc)
end
end
local funcOfTimer = {}
local function timerStartWrapper()
local whichTimer = GetExpiredTimer()
if funcOfTimer[whichTimer] and coroutineWrapEnabled then
return coroutine.resume(getCoroutine(getTryWrapper(funcOfTimer[whichTimer])))
else
funcOfTimer[whichTimer]()
end
end
TimerStart = function(whichTimer, timeout, periodic, actionFunc)
funcOfTimer[whichTimer] = actionFunc
return originals.timerStart(whichTimer, timeout, periodic, timerStartWrapper)
end
if compiler.WRAP_ALL_BLIZZARD_API_POINTS == "always" then
coroutineWrapEnabled = true
end
end
--======================================================================================================================
--Code Editor
--======================================================================================================================
local function PlayError()
local s = CreateSound("Sound\\Interface\\Error.flac" , false, false, false, 10, 10, "DefaultEAXON")
SetSoundChannel(s, 0)
StartSound(s)
KillSoundWhenDone(s)
end
local function GetTextHighlightFrame(playerIndexString, alpha, type)
local frame
if #textHighlights > 0 then
frame = textHighlights[#textHighlights]
textHighlights[#textHighlights] = nil
if highlightTypeOfFrame[frame] ~= type then
highlightTypeOfFrame[frame] = type
BlzFrameSetTexture(frame, "ReplaceableTextures\\TeamColor\\TeamColor" .. playerIndexString .. ".blp", 0, true)
BlzFrameSetAlpha(frame, alpha)
end
BlzFrameSetVisible(frame, true)
else
frame = BlzCreateFrameByType("BACKDROP", "", codeEditorParent, "", 0)
BlzFrameSetTexture(frame, "ReplaceableTextures\\TeamColor\\TeamColor" .. playerIndexString .. ".blp", 0, true)
BlzFrameSetAlpha(frame, alpha)
BlzFrameSetEnable(frame, false)
highlightTypeOfFrame[frame] = type
end
return frame
end
local function ReturnTextHighlightFrame(whichFrame)
BlzFrameSetVisible(whichFrame, false)
textHighlights[#textHighlights + 1] = whichFrame
end
local function GetTextWidth(str)
BlzFrameSetText(widthTestFrame, str:gsub("|", "||"))
BlzFrameSetSize(widthTestFrame, 0, 0)
return BlzFrameGetWidth(widthTestFrame)
end
local function SearchForClosingBracket()
local line = cursor.adjustedLine
local currentLines = codeLines[currentTab][step[currentTab]]
local previousChar = sub(currentLines[line], cursor.pos, cursor.pos)
local nextChar = sub(currentLines[line], cursor.pos + 1, cursor.pos + 1)
local whichChar = brackets.IS_BRACKET[previousChar] and previousChar or brackets.IS_BRACKET[nextChar] and nextChar
if whichChar and not selection.hasSelection then
local originalPos = brackets.IS_BRACKET[previousChar] and cursor.pos or cursor.pos + 1
local searchFor = brackets.CORRESPONDING_BRACKET[whichChar]
local bracketFound = false
if whichChar == ")" or whichChar == "]" or whichChar == "}" then
local startPoint = originalPos - 1
local bracketBalance = -1
local char
repeat
for i = startPoint, 1, -1 do
char = sub(currentLines[line], i, i)
if char == searchFor then
bracketBalance = bracketBalance + 1
if bracketBalance == 0 then
bracketFound = true
brackets.lines[1] = cursor.adjustedLine
brackets.pos[1] = originalPos
brackets.lines[2] = line
brackets.pos[2] = i
break
end
elseif char == whichChar then
bracketBalance = bracketBalance - 1
end
end
line = line - 1
startPoint = #currentLines[line]
until bracketFound or line < 1
else
local startPoint = originalPos + 1
local bracketBalance = 1
local char
repeat
for i = startPoint, #currentLines[line] do
char = sub(currentLines[line], i, i)
if char == searchFor then
bracketBalance = bracketBalance - 1
if bracketBalance == 0 then
bracketFound = true
brackets.lines[1] = cursor.adjustedLine
brackets.pos[1] = originalPos
brackets.lines[2] = line
brackets.pos[2] = i
break
end
elseif char == whichChar then
bracketBalance = bracketBalance + 1
end
end
line = line + 1
startPoint = 1
until bracketFound or line > highestNonEmptyLine[currentTab][step[currentTab]]
end
if bracketFound then
local rawLine = brackets.lines[1] - lineNumberOffset[currentTab]
if rawLine >= 1 and rawLine <= editor.MAX_LINES_ON_SCREEN then
BlzFrameSetVisible(brackets.highlights[1], true)
BlzFrameSetPoint(brackets.highlights[1], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(currentLines[brackets.lines[1]], 1, brackets.pos[1] - 1)), -editor.CODE_TOP_INSET - rawLine*editor.LINE_SPACING)
BlzFrameSetPoint(brackets.highlights[1], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(currentLines[brackets.lines[1]], 1, brackets.pos[1])), -editor.CODE_TOP_INSET - (rawLine - 1)*editor.LINE_SPACING)
else
BlzFrameSetVisible(brackets.highlights[1], false)
end
rawLine = brackets.lines[2] - lineNumberOffset[currentTab]
if rawLine >= 1 and rawLine <= editor.MAX_LINES_ON_SCREEN then
BlzFrameSetVisible(brackets.highlights[2], true)
BlzFrameSetPoint(brackets.highlights[2], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(currentLines[brackets.lines[2]], 1, brackets.pos[2] - 1)), -editor.CODE_TOP_INSET - rawLine*editor.LINE_SPACING)
BlzFrameSetPoint(brackets.highlights[2], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(currentLines[brackets.lines[2]], 1, brackets.pos[2])), -editor.CODE_TOP_INSET - (rawLine - 1)*editor.LINE_SPACING)
else
BlzFrameSetVisible(brackets.highlights[2], false)
end
else
brackets.lines[1] = nil
brackets.lines[2] = nil
BlzFrameSetVisible(brackets.highlights[1], false)
BlzFrameSetVisible(brackets.highlights[2], false)
end
else
brackets.lines[1] = nil
brackets.lines[2] = nil
BlzFrameSetVisible(brackets.highlights[1], false)
BlzFrameSetVisible(brackets.highlights[2], false)
end
end
local function SetCursorX(str)
local width = GetTextWidth(str)
BlzFrameSetPoint(cursorFrame, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + width, -editor.CODE_TOP_INSET - cursor.rawLine*editor.LINE_SPACING)
cursor.x = width
cursorCounter = 0
end
local function SetCursorPos(whichPos)
cursor.pos = whichPos
SearchForClosingBracket()
end
local function SetLineOffset(whichOffset)
BlzFrameSetValue(codeScroller, highestNonEmptyLine[currentTab][step[currentTab]] - whichOffset)
end
local function JumpWindow(whichLine, jumpToCenter, forceCenter)
if forceCenter then
SetLineOffset(math.max(0, math.min(whichLine - editor.MAX_LINES_ON_SCREEN // 2, highestNonEmptyLine[currentTab][step[currentTab]] - editor.MAX_LINES_ON_SCREEN + 4)))
return true
else
if lineNumberOffset[currentTab] > whichLine - 4 and lineNumberOffset[currentTab] > 1 then
if jumpToCenter then
SetLineOffset(math.max(0, math.min(whichLine - editor.MAX_LINES_ON_SCREEN // 2, highestNonEmptyLine[currentTab][step[currentTab]] - editor.MAX_LINES_ON_SCREEN+ 4)))
else
SetLineOffset(math.max(0, whichLine - 4))
end
return true
elseif lineNumberOffset[currentTab] < whichLine - editor.MAX_LINES_ON_SCREEN + 4 then
if jumpToCenter then
SetLineOffset(math.max(0, math.min(whichLine - editor.MAX_LINES_ON_SCREEN // 2, highestNonEmptyLine[currentTab][step[currentTab]] - editor.MAX_LINES_ON_SCREEN + 4)))
else
SetLineOffset(whichLine - editor.MAX_LINES_ON_SCREEN + 4)
end
return true
else
return false
end
end
end
JumpToError = function(fileName, lineNumber)
if not compiler.LOAD_EXTERNAL_SCRIPTS_ON_ERROR or not canJumpToError or not fileName or not lineNumber then
return
end
for i = 1, #fileNames do
if fileNames[i] == fileName then
EnableEditor(user)
local alreadyActive, tabIndex
for j = 1, numTabs do
if tabNames[j] == fileName then
alreadyActive = true
tabIndex = j
break
end
end
if not alreadyActive then
Pull(fileName)
else
SwitchToTab(tabIndex)
end
BlzFrameSetVisible(errorHighlight, true)
errorLineNumber = lineNumber
JumpWindow(errorLineNumber, true, true)
hasError[currentTab] = true
BlzFrameSetPoint(errorHighlight, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET, -editor.CODE_TOP_INSET - editor.LINE_SPACING*(errorLineNumber - lineNumberOffset[currentTab]))
BlzFrameSetPoint(errorHighlight, FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPRIGHT, -editor.CODE_RIGHT_INSET, -editor.CODE_TOP_INSET - editor.LINE_SPACING*(errorLineNumber - lineNumberOffset[currentTab] - 1))
canJumpToError = false
break
end
end
end
local function ConvertStringToLines(whichString)
local lines = {}
local k = 1
local beginning = 1
local pos = whichString:find("\n")
while pos do
lines[k] = sub(whichString, beginning, pos - 1)
beginning = pos + 1
pos = whichString:find("\n", pos + 1)
k = k + 1
end
if beginning <= #whichString then
lines[#lines + 1] = sub(whichString, beginning)
end
return lines
end
local function SetCurrentLine()
if currentStop - lineNumberOffset[currentTab] < 1 then
BlzFrameSetPoint(currentLineHighlight, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.LINE_HIGHLIGHT_HORIZONTAL_INSET, -editor.CODE_TOP_INSET)
BlzFrameSetPoint(currentLineHighlight, FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.LINE_HIGHLIGHT_HORIZONTAL_INSET + editor.LINE_SPACING, -editor.CODE_TOP_INSET - editor.LINE_SPACING)
BlzFrameSetTexture(currentLineHighlight, "currentLineDown.blp", 0, true)
elseif currentStop - lineNumberOffset[currentTab] > editor.MAX_LINES_ON_SCREEN then
BlzFrameSetPoint(currentLineHighlight, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.LINE_HIGHLIGHT_HORIZONTAL_INSET, -editor.CODE_TOP_INSET - (editor.MAX_LINES_ON_SCREEN - 1)*editor.LINE_SPACING)
BlzFrameSetPoint(currentLineHighlight, FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.LINE_HIGHLIGHT_HORIZONTAL_INSET + editor.LINE_SPACING, -editor.CODE_TOP_INSET - editor.MAX_LINES_ON_SCREEN*editor.LINE_SPACING)
BlzFrameSetTexture(currentLineHighlight, "currentLineUp.blp", 0, true)
else
BlzFrameSetPoint(currentLineHighlight, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.LINE_HIGHLIGHT_HORIZONTAL_INSET, -editor.CODE_TOP_INSET - (currentStop - lineNumberOffset[currentTab])*editor.LINE_SPACING)
BlzFrameSetPoint(currentLineHighlight, FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.LINE_HIGHLIGHT_HORIZONTAL_INSET + editor.LINE_SPACING, -editor.CODE_TOP_INSET - (currentStop - lineNumberOffset[currentTab] - 1)*editor.LINE_SPACING)
BlzFrameSetTexture(currentLineHighlight, "currentLine.blp", 0, true)
end
end
local function SearchForDuplicates()
local pos, endPos
local currentLines = codeLines[currentTab][step[currentTab]]
local start = math.min(selection.startPos, selection.endPos) + 1
local stop = math.max(selection.startPos, selection.endPos)
local selectString = sub(currentLines[selection.startLine], start, stop):gsub("([\x25^\x25$\x25(\x25)\x25\x25\x25.\x25[\x25]\x25*\x25+\x25-\x25?])", "\x25\x25\x251") --escape all special characters
local numDuplicates = 0
local offset
if #selectString > 0 and selectString:find("^\x25s*$") == nil then --empty line
for i = lineNumberOffset[currentTab] + 1, editor.MAX_LINES_ON_SCREEN + lineNumberOffset[currentTab] do
pos, endPos = find(currentLines[i], selectString)
while pos do
if i ~= selection.startLine or pos ~= start then
numDuplicates = numDuplicates + 1
duplicateHighlightFrames[numDuplicates] = duplicateHighlightFrames[numDuplicates] or GetTextHighlightFrame("09", 90, "duplicate")
BlzFrameSetPoint(duplicateHighlightFrames[numDuplicates], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(currentLines[i], 1, pos - 1)), -editor.CODE_TOP_INSET - (i - lineNumberOffset[currentTab])*editor.LINE_SPACING)
BlzFrameSetPoint(duplicateHighlightFrames[numDuplicates], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(currentLines[i], 1, endPos)), -editor.CODE_TOP_INSET - (i - 1 - lineNumberOffset[currentTab])*editor.LINE_SPACING)
end
offset = endPos
pos, endPos = find(sub(currentLines[i], endPos + 1), selectString)
if pos then
pos = pos + offset
endPos = endPos + offset
end
end
end
end
for i = numDuplicates + 1, #duplicateHighlightFrames do
ReturnTextHighlightFrame(duplicateHighlightFrames[i])
duplicateHighlightFrames[i] = nil
end
end
local function RemoveDuplicates()
for i = 1, #duplicateHighlightFrames do
ReturnTextHighlightFrame(duplicateHighlightFrames[i])
duplicateHighlightFrames[i] = nil
end
end
local function ConvertAndValidateLines(lines, duringInit, printError)
local adjustedCodeLines = {}
adjustedCodeLines[1] = lines[1]
for i = 2, #lines do
adjustedCodeLines[i] = "\n" .. lines[i]
end
local code = table.concat(adjustedCodeLines):gsub("Debug.beginFile", "DoNothing"):gsub("Debug.endFile", "DoNothing"):gsub(";", "")
local func, err
func, err = load(code, "WSCodeValidation", "t")
if not func then
if not duringInit then
if err then
local lineNumber = err:match('"\x25]:(\x25d+):') --"]:NUMBER:
if lineNumber then
hasError[currentTab] = true
errorLineNumber = lineNumber
BlzFrameSetVisible(errorHighlight, errorLineNumber - lineNumberOffset[executionTab] >= 1 and errorLineNumber - lineNumberOffset[executionTab] <= editor.MAX_LINES_ON_SCREEN)
BlzFrameSetAlpha(errorHighlight, 128)
BlzFrameSetPoint(errorHighlight, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET, -editor.CODE_TOP_INSET - editor.LINE_SPACING*(errorLineNumber - lineNumberOffset[executionTab]))
BlzFrameSetPoint(errorHighlight, FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPRIGHT, -editor.CODE_RIGHT_INSET, -editor.CODE_TOP_INSET - editor.LINE_SPACING*(errorLineNumber - lineNumberOffset[executionTab] - 1))
if printError then
print("|cffff5555SYNTAX ERROR at " .. (executionTab or "Unknown") .. (err:match('"WSCodeValidation"\x25](.+)') or "") .. "|r")
end
return false
else
hasError[currentTab] = false
BlzFrameSetVisible(errorHighlight, false)
return false
end
else
hasError[currentTab] = false
BlzFrameSetVisible(errorHighlight, false)
return false
end
elseif err then
if printError then
print("|cffff5555SYNTAX ERROR at " .. (executionTab or "Unknown") .. (err:match('"WSCodeValidation"\x25](.+)') or "") .. "|r")
end
if duringInit then
initError = {
fileName = executionTab or "Unknown",
lineNumber = err:match('"\x25]:(\x25d+):') --"]:NUMBER:
}
end
return false
end
elseif not duringInit then
hasError[currentTab] = false
BlzFrameSetVisible(errorHighlight, false)
end
return true, code, func
end
local function IncrementStep()
step[currentTab] = step[currentTab] + 1
local lastLines = codeLines[currentTab][step[currentTab] - 1]
local currentLines = codeLines[currentTab][step[currentTab]]
for i = 1, highestNonEmptyLine[currentTab][step[currentTab] - 1] do
currentLines[i] = lastLines[i]
coloredCodeLines[currentTab][step[currentTab]][i] = coloredCodeLines[currentTab][step[currentTab] - 1][i]
end
highestNonEmptyLine[currentTab][step[currentTab]] = highestNonEmptyLine[currentTab][step[currentTab] - 1]
BlzFrameSetVisible(currentLineHighlight, false)
hasCurrentLineHighlight = false
BlzFrameSetVisible(errorHighlight, false)
BlzFrameSetEnable(buttons.undo, true)
BlzFrameSetEnable(buttons.redo, false)
maxRedo[currentTab] = step[currentTab]
lineNumberOfEdit[currentTab][step[currentTab]] = cursor.adjustedLine or 1
posOfEdit[currentTab][step[currentTab]] = cursor.pos
changeMade = true
end
local function RecalculateHighestNonEmptyLineNumber()
local currentLines = codeLines[currentTab][step[currentTab]]
for i = highestNonEmptyLine[currentTab][step[currentTab]], 0, -1 do
if currentLines[i] ~= "" then
highestNonEmptyLine[currentTab][step[currentTab]] = i
BlzFrameSetMinMaxValue(codeScroller, 0, i)
return
end
end
highestNonEmptyLine[currentTab][step[currentTab]] = 0
BlzFrameSetMinMaxValue(codeScroller, 0, 0)
end
local function UpdateAllLines()
local currentColoredLines = coloredCodeLines[currentTab][step[currentTab]]
for i = 1, editor.MAX_LINES_ON_SCREEN do
BlzFrameSetText(codeLineFrames[i], currentColoredLines[i + lineNumberOffset[currentTab]])
end
RecalculateHighestNonEmptyLineNumber()
end
local function AddColors(prefix, match, suffix)
if LUA_KEYWORDS[match] then
return prefix .. editor.KEYWORD_COLOR .. match .. "|r" .. suffix
elseif match == "false" or match == "true" or match == "nil" or tonumber(match) then
return prefix .. editor.VALUE_COLOR .. match .. "|r" .. suffix
elseif FUNCTION_PREVIEW and FUNCTION_PREVIEW[match] then
return prefix .. editor.NATIVE_COLOR .. match .. "|r" .. suffix
end
return prefix .. match .. suffix
end
local function ColorFunctions(prefix, funcWord, funcName)
return prefix .. " " .. editor.FUNCTION_NAME_COLOR .. funcName .. "|r"
end
local function GetColoredText(str, lineNumber, whichTab)
local inSingleQuoteString
local inDoubleQuoteString
local inMultilineString
local endsInLongComment
local oldEqualSigns
str = str:gsub("|", "||")
local lineSegments = {}
local restStr, commentStr
if lineNumber > 1 and lines.endsInLongString[whichTab][lineNumber - 1] then
inMultilineString = true
lines.numEqualSigns[lineNumber] = lines.numEqualSigns[lineNumber - 1]
restStr = str
lineSegments[1] = ""
commentStr = ""
elseif lineNumber > 1 and lines.endsInLongComment[whichTab][lineNumber - 1] then
lines.numEqualSigns[lineNumber] = lines.numEqualSigns[lineNumber - 1]
restStr = ""
commentStr = editor.COMMENT_COLOR .. str .. "|r"
local matchStr = "]"
for __ = 1, lines.numEqualSigns[lineNumber] do
matchStr = matchStr .. "="
end
matchStr = matchStr .. "]"
endsInLongComment = find(str, matchStr) == nil
else
local commentBegin = find(str, "\x25-\x25-")
if commentBegin then
commentStr = editor.COMMENT_COLOR .. sub(str, commentBegin) .. "|r"
restStr = sub(str, 1, commentBegin - 1)
if sub(str, commentBegin):match("^\x25-\x25-\x25[") ~= nil then
oldEqualSigns = lines.numEqualSigns[lineNumber]
lines.numEqualSigns[lineNumber] = 0
local i = commentBegin + 3
while sub(str, i, i) == "=" do
lines.numEqualSigns[lineNumber] = lines.numEqualSigns[lineNumber] + 1
i = i + 1
end
if sub(str, i, i) == "[" then
endsInLongComment = true
end
end
else
restStr = str
commentStr = ""
endsInLongComment = nil
end
end
local i = 1
local beginningOfSegment = 1
while i <= #restStr do
local char = sub(restStr, i, i)
if char == "'" then
if not inDoubleQuoteString and not inMultilineString then
if inSingleQuoteString then
inSingleQuoteString = false
lineSegments[#lineSegments + 1] = editor.STRING_COLOR .. sub(restStr, beginningOfSegment, i) .. "|r"
beginningOfSegment = i + 1
elseif find(sub(restStr, i + 1), "'") then
inSingleQuoteString = true
lineSegments[#lineSegments + 1] = sub(restStr, beginningOfSegment, i - 1)
beginningOfSegment = i
end
end
elseif char == '"' then
if not inSingleQuoteString and not inMultilineString then
if inDoubleQuoteString then
inDoubleQuoteString = false
lineSegments[#lineSegments + 1] = editor.STRING_COLOR .. sub(restStr, beginningOfSegment, i) .. "|r"
beginningOfSegment = i + 1
elseif find(sub(restStr, i + 1), '"') then
inDoubleQuoteString = true
lineSegments[#lineSegments + 1] = sub(restStr, beginningOfSegment, i - 1)
beginningOfSegment = i
end
end
elseif sub(restStr, i):match("^\x25[=*\x25[") and not inMultilineString then
local j = i + 1
lines.numEqualSigns[lineNumber] = 0
while sub(restStr, j, j) == "=" do
lines.numEqualSigns[lineNumber] = lines.numEqualSigns[lineNumber] + 1
j = j + 1
end
if sub(restStr, j, j) == "[" then
inMultilineString = true
lineSegments[#lineSegments + 1] = sub(restStr, beginningOfSegment, i - 1)
beginningOfSegment = i
end
i = i + 1 + lines.numEqualSigns[lineNumber]
elseif inMultilineString and sub(restStr, i, i + 1 + lines.numEqualSigns[lineNumber]):match("^\x25]=*\x25]") then
inMultilineString = false
lineSegments[#lineSegments + 1] = editor.STRING_COLOR .. sub(restStr, beginningOfSegment, i + 1 + lines.numEqualSigns[lineNumber]) .. "|r"
beginningOfSegment = i + 2 + lines.numEqualSigns[lineNumber]
i = i + 1 + lines.numEqualSigns[lineNumber]
end
i = i + 1
end
if inMultilineString then
lineSegments[#lineSegments + 1] = editor.STRING_COLOR .. sub(restStr, beginningOfSegment) .. "|r"
else
lineSegments[#lineSegments + 1] = sub(restStr, beginningOfSegment)
end
for i = 1, #lineSegments, 2 do
lineSegments[i] = lineSegments[i]:gsub("(\x25f[\x25a_]function)(\x25f[\x25A])\x25s+([\x25a_][\x25w_]*)", ColorFunctions)
lineSegments[i] = lineSegments[i]:gsub("(\x25f[\x25w_.-])([\x25w_.-]+)(\x25f[\x25W])", AddColors)
end
if (lines.endsInLongString[whichTab][lineNumber] == true) ~= (inMultilineString == true) then
lines.endsInLongString[whichTab][lineNumber] = inMultilineString
if lineNumber + 1 <= highestNonEmptyLine[whichTab][step[whichTab]] then
coloredCodeLines[whichTab][step[whichTab]][lineNumber + 1] = GetColoredText(codeLines[whichTab][step[whichTab]][lineNumber + 1], lineNumber + 1, whichTab)
if lineNumber + 1 - lineNumberOffset[whichTab] >= 1 and lineNumber + 1 - lineNumberOffset[whichTab] <= editor.MAX_LINES_ON_SCREEN then
BlzFrameSetText(codeLineFrames[lineNumber + 1 - lineNumberOffset[whichTab]], coloredCodeLines[whichTab][step[whichTab]][lineNumber + 1])
end
end
elseif (lines.endsInLongComment[whichTab][lineNumber] == true) ~= (endsInLongComment == true) or (lines.endsInLongComment[whichTab][lineNumber] and oldEqualSigns ~= lines.numEqualSigns[lineNumber]) then
lines.endsInLongComment[whichTab][lineNumber] = endsInLongComment
if lineNumber + 1 <= highestNonEmptyLine[whichTab][step[whichTab]] then
coloredCodeLines[whichTab][step[whichTab]][lineNumber + 1] = GetColoredText(codeLines[whichTab][step[whichTab]][lineNumber + 1], lineNumber + 1, whichTab)
if lineNumber + 1 - lineNumberOffset[whichTab] >= 1 and lineNumber + 1 - lineNumberOffset[whichTab] <= editor.MAX_LINES_ON_SCREEN then
BlzFrameSetText(codeLineFrames[lineNumber + 1 - lineNumberOffset[whichTab]], coloredCodeLines[whichTab][step[whichTab]][lineNumber + 1])
end
end
end
return (table.concat(lineSegments) .. commentStr)
end
local function SetLocalList(lineNumber)
local str, vars, varName
localLookupTable = {}
local varDefLevels = {}
local level = 0
local isLocal, isAssignment, isFunctionDef
local lines = codeLines[currentTab][step[currentTab]]
for i = 1, lineNumber - 1 do
str = lines[i]:gsub("'([^']+)'", ""):gsub('"([^"]+)"', "") --Remove string literals.
isFunctionDef = find(str, "\x25f[\x25w_]function\x25f[^\x25w_]")
if isFunctionDef or find(str, "\x25f[\x25w_]do\x25f[^\x25w_]") or find(str, "\x25f[\x25w_]if\x25f[^\x25w_]") or find(str, "\x25f[\x25w_]for\x25f[^\x25w_]") or find(str, "\x25f[\x25w_]while\x25f[^\x25w_]") or find(str, "\x25f[\x25w_]repeat\x25f[^\x25w_]") then
level = level + 1
end
if isFunctionDef then
vars = str:match("\x25((.+)\x25)") or "" --Get function arguments.
vars = vars:gsub(" ", "")
local pos = find(vars, ",")
while pos do
varName = sub(vars, 1, pos - 1)
vars = sub(vars, pos + 1)
if not localLookupTable[varName] and not globalLookupTable[varName] then
localLookupTable[#localLookupTable + 1] = varName
localLookupTable[varName] = true
varDefLevels[#varDefLevels + 1] = level
end
pos = find(vars, ",")
end
if not localLookupTable[vars] and not globalLookupTable[vars] then
localLookupTable[#localLookupTable + 1] = vars
localLookupTable[vars] = true
varDefLevels[#varDefLevels + 1] = level
end
isLocal = find(str, "^\x25s*local ")
isAssignment = find(str, "[^=<>~]=[^=<>~]") --single equal sign, not <= etc.
if isAssignment then
vars = str:match("([\x25w_]+)\x25s*=") --name before equal sign
else
vars = str:match("([\x25w_]+)\x25s*\x25(") --name before opening parenthesis
end
if vars and not localLookupTable[vars] and not globalLookupTable[vars] then
localLookupTable[#localLookupTable + 1] = vars
localLookupTable[vars] = true
varDefLevels[#varDefLevels + 1] = isLocal and level or -1
end
else
isLocal = find(str, "^\x25s*local ")
isAssignment = find(str, "[^=<>~]=[^=<>~]") --single equal sign, not <= etc.
if isLocal or isAssignment then
if isLocal then
if isAssignment then
vars = str:match("local\x25s*(.+)="):gsub(" ", "") --Between local and =
else
vars = str:match("local\x25s*(.+)"):gsub(" ", "") --everthing after local
end
else
vars = str:match("(.+)="):gsub(" ", "")
end
if not find(vars, "\x25.") and not find(vars, "\x25[") then --ignore table assignments
local pos = find(vars, ",")
--Separate variable names by commas
while pos do
varName = sub(vars, 1, pos - 1)
vars = sub(vars, pos + 1)
if not localLookupTable[varName] and not globalLookupTable[varName] then
localLookupTable[#localLookupTable + 1] = varName
localLookupTable[varName] = true
varDefLevels[#varDefLevels + 1] = isLocal and level or -1
end
pos = find(vars, ",")
end
if not localLookupTable[vars] and not globalLookupTable[vars] then
localLookupTable[#localLookupTable + 1] = vars
localLookupTable[vars] = true
varDefLevels[#varDefLevels + 1] = isLocal and level or -1
end
end
end
end
if find(str, "\x25f[\x25w_]end\x25f[^\x25w_]") or find(str, "\x25f[\x25w_]until\x25f[^\x25w_]") then
for j = 1, #varDefLevels do
if varDefLevels[j] == level then
localLookupTable[localLookupTable[j]] = nil
localLookupTable[j] = localLookupTable[#localLookupTable]
localLookupTable[#localLookupTable] = nil
varDefLevels[j] = varDefLevels[#varDefLevels]
varDefLevels[#varDefLevels] = nil
end
end
level = level - 1
end
end
table.sort(localLookupTable)
end
local function PutStop(lineNumber, shift)
local currentLines = codeLines[currentTab][step[currentTab]]
local hasWarning
if not lineNumber then
local button = BlzGetTriggerFrame()
BlzFrameSetEnable(button, false)
BlzFrameSetEnable(button, true)
lineNumber = indexFromFrame[button] + lineNumberOffset[currentTab]
shift = 1
end
if lineNumber > highestNonEmptyLine[currentTab][step[currentTab]] then
PlayError()
return
end
if lines.debugState[currentTab][lineNumber] == 0 then
if find(currentLines[lineNumber], "^\x25s*$") ~= nil or find(currentLines[lineNumber], "^\x25s*else\x25s*$") ~= nil or find(currentLines[lineNumber], "^\x25s*do\x25s*$") ~= nil then
hasWarning = true
end
if find(currentLines[lineNumber], "\x25f[\x25a_]end\x25f[^\x25w_]") ~= nil then
--Stop can only be put on an end token if it is the closing end of a function, but not if the previous line contains a return statement.
if find(currentLines[lineNumber - 1], "\x25f[\x25a_]return\x25f[^\x25w_]") ~= nil then
hasWarning = true
end
if not hasWarning then
local i = lineNumber
local level = -1
repeat
if find(currentLines[i], "\x25f[\x25a_]for\x25f[^\x25w_]") ~= nil
or find(currentLines[i], "\x25f[\x25a_]do\x25f[^\x25w_]") ~= nil
or find(currentLines[i], "\x25f[\x25a_]if\x25f[^\x25w_]") ~= nil
or find(currentLines[i], "\x25f[\x25a_]function\x25f[^\x25w_]") then
level = level - 1
end
if find(currentLines[i], "\x25f[\x25a_]end\x25f[^\x25w_]") ~= nil
or find(currentLines[i], "\x25f[\x25a_]until\x25f[^\x25w_]") ~= nil then
level = level + 1
end
i = i - 1
until i <= 0 or (find(currentLines[i], "\x25f[\x25a_]function\x25f[^\x25w_]") ~= nil and level <= 0)
if i <= 0 or level < 0 then
hasWarning = true
end
end
elseif not hasWarning then
--Check if line is part of multiline function call or table def.
local i = lineNumber - 1
local level = 0
local numRoundBrackets = 0
local numSquareBrackets = 0
local numCurlyBrackets = 0
repeat
if find(currentLines[i], "\x25f[\x25a_]for\x25f[^\x25w_]") ~= nil
or find(currentLines[i], "\x25f[\x25a_]do\x25f[^\x25w_]") ~= nil
or find(currentLines[i], "\x25f[\x25a_]if\x25f[^\x25w_]") ~= nil
or find(currentLines[i], "\x25f[\x25a_]function\x25f[^\x25w_]") then
level = level - 1
end
if find(currentLines[i], "\x25f[\x25a_]end\x25f[^\x25w_]") ~= nil
or find(currentLines[i], "\x25f[\x25a_]until\x25f[^\x25w_]") ~= nil then
level = level + 1
end
if level < 0 then
break
end
numRoundBrackets = numRoundBrackets + select(2, currentLines[i]:gsub("\x25(", "")) - select(2, currentLines[i]:gsub("\x25)", ""))
numSquareBrackets = numSquareBrackets + select(2, currentLines[i]:gsub("\x25[", "")) - select(2, currentLines[i]:gsub("\x25]", ""))
numCurlyBrackets = numCurlyBrackets + select(2, currentLines[i]:gsub("{", "")) - select(2, currentLines[i]:gsub("}", ""))
if numRoundBrackets > 0 or numSquareBrackets > 0 or numCurlyBrackets > 0 then
hasWarning = true
break
end
i = i - 1
until i <= 0
end
if hasWarning then
print("|cffff0000Warning:|r Breakpoint may not be reachable.")
end
end
lines.debugState[currentTab][lineNumber] = math.fmod(lines.debugState[currentTab][lineNumber] + shift, 3)
BlzFrameSetTexture(BlzFrameGetChild(stopButtons[lineNumber - lineNumberOffset[currentTab]], 0), LINE_STATE_ICONS[lines.debugState[currentTab][lineNumber]], 0, true)
end
local function GetCursorPos(x, y)
local lineNumber = math.floor(((editor.Y_TOP - editor.CODE_TOP_INSET) - y)/editor.LINE_SPACING) + 1
if lineNumber >= 1 and lineNumber <= editor.MAX_LINES_ON_SCREEN then
local dx = isExpanded and x - (editor.EXPANDED_X + editor.CODE_LEFT_INSET) or x - (editor.COLLAPSED_X + editor.CODE_LEFT_INSET)
if dx < 0 then
return lineNumber, -1, nil
elseif dx > (isExpanded and editor.EXPANDED_WIDTH - (editor.CODE_LEFT_INSET + editor.CODE_RIGHT_INSET) or editor.COLLAPSED_WIDTH - (editor.CODE_LEFT_INSET + editor.CODE_RIGHT_INSET)) then
return lineNumber, math.huge, nil
end
local text = codeLines[currentTab][step[currentTab]][lineNumber + lineNumberOffset[currentTab]]
if #text == 0 then
return lineNumber, 0, 0
end
local testPos = math.min(math.floor(dx/0.005), #text)
local width, over
over = GetTextWidth(sub(text, 1, testPos)) < dx
repeat
testPos = testPos + (over and 1 or - 1)
width = GetTextWidth(sub(text, 1, testPos))
until width < dx ~= over or testPos > #text
if over then
testPos = testPos - 1
end
if #text > testPos then
local diff1 = math.abs(GetTextWidth(sub(text, 1, testPos)) - dx)
local diff2 = math.abs(GetTextWidth(sub(text, 1, testPos + 1)) - dx)
if diff2 < diff1 then
testPos = testPos + 1
end
end
width = GetTextWidth(sub(text, 1, testPos))
return lineNumber, testPos, width
elseif lineNumber < 1 then
return -1, nil, nil
else
return math.huge, nil, nil
end
end
SetSelection = function(xc)
local lower = math.max(selection.startLine, selection.endLine) - lineNumberOffset[currentTab]
local upper = math.min(selection.startLine, selection.endLine) - lineNumberOffset[currentTab]
local currentLines = codeLines[currentTab][step[currentTab]]
local j
for i = 1, editor.MAX_LINES_ON_SCREEN do
j = i + lineNumberOffset[currentTab]
if i > lower or i < upper then
if selection.lines[i] then
ReturnTextHighlightFrame(selection.lines[i])
selection.lines[i] = nil
end
else
selection.lines[i] = selection.lines[i] or GetTextHighlightFrame("09", 150, "selection")
if lower == upper then
if selection.endPos < selection.startPos then
BlzFrameSetPoint(selection.lines[i], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + xc, -editor.CODE_TOP_INSET - i*editor.LINE_SPACING)
BlzFrameSetPoint(selection.lines[i], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(currentLines[j], 1, selection.startPos)), -editor.CODE_TOP_INSET - (i - 1)*editor.LINE_SPACING)
else
BlzFrameSetPoint(selection.lines[i], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(currentLines[j], 1, selection.startPos)), -editor.CODE_TOP_INSET - i*editor.LINE_SPACING)
BlzFrameSetPoint(selection.lines[i], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + xc, -editor.CODE_TOP_INSET - (i - 1)*editor.LINE_SPACING)
end
elseif i == lower then
if lower == selection.endLine - lineNumberOffset[currentTab] then
BlzFrameSetPoint(selection.lines[i], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET, -editor.CODE_TOP_INSET - i*editor.LINE_SPACING)
BlzFrameSetPoint(selection.lines[i], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + xc, -editor.CODE_TOP_INSET - (i - 1)*editor.LINE_SPACING)
else
BlzFrameSetPoint(selection.lines[i], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET, -editor.CODE_TOP_INSET - i*editor.LINE_SPACING)
BlzFrameSetPoint(selection.lines[i], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(currentLines[j], 1, selection.startPos)), -editor.CODE_TOP_INSET - (i - 1)*editor.LINE_SPACING)
end
elseif i == upper then
if upper == selection.endLine - lineNumberOffset[currentTab] then
BlzFrameSetPoint(selection.lines[i], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + xc, -editor.CODE_TOP_INSET - i*editor.LINE_SPACING)
BlzFrameSetPoint(selection.lines[i], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(currentLines[j]), -editor.CODE_TOP_INSET - (i - 1)*editor.LINE_SPACING)
else
BlzFrameSetPoint(selection.lines[i], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(currentLines[j], 1, selection.startPos)), -editor.CODE_TOP_INSET - i*editor.LINE_SPACING)
BlzFrameSetPoint(selection.lines[i], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(currentLines[j]), -editor.CODE_TOP_INSET - (i - 1)*editor.LINE_SPACING)
end
else
BlzFrameSetPoint(selection.lines[i], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET, -editor.CODE_TOP_INSET - i*editor.LINE_SPACING)
BlzFrameSetPoint(selection.lines[i], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(currentLines[j]), -editor.CODE_TOP_INSET - (i - 1)*editor.LINE_SPACING)
end
end
end
if lower == upper and selection.startPos ~= selection.endPos then
SearchForDuplicates()
else
RemoveDuplicates()
end
end
HideSelection = function()
if selection.hasSelection then
for i = 1, editor.MAX_LINES_ON_SCREEN do
if selection.lines[i] then
ReturnTextHighlightFrame(selection.lines[i])
selection.lines[i] = nil
end
end
RemoveDuplicates()
end
end
local function OnMouseMove()
mouse.lastMove = timeElapsed
checkedForVariableDisplay = false
if isShowingVariableTooltip then
BlzFrameSetVisible(helperFrame, false)
isShowingVariableTooltip = false
end
if not leftMouseButtonIsPressed then
return
end
local xw, yw = BlzGetTriggerPlayerMouseX(), BlzGetTriggerPlayerMouseY()
local x, y = World2Screen(xw, yw, GetTerrainZ(xw, yw))
y = y + 0.0015
local line, pos, xc = GetCursorPos(x, y)
local currentLines = codeLines[currentTab][step[currentTab]]
if line == -1 then
line = 1
pos = 0
xc = 0
elseif line == math.huge then
line = editor.MAX_LINES_ON_SCREEN
xc = GetTextWidth(currentLines[editor.MAX_LINES_ON_SCREEN + lineNumberOffset[currentTab]])
pos = #currentLines[line + lineNumberOffset[currentTab]]
end
if pos == -1 then
pos = 0
xc = 0
elseif pos == math.huge then
pos = #currentLines[line + lineNumberOffset[currentTab]]
xc = GetTextWidth(currentLines[line + lineNumberOffset[currentTab]])
end
if line == (selection.endLine - lineNumberOffset[currentTab]) and pos == selection.endPos then
return
end
selection.endLine = line + lineNumberOffset[currentTab]
selection.endPos = pos
selection.hasSelection = true
cursor.adjustedLine = selection.endLine
cursor.rawLine = line
SetCursorPos(pos)
SetCursorX(sub(codeLines[currentTab][step[currentTab]][cursor.adjustedLine], 1, pos))
SetSelection(xc)
end
local function OnMouseRelease()
if leftMouseButtonIsPressed and BlzGetTriggerPlayerMouseButton() == MOUSE_BUTTON_TYPE_LEFT then
leftMouseButtonIsPressed = false
end
end
local function DefocusCodeEditor()
BlzFrameSetFocus(enterBox, false)
editBoxFocused = false
BlzFrameSetVisible(cursorFrame, false)
BlzFrameSetVisible(brackets.highlights[1], false)
BlzFrameSetVisible(brackets.highlights[2], false)
brackets.lines[1] = nil
brackets.lines[2] = nil
end
local function MoveCursor(pos, line, xc)
cursor.adjustedLine = line + lineNumberOffset[currentTab]
cursor.rawLine = line
SetCursorPos(pos)
cursor.x = xc
cursorCounter = 0
editBoxFocused = true
BlzFrameSetVisible(cursorFrame, true)
BlzFrameSetPoint(cursorFrame, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + cursor.x, -editor.CODE_TOP_INSET - cursor.rawLine*editor.LINE_SPACING)
enterBoxText = " "
BlzFrameSetText(enterBox, " ")
SetLocalList(cursor.adjustedLine)
selection.startLine = cursor.adjustedLine
selection.endLine = cursor.adjustedLine
selection.startPos = cursor.pos
selection.endPos = cursor.pos
end
local function OnMouseClick()
if ignoreMouseClickUntil > timeElapsed then
return
end
BlzFrameSetVisible(helperFrame, false)
local xw, yw = BlzGetTriggerPlayerMouseX(), BlzGetTriggerPlayerMouseY()
local x, y = World2Screen(xw, yw, GetTerrainZ(xw, yw))
y = y + 0.0015
if BlzGetTriggerPlayerMouseButton() == MOUSE_BUTTON_TYPE_LEFT then
--Check if click is over one of the popup menus.
if BlzFrameIsVisible(flagsMenuParent) then
if x > (isExpanded and editor.EXPANDED_X + editor.EXPANDED_WIDTH/2 + flags.minX or editor.COLLAPSED_X + editor.COLLAPSED_WIDTH/2 + flags.minX)
and x < (isExpanded and editor.EXPANDED_X + editor.EXPANDED_WIDTH/2 + flags.maxX or editor.COLLAPSED_X + editor.COLLAPSED_WIDTH/2 + flags.maxX)
and y < flags.maxY then
DefocusCodeEditor()
BlzFrameSetFocus(searchBar.textField, false)
return
else
BlzFrameSetVisible(flagsMenuParent, false)
end
end
if BlzFrameIsVisible(contextMenu.parent) then
if x > contextMenu.minX and x < contextMenu.maxX and y > contextMenu.minY and y < contextMenu.maxY then
DefocusCodeEditor()
BlzFrameSetFocus(searchBar.textField, false)
return
else
BlzFrameSetVisible(contextMenu.parent, false)
end
end
if BlzFrameIsVisible(searchBar.parent) then
local dx = x - (isExpanded and editor.EXPANDED_X + editor.EXPANDED_WIDTH - searchBar.HORIZONTAL_INSET - searchBar.WIDTH or editor.COLLAPSED_X + editor.COLLAPSED_WIDTH - searchBar.HORIZONTAL_INSET - searchBar.WIDTH)
local dy = y - (editor.Y_TOP - searchBar.VERTICAL_INSET - searchBar.HEIGHT)
if dx > 0 and dx < searchBar.WIDTH and dy > 0 and dy < searchBar.HEIGHT then
if editBoxFocused and selection.hasSelection and selection.startLine == selection.endLine and dx > searchBar.SEARCH_FIELD_LEFT_INSET and dx < searchBar.WIDTH - searchBar.SEARCH_FIELD_RIGHT_INSET and dy > searchBar.SEARCH_FIELD_BOTTOM_INSET and dy < searchBar.HEIGHT - searchBar.SEARCH_FIELD_TOP_INSET then
local text = sub(codeLines[currentTab][step[currentTab]][selection.startLine], math.min(selection.startPos, selection.endPos) + 1, math.max(selection.startPos, selection.endPos))
BlzFrameSetText(searchBar.textField, text)
end
DefocusCodeEditor()
return
end
else
BlzFrameSetFocus(searchBar.textField, false)
end
local line, pos, xc = GetCursorPos(x, y)
if xc then
HideSelection()
selection.hasSelection = false
local diff = timeElapsed - lastMouseClick
lastMouseClick = timeElapsed
if diff < 0.35 and cursor.pos == pos and cursor.rawLine == line then
--Double click
local text = codeLines[currentTab][step[currentTab]][line + lineNumberOffset[currentTab]]
local char = sub(text, pos, pos)
if char == "" then
return
end
--Get bounds of token being selected
local first, last = pos, pos
if char:match("[\x25w_]") then
while sub(text, first, first):match("[\x25w_]") do
first = first - 1
end
while sub(text, last + 1, last + 1):match("[\x25w_]") do
last = last + 1
end
elseif char == " " then
while sub(text, first, first) == " " do
first = first - 1
end
while sub(text, last + 1, last + 1) == " " do
last = last + 1
end
else
while sub(text, first, first):match("[\x25w\x25s_]") == nil and first > 0 do
first = first - 1
end
while sub(text, last + 1, last + 1):match("[\x25w\x25s_]") == nil and last < #text do
last = last + 1
end
end
selection.startLine = cursor.adjustedLine
selection.endLine = cursor.adjustedLine
selection.startPos = first
selection.endPos = last
selection.hasSelection = true
selection.lines[cursor.rawLine] = GetTextHighlightFrame("09", 150, "selection")
BlzFrameSetPoint(selection.lines[cursor.rawLine], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(text, 1, first)), -editor.CODE_TOP_INSET - cursor.rawLine*editor.LINE_SPACING)
BlzFrameSetPoint(selection.lines[cursor.rawLine], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(text, 1, last)), -editor.CODE_TOP_INSET - (cursor.rawLine - 1)*editor.LINE_SPACING)
SetCursorPos(last)
SetCursorX(sub(codeLines[currentTab][step[currentTab]][cursor.adjustedLine], 1, cursor.pos))
SearchForDuplicates()
else
MoveCursor(pos, line, xc)
leftMouseButtonIsPressed = true
end
else
if not (((isExpanded and x >= editor.EXPANDED_X + editor.EXPANDED_WIDTH - editor.CODE_SCROLLER_HORIZONTAL_INSET - 0.006
and x <= editor.EXPANDED_X + editor.COLLAPSED_WIDTH - editor.CODE_SCROLLER_HORIZONTAL_INSET + 0.006)
or (not isExpanded and x >= editor.COLLAPSED_X + editor.COLLAPSED_WIDTH - editor.CODE_SCROLLER_HORIZONTAL_INSET - 0.006
and x <= editor.COLLAPSED_X + editor.COLLAPSED_WIDTH - editor.CODE_SCROLLER_HORIZONTAL_INSET + 0.006))
and y <= editor.Y_TOP - editor.CODE_TOP_INSET and y >= editor.Y_TOP - editor.CODE_TOP_INSET - editor.MAX_LINES_ON_SCREEN*editor.LINE_SPACING) then
DefocusCodeEditor()
BlzFrameSetFocus(searchBar.textField, false)
end
end
else
--Right-click over stop button.
if (isExpanded and x >= editor.EXPANDED_X + editor.STOP_BUTTON_HORIZONTAL_INSET
and x <= editor.EXPANDED_X + editor.STOP_BUTTON_HORIZONTAL_INSET + editor.LINE_SPACING)
or (not isExpanded and x >= editor.COLLAPSED_X + editor.STOP_BUTTON_HORIZONTAL_INSET
and x <= editor.COLLAPSED_X + editor.STOP_BUTTON_HORIZONTAL_INSET + editor.LINE_SPACING) then
local lineNumber = math.floor(((editor.Y_TOP - editor.CODE_TOP_INSET) - y)/editor.LINE_SPACING) + 1
if lineNumber >= 1 and lineNumber <= editor.MAX_LINES_ON_SCREEN then
PutStop(lineNumber + lineNumberOffset[currentTab], 2)
end
--Right-click to open context menu.
elseif (isExpanded and x >= editor.EXPANDED_X + editor.CODE_LEFT_INSET
and x <= editor.EXPANDED_X + editor.EXPANDED_WIDTH - editor.CODE_RIGHT_INSET)
or (not isExpanded and x >= editor.COLLAPSED_X + editor.CODE_LEFT_INSET
and x <= editor.COLLAPSED_X + editor.COLLAPSED_WIDTH - editor.CODE_RIGHT_INSET) then
local line, pos, xc = GetCursorPos(x, y)
if line >= 1 and line <= editor.MAX_LINES_ON_SCREEN then
if not selection.hasSelection then
MoveCursor(pos, line, xc)
end
BlzFrameSetVisible(contextMenu.parent, true)
BlzFrameSetEnable(contextMenu.copy, selection.hasSelection)
BlzFrameSetEnable(contextMenu.cut, selection.hasSelection)
BlzFrameSetEnable(contextMenu.execute, selection.hasSelection)
BlzFrameSetEnable(contextMenu.evaluate, selection.hasSelection)
if selection.hasSelection then
BlzFrameSetText(BlzFrameGetChild(contextMenu.copy, 0), "Copy")
BlzFrameSetText(BlzFrameGetChild(contextMenu.cut, 0), "Cut")
BlzFrameSetText(BlzFrameGetChild(contextMenu.execute, 0), "Execute")
BlzFrameSetText(BlzFrameGetChild(contextMenu.evaluate, 0), "Evaluate")
else
BlzFrameSetText(BlzFrameGetChild(contextMenu.copy, 0), "|cff999999Copy|r")
BlzFrameSetText(BlzFrameGetChild(contextMenu.cut, 0), "|cff999999Cut|r")
BlzFrameSetText(BlzFrameGetChild(contextMenu.execute, 0), "|cff999999Execute|r")
BlzFrameSetText(BlzFrameGetChild(contextMenu.evaluate, 0), "|cff999999Evaluate|r")
end
local selectedText = sub(codeLines[currentTab][step[currentTab]][selection.startLine], math.min(selection.startPos + 1, selection.endPos), math.max(selection.startPos + 1, selection.endPos))
if selection.hasSelection
and selection.startLine == selection.endLine
and match(selectedText, "^[\x25w_]+$") ~= nil
and match(sub(selectedText, 1, 1), "\x25d") == nil
and not LUA_KEYWORDS[selectedText] then
BlzFrameSetEnable(contextMenu.gotodef, true)
BlzFrameSetText(BlzFrameGetChild(contextMenu.gotodef, 0), "Go to Definition")
else
BlzFrameSetEnable(contextMenu.gotodef, false)
BlzFrameSetText(BlzFrameGetChild(contextMenu.gotodef, 0), "|cff999999Go to Definition|r")
end
BlzFrameSetEnable(contextMenu.paste, clipboard ~= nil)
if clipboard ~= nil then
BlzFrameSetText(BlzFrameGetChild(contextMenu.paste, 0), "Paste")
else
BlzFrameSetText(BlzFrameGetChild(contextMenu.paste, 0), "|cff999999Paste|r")
end
contextMenu.minX = x
contextMenu.maxX = x + flagsMenu.WIDTH
BlzFrameClearAllPoints(contextMenu.parent)
if line < editor.MAX_LINES_ON_SCREEN/2 then
BlzFrameSetAbsPoint(contextMenu.parent, FRAMEPOINT_TOPLEFT, x, y)
BlzFrameSetSize(contextMenu.parent, flagsMenu.WIDTH, 2*flagsMenu.TEXT_INSET + 6*editor.LINE_SPACING)
contextMenu.minY = y - 2*flagsMenu.TEXT_INSET - 6*editor.LINE_SPACING
contextMenu.maxY = y
else
BlzFrameSetAbsPoint(contextMenu.parent, FRAMEPOINT_BOTTOMLEFT, x, y)
BlzFrameSetSize(contextMenu.parent, flagsMenu.WIDTH, 2*flagsMenu.TEXT_INSET + 6*editor.LINE_SPACING)
contextMenu.minY = y
contextMenu.maxY = y + 2*flagsMenu.TEXT_INSET + 6*editor.LINE_SPACING
end
end
end
end
end
local function CheckForAutoCompleteSuggestions(str, rawLineNumber, searchFileNames)
local shift = 0
local entered, lookupTable, prefix, hasDot
if not searchFileNames then
if cursor.pos < #str then
local i = cursor.pos
while sub(str, i, i):match("[\x25w_]") do
i = i + 1
shift = shift + 1
end
end
entered = match(sub(str, 1, cursor.pos), "([\x25w_\x25.:]+)$") --last word including dot or colon
if entered == nil or #entered < 2 or find(str, "\x25-\x25-") or find(str, "[\x25w_]") == nil or find(str, "\x25.\x25.") ~= nil then
BlzFrameSetVisible(helperFrame, false)
return false
end
--Check if in string.
local num = 0
for __ in str:gmatch("'") do
num = num + 1
end
if math.fmod(num, 2) == 1 then
BlzFrameSetVisible(helperFrame, false)
return false
end
num = 0
for __ in str:gmatch('"') do
num = num + 1
end
if math.fmod(num, 2) == 1 then
BlzFrameSetVisible(helperFrame, false)
return false
end
hasDot = find(entered, "[\x25.:]") ~= nil
lookupTable = globalLookupTable
prefix = ""
for match in entered:gmatch("\x25s*([\x25w_]+)\x25s*[\x25.:]") do
lookupTable = lookupTable[match]
if type(lookupTable) ~= "table" then
BlzFrameSetVisible(helperFrame, false)
return false
end
prefix = prefix .. match .. "."
end
if hasDot then
entered = entered:match("[\x25.:]\x25s*([\x25w_]+)\x25s*$") or "" --last table field after dots or colons
end
else
entered = str
if str == "" then
BlzFrameSetVisible(helperFrame, false)
return false
end
lookupTable = orderedFileNames
end
local low, high = 1, #lookupTable
while low <= high do
local mid = math.floor((low + high) / 2)
if sub(lookupTable[mid], 1, #entered) < entered then
low = mid + 1
else
high = mid - 1
end
end
while low <= #lookupTable and sub(lookupTable[low], 1, #entered) == entered do
autoCompleteSuggestions[#autoCompleteSuggestions + 1] = lookupTable[low]
low = low + 1
end
if not searchFileNames and not hasDot then
low, high = 1, #localLookupTable
while low <= high do
local mid = math.floor((low + high) / 2)
if sub(localLookupTable[mid], 1, #entered) < entered then
low = mid + 1
else
high = mid - 1
end
end
while low <= #localLookupTable and sub(localLookupTable[low], 1, #entered) == entered do
autoCompleteSuggestions[#autoCompleteSuggestions + 1] = localLookupTable[low]
low = low + 1
end
end
if #autoCompleteSuggestions > 0 then
table.sort(autoCompleteSuggestions)
BlzFrameSetVisible(helperFrame, true)
local first = true
for i = 1, #concatTable do
concatTable[i] = nil
end
for i = 1, #autoCompleteSuggestions do
if not first then
concatTable = concatTable .. "\n"
else
first = false
end
concatTable = ((((((((concatTable .. "|cffffcc00") .. prefix) .. "|r") .. "|cffffcc00") .. entered) .. "|r|cffaaaaaa") .. sub(autoCompleteSuggestions[i], #entered + 1)) .. "|r")
end
BlzFrameSetText(helperText, table.concat(concatTable))
BlzFrameSetSize(helperText, 0, 0)
BlzFrameSetSize(helperFrame, BlzFrameGetWidth(helperText) + 0.009, BlzFrameGetHeight(helperText) + 0.009)
if searchFileNames then
BlzFrameClearAllPoints(helperFrame)
BlzFrameSetPoint(helperFrame, FRAMEPOINT_BOTTOMLEFT, nameDialog.parent, FRAMEPOINT_TOP, 0, 0)
else
local pos = cursor.pos + shift - #entered
BlzFrameSetText(widthTestFrame, sub(str, 1, pos))
BlzFrameSetSize(widthTestFrame, 0, 0)
local x = math.min(editor.CODE_LEFT_INSET + BlzFrameGetWidth(widthTestFrame), (isExpanded and editor.EXPANDED_WIDTH or editor.COLLAPSED_WIDTH) - BlzFrameGetWidth(helperFrame))
if rawLineNumber < editor.MAX_LINES_ON_SCREEN/2 then
BlzFrameClearAllPoints(helperFrame)
BlzFrameSetPoint(helperFrame, FRAMEPOINT_TOPLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, x, -editor.CODE_TOP_INSET - rawLineNumber*editor.LINE_SPACING)
else
BlzFrameClearAllPoints(helperFrame)
BlzFrameSetPoint(helperFrame, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, x, -editor.CODE_TOP_INSET - (rawLineNumber-1)*editor.LINE_SPACING)
end
end
for i = 1, #autoCompleteSuggestions do
autoCompleteSuggestions[i] = nil
end
return true
else
BlzFrameSetVisible(helperFrame, false)
return false
end
end
local function CheckForFunctionPreviews(str, rawLineNumber, cursorpos)
str = sub(str, 1, cursorpos)
local currentLevel = select(2, str:gsub("\x25(", "")) - select(2, str:gsub("\x25)", "")) --opened parentheses
local loopLevel = currentLevel
local char
if currentLevel > 0 then
for i = #str, 1, -1 do
char = sub(str, i, i)
if char == "(" then
loopLevel = loopLevel - 1
if loopLevel < currentLevel then
local funcName = sub(str, 1, i):match("([\x25w_\x25.:]+)\x25s*\x25($") --var name before (
if FUNCTION_PREVIEW[funcName] then
BlzFrameSetVisible(helperFrame, true)
local numCommas = select(2, sub(str, i):gsub(",", ""))
for j = 1, #concatTable do
concatTable[j] = nil
end
local j = 0
concatTable = (((concatTable .. "|cffaaaaaafunction|r ") .. funcName) .. "(")
for argName in FUNCTION_PREVIEW[funcName]:gmatch("([\x25w_]+)\x25s*,") do
if j == numCommas then
if j > 0 then
concatTable = (((concatTable .. ", |cffffcc00") .. argName) .. "|r")
else
concatTable = (((concatTable .. "|cffffcc00") .. argName) .. "|r")
end
else
if j > 0 then
concatTable = ((concatTable .. ", ") .. argName)
else
concatTable = concatTable .. argName
end
end
j = j + 1
end
BlzFrameSetText(widthTestFrame, sub(str, 1, i))
BlzFrameSetSize(widthTestFrame, 0, 0)
BlzFrameSetText(helperText, table.concat(concatTable .. ")"))
BlzFrameSetSize(helperText, 0, 0)
BlzFrameSetSize(helperFrame, BlzFrameGetWidth(helperText) + 0.009, BlzFrameGetHeight(helperText) + 0.009)
local x = math.min(editor.CODE_LEFT_INSET + BlzFrameGetWidth(widthTestFrame), (isExpanded and editor.EXPANDED_WIDTH or editor.COLLAPSED_WIDTH) - BlzFrameGetWidth(helperFrame))
BlzFrameClearAllPoints(helperFrame)
BlzFrameSetPoint(helperFrame, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, x, -editor.CODE_TOP_INSET - (rawLineNumber-1)*editor.LINE_SPACING)
return true
end
end
elseif char == ")" then
loopLevel = loopLevel + 1
end
end
else
BlzFrameSetVisible(helperFrame, false)
return false
end
end
local function CheckForVariableDisplay(line, pos, executionLine, xs)
local text = codeLines[currentTab][step[currentTab]][line]
local char = sub(text, pos, pos)
if char == "" then
return
end
local stringList = {}
text = text:gsub('"[^"]*"', function(str)
stringList[#stringList + 1] = str
return "__STRING" .. #stringList
end)
text = text:gsub("'[^']*'", function(str)
stringList[#stringList + 1] = str
return "__STRING" .. #stringList
end)
local first, last = pos, pos
if char:match("[\x25w_\x25.\x25[\x25]]") == nil then
return
else
while sub(text, first, first):match("[\x25w_\x25.\x25[\x25]]") do
first = first - 1
end
while sub(text, last + 1, last + 1):match("[\x25w_\x25.\x25[\x25]]") do
last = last + 1
end
local token = sub(text, first + 1, last)
local tableTrace = {}
local beginning = 1
repeat
pos = find(token, "[\x25.\x25[]", beginning)
if pos then
tableTrace[#tableTrace + 1] = sub(token, beginning, pos - 1)
if sub(tableTrace[#tableTrace], -1, -1) == "]" then
tableTrace[#tableTrace] = sub(tableTrace[#tableTrace], 1, -2)
tableTrace[#tableTrace] = tonumber(tableTrace[#tableTrace]) or tableTrace[#tableTrace]
end
beginning = pos + 1
else
tableTrace[#tableTrace + 1] = sub(token, beginning)
if sub(tableTrace[#tableTrace], -1, -1) == "]" then
tableTrace[#tableTrace] = sub(tableTrace[#tableTrace], 1, -2)
tableTrace[#tableTrace] = tonumber(tableTrace[#tableTrace]) or tableTrace[#tableTrace]
end
end
until pos == nil
for i = 1, #stringList do
for j = 1, #tableTrace do
if type(tableTrace[j]) == "string" then
tableTrace[j] = tableTrace[j]:gsub("__STRING" .. i, sub(stringList[i], 2, -2))
end
end
token = token:gsub("__STRING" .. i, stringList[i])
end
local level = visibleVarsOfLine[currentTab][executionLine][tableTrace[1]]
if level then
local var = WSDebug.VarTable[currentTab][level][tableTrace[1]]
for i = 2, #tableTrace do
if type(var) ~= "table" then
break
end
var = var[tableTrace[i]]
end
local str = Value2String(var, nil, 1000)
local dx = xs - (isExpanded and editor.EXPANDED_X or editor.COLLAPSED_X)
BlzFrameSetVisible(helperFrame, true)
BlzFrameClearAllPoints(helperFrame)
BlzFrameSetPoint(helperFrame, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, dx, -editor.CODE_TOP_INSET - (line - lineNumberOffset[currentTab] - 1)*editor.LINE_SPACING)
BlzFrameSetText(helperText, token .. ": " .. str)
BlzFrameSetSize(helperText, 0, 0)
BlzFrameSetSize(helperFrame, BlzFrameGetWidth(helperText) + 0.009, BlzFrameGetHeight(helperText) + 0.009)
isShowingVariableTooltip = true
end
end
end
local function LineOpensScope(whichLine)
return find(whichLine, "\x25f[\x25a_]do\x25f[^\x25w_]")
or find(whichLine, "\x25f[\x25a_]then\x25f[^\x25w_]")
or find(whichLine, "\x25f[\x25a_]repeat\x25f[^\x25w_]")
or find(whichLine, "\x25f[\x25a_]else\x25f[^\x25w_]")
or find(whichLine, "\x25f[\x25a_]elseif\x25f[^\x25w_]")
or find(whichLine, "[\x25(\x25[{]\x25s*$")
or find(whichLine, "\x25f[\x25a_]function\x25f[^\x25w_]")
end
local function ConvertSelectionToLines()
local lower = math.min(math.max(selection.startLine, selection.endLine), highestNonEmptyLine[currentTab][step[currentTab]])
local upper = math.min(selection.startLine, selection.endLine)
if lower < upper then
return {}
end
local currentLines = codeLines[currentTab][step[currentTab]]
local left, right
if lower == upper then
left = math.min(selection.startPos, selection.endPos)
right = math.max(selection.startPos, selection.endPos)
else
left = selection.startLine == upper and selection.startPos or selection.endPos
right = selection.startLine == lower and selection.startPos or selection.endPos
end
local lines = {}
if lower == upper then
lines[1] = sub(currentLines[upper], left + 1, right)
else
lines[1] = sub(currentLines[upper], left + 1)
end
for i = upper + 1, lower do
if i == lower then
lines[#lines + 1] = sub(currentLines[lower], 1, right)
else
lines[#lines + 1] = currentLines[i]
end
end
return lines
end
local function DeleteSelection()
local lower = math.max(selection.startLine, selection.endLine)
local upper = math.min(selection.startLine, selection.endLine)
if not lower or not upper then
return
end
local currentLines = codeLines[currentTab][step[currentTab]]
local left, right
if lower == upper then
left = math.min(selection.startPos, selection.endPos)
right = math.max(selection.startPos, selection.endPos)
else
left = selection.startLine == upper and selection.startPos or selection.endPos
right = selection.startLine == lower and selection.startPos or selection.endPos
end
currentLines[upper] = sub(currentLines[upper], 1, left) .. sub(currentLines[lower], right + 1)
coloredCodeLines[currentTab][step[currentTab]][upper] = GetColoredText(currentLines[upper], upper, currentTab)
local completeDeletes = lower - upper
if completeDeletes > 0 then
for i = upper + 1, highestNonEmptyLine[currentTab][step[currentTab]] do
currentLines[i] = currentLines[i + completeDeletes]
coloredCodeLines[currentTab][step[currentTab]][i] = coloredCodeLines[currentTab][step[currentTab]][i + completeDeletes]
end
highestNonEmptyLine[currentTab][step[currentTab]] = highestNonEmptyLine[currentTab][step[currentTab]] - completeDeletes
BlzFrameSetMinMaxValue(codeScroller, 0, highestNonEmptyLine[currentTab][step[currentTab]])
end
cursor.adjustedLine = upper
cursor.rawLine = upper - lineNumberOffset[currentTab]
SetCursorPos(upper == lower and left or upper == selection.startLine and selection.startPos or selection.endPos)
SetCursorX(sub(currentLines[cursor.adjustedLine], 1, cursor.pos))
lastKeyStroke = timeElapsed
lineNumberOfEdit[currentTab][step[currentTab]] = upper
posOfEdit[currentTab][step[currentTab]] = cursor.pos
HideSelection()
selection.hasSelection = false
UpdateAllLines()
end
local function GetFirstDifferentCharPos(str1, str2, firstLook)
local found
firstLook = math.min(#str2, firstLook)
for i = firstLook, math.max(#str1, #str2) do
if sub(str1, i, i) ~= sub(str2, i, i) then
return i, true
end
end
if not found then
for i = 1, firstLook - 1 do
if sub(str1, i, i) ~= sub(str2, i, i) then
return i, true
end
end
end
return firstLook, false
end
local function ShiftLines(amount, firstLine)
local currentLines = codeLines[currentTab][step[currentTab]]
local currentColoredLines = coloredCodeLines[currentTab][step[currentTab]]
if amount > 0 then
for i = highestNonEmptyLine[currentTab][step[currentTab]] + amount, firstLine + amount, -1 do
currentLines[i] = currentLines[i - amount]
currentColoredLines[i] = currentColoredLines[i - amount]
if lines.debugState[currentTab][i] ~= lines.debugState[currentTab][i - amount] then
lines.debugState[currentTab][i] = lines.debugState[currentTab][i - amount]
BlzFrameSetTexture(BlzFrameGetChild(stopButtons[i - lineNumberOffset[currentTab]], 0), LINE_STATE_ICONS[lines.debugState[currentTab][i]], 0, true)
end
end
for i = firstLine, firstLine + amount do
lines.debugState[currentTab][i] = 0
BlzFrameSetTexture(BlzFrameGetChild(stopButtons[i - lineNumberOffset[currentTab]], 0), LINE_STATE_ICONS[0], 0, true)
end
else
amount = -amount
for i = firstLine + amount, highestNonEmptyLine[currentTab][step[currentTab]] + amount do
currentLines[i - amount] = currentLines[i]
currentColoredLines[i - amount] = currentColoredLines[i]
if lines.debugState[currentTab][i - amount] ~= lines.debugState[currentTab][i] then
lines.debugState[currentTab][i - amount] = lines.debugState[currentTab][i]
BlzFrameSetTexture(BlzFrameGetChild(stopButtons[i - amount - lineNumberOffset[currentTab]], 0), LINE_STATE_ICONS[lines.debugState[currentTab][i - amount]], 0, true)
end
end
end
end
local function ChangeCodeLine(text)
local oldText = enterBoxText
if text then
enterBoxText = text
else
enterBoxText = BlzFrameGetText(enterBox)
if oldText == enterBoxText then
return
end
end
BlzFrameSetEnable(buttons.undo, true)
BlzFrameSetEnable(buttons.redo, false)
BlzFrameSetVisible(currentLineHighlight, false)
hasCurrentLineHighlight = false
BlzFrameSetVisible(errorHighlight, false)
BlzFrameSetVisible(variableViewer.parent, false)
editBoxFocused = true
BlzFrameSetVisible(cursorFrame, true)
WSDebug.Coroutine[currentTab] = nil
hasError[currentTab] = false
WSDebug.GenerationCounter[currentTab] = WSDebug.GenerationCounter[currentTab] + 1
if timeElapsed - lastKeyStroke > editor.MIN_TIME_BETWEEN_UNDO_STEPS or cursor.adjustedLine ~= lastFrame or selection.hasSelection then
IncrementStep()
end
local currentLines = codeLines[currentTab][step[currentTab]]
local selectionDeleted
if selection.hasSelection then
DeleteSelection()
selectionDeleted = true
end
if #enterBoxText == 0 or #enterBoxText == #oldText - 1 or (editor.LEGAL_CHARACTERS and not editor.LEGAL_CHARACTERS[sub(oldText, -1,-1) or sub(oldText, -2, -1) == " "] and #enterBoxText == #oldText - 2) then
--Backspace
if not selectionDeleted then
if cursor.pos > 0 then
if sub(currentLines[cursor.adjustedLine], 1, cursor.pos):match("^\x25s*$") then
local oldCursorPos = cursor.pos
SetCursorPos(math.floor((cursor.pos - 1)/4)*4)
currentLines[cursor.adjustedLine] = sub(currentLines[cursor.adjustedLine], 1, cursor.pos) .. sub(currentLines[cursor.adjustedLine], oldCursorPos + 1)
elseif brackets.lines[1] == cursor.adjustedLine and brackets.pos[1] == cursor.pos and brackets.lines[2] == cursor.adjustedLine and brackets.pos[2] == cursor.pos + 1 then
currentLines[cursor.adjustedLine] = sub(currentLines[cursor.adjustedLine], 1, cursor.pos - 1) .. sub(currentLines[cursor.adjustedLine], cursor.pos + 2)
SetCursorPos(cursor.pos - 1)
else
currentLines[cursor.adjustedLine] = sub(currentLines[cursor.adjustedLine], 1, cursor.pos - 1) .. sub(currentLines[cursor.adjustedLine], cursor.pos + 1)
SetCursorPos(cursor.pos - 1)
end
--Backspace on empty line
elseif cursor.adjustedLine > 1 then
SetCursorPos(#currentLines[cursor.adjustedLine - 1])
currentLines[cursor.adjustedLine - 1] = currentLines[cursor.adjustedLine - 1] .. currentLines[cursor.adjustedLine]
coloredCodeLines[currentTab][step[currentTab]][cursor.adjustedLine - 1] = GetColoredText(currentLines[cursor.adjustedLine - 1], cursor.adjustedLine - 1, currentTab)
BlzFrameSetText(codeLineFrames[cursor.rawLine - 1], coloredCodeLines[currentTab][step[currentTab]][cursor.adjustedLine - 1])
ShiftLines(-1, cursor.adjustedLine)
UpdateAllLines()
cursor.rawLine = cursor.rawLine - 1
cursor.adjustedLine = cursor.adjustedLine - 1
SetCursorX(sub(currentLines[cursor.adjustedLine], 1, cursor.pos))
BlzFrameSetAlpha(cursorFrame, 255)
if #enterBoxText == 0 or #enterBoxText == 255 then
enterBoxText = " "
BlzFrameSetText(enterBox, " ")
end
SetLocalList(cursor.adjustedLine)
return
end
end
local found
firstDifferentChar, found = GetFirstDifferentCharPos(enterBoxText, oldText, math.max(1, firstDifferentChar - 1))
if found and firstDifferentChar == 1 then
enterBoxText = " "
BlzFrameSetText(enterBox, " ")
end
else
--Entered char
local found
firstDifferentChar, found = GetFirstDifferentCharPos(enterBoxText, oldText, firstDifferentChar)
if not found then
if #enterBoxText == 0 or #enterBoxText == 255 then
enterBoxText = " "
BlzFrameSetText(enterBox, " ")
end
return
end
if editor.LEFT_ARROW_REPLACEMENT_CHAR and sub(enterBoxText, firstDifferentChar, firstDifferentChar + #editor.LEFT_ARROW_REPLACEMENT_CHAR - 1) == editor.LEFT_ARROW_REPLACEMENT_CHAR then
SetCursorPos(math.max(cursor.pos - 1, 0))
elseif editor.RIGHT_ARROW_REPLACEMENT_CHAR and sub(enterBoxText, firstDifferentChar, firstDifferentChar + #editor.RIGHT_ARROW_REPLACEMENT_CHAR - 1) == editor.RIGHT_ARROW_REPLACEMENT_CHAR then
SetCursorPos(math.min(cursor.pos + 1, #currentLines[cursor.adjustedLine]))
elseif editor.DELETE_REPLACEMENT_CHAR and sub(enterBoxText, firstDifferentChar, firstDifferentChar + #editor.DELETE_REPLACEMENT_CHAR - 1) == editor.DELETE_REPLACEMENT_CHAR then
currentLines[cursor.adjustedLine] = sub(currentLines[cursor.adjustedLine], 1, cursor.pos) .. sub(currentLines[cursor.adjustedLine], cursor.pos + 2)
coloredCodeLines[currentTab][step[currentTab]][cursor.adjustedLine] = GetColoredText(currentLines[cursor.adjustedLine], cursor.adjustedLine, currentTab)
SetCursorPos(cursor.pos)
else
local isTab = editor.TAB_REPLACEMENT_CHAR and sub(enterBoxText, firstDifferentChar, firstDifferentChar + #editor.TAB_REPLACEMENT_CHAR - 1) == editor.TAB_REPLACEMENT_CHAR
local numChars = #enterBoxText - #oldText
if numChars < 1 then
numChars = 1
end
local newText
if isTab then
local numSpaces = math.floor((cursor.pos)/4)*4 - cursor.pos + 4
newText = ""
for __ = 1, numSpaces do
newText = newText .. " "
end
elseif not text then
newText = sub(enterBoxText, firstDifferentChar, firstDifferentChar + numChars - 1)
else
newText = enterBoxText
end
if find(newText, "\n") then
--Copy & pasted code chunk
local newLines = ConvertStringToLines(newText)
for i = 1, #newLines do
newLines[i] = newLines[i]:gsub("\t", " "):gsub("\n", ""):gsub("\r", "")
end
local newCodeSize = #newLines
ShiftLines(newCodeSize, cursor.adjustedLine)
highestNonEmptyLine[currentTab][step[currentTab]] = highestNonEmptyLine[currentTab][step[currentTab]] + newCodeSize
local remainingText = sub(currentLines[cursor.adjustedLine], cursor.pos + 1)
currentLines[cursor.adjustedLine] = sub(currentLines[cursor.adjustedLine], 1, cursor.pos) .. newLines[1]
coloredCodeLines[currentTab][step[currentTab]][cursor.adjustedLine] = GetColoredText(currentLines[cursor.adjustedLine], cursor.adjustedLine, currentTab)
local j = cursor.adjustedLine + 1
for i = 2, newCodeSize - 1 do
currentLines[j] = newLines[i]
coloredCodeLines[currentTab][step[currentTab]][j] = GetColoredText(currentLines[j], j, currentTab)
j = j + 1
end
currentLines[j] = newLines[newCodeSize] .. remainingText
coloredCodeLines[currentTab][step[currentTab]][j] = GetColoredText(currentLines[j], j, currentTab)
UpdateAllLines()
cursor.rawLine = j - lineNumberOffset[currentTab]
cursor.adjustedLine = j
SetCursorPos(#newLines[#newLines])
BlzFrameSetAlpha(cursorFrame, 255)
enterBoxText = " "
BlzFrameSetText(enterBox, " ")
else
--Entered single line or character
if editor.LEGAL_CHARACTERS then
for i = 1, #newText do
if not editor.LEGAL_CHARACTERS[sub(newText, i, i)] and sub(newText, i, i) ~= "\n" then
PlayError()
enterBoxText = " "
BlzFrameSetText(enterBox, " ")
print("|cffff5555Illegal character " .. sub(newText, i, i) .. ".")
return
end
end
end
if editor.AUTO_CLOSE_BRACKETS_AND_QUOTES then
local str = currentLines[cursor.adjustedLine]
--Do not enter character and simply move cursor forward if bracket is already closed.
if sub(str, cursor.pos + 1, cursor.pos + 1) == newText and
((newText == ")" and select(2, str:gsub("\x25(", "")) - select(2, str:gsub("\x25)", "")) <= 0) or
(newText == "]" and select(2, str:gsub("\x25[", "")) - select(2, str:gsub("\x25]", "")) <= 0) or
(newText == "}" and select(2, str:gsub("{", "")) - select(2, str:gsub("}", "")) <= 0) or
(newText == '"' and math.fmod(select(2, str:gsub('"', "")), 2) == 0) or
(newText == "'" and math.fmod(select(2, str:gsub("'", "")), 2) == 0)) then
SetCursorPos(cursor.pos + 1)
else
currentLines[cursor.adjustedLine] = sub(currentLines[cursor.adjustedLine], 1, cursor.pos) .. newText .. sub(currentLines[cursor.adjustedLine], cursor.pos + 1)
SetCursorPos(cursor.pos + #newText)
if newText == "(" then
currentLines[cursor.adjustedLine] = sub(currentLines[cursor.adjustedLine], 1, cursor.pos) .. ")" .. sub(currentLines[cursor.adjustedLine], cursor.pos + 1)
SearchForClosingBracket()
elseif newText == "[" and find(sub(str, 1, cursor.pos), "\x25-\x25-") == nil then
currentLines[cursor.adjustedLine] = sub(currentLines[cursor.adjustedLine], 1, cursor.pos) .. "]" .. sub(currentLines[cursor.adjustedLine], cursor.pos + 1)
SearchForClosingBracket()
elseif newText == "{" then
currentLines[cursor.adjustedLine] = sub(currentLines[cursor.adjustedLine], 1, cursor.pos) .. "}" .. sub(currentLines[cursor.adjustedLine], cursor.pos + 1)
SearchForClosingBracket()
elseif newText == '"' then
currentLines[cursor.adjustedLine] = sub(currentLines[cursor.adjustedLine], 1, cursor.pos) .. '"' .. sub(currentLines[cursor.adjustedLine], cursor.pos + 1)
elseif newText == "'" then
currentLines[cursor.adjustedLine] = sub(currentLines[cursor.adjustedLine], 1, cursor.pos) .. "'" .. sub(currentLines[cursor.adjustedLine], cursor.pos + 1)
end
end
else
currentLines[cursor.adjustedLine] = sub(currentLines[cursor.adjustedLine], 1, cursor.pos) .. newText .. sub(currentLines[cursor.adjustedLine], cursor.pos + 1)
SetCursorPos(cursor.pos + #newText)
end
end
end
end
if #enterBoxText == 0 or #enterBoxText == 255 then
enterBoxText = " "
BlzFrameSetText(enterBox, " ")
end
if FUNCTION_PREVIEW then
if not CheckForAutoCompleteSuggestions(currentLines[cursor.adjustedLine], cursor.rawLine) then
CheckForFunctionPreviews(currentLines[cursor.adjustedLine], cursor.rawLine, cursor.pos)
end
end
lastKeyStroke = timeElapsed
lastFrame = cursor.adjustedLine
if currentLines[cursor.adjustedLine]:match("^\x25s*nd$") then
currentLines[cursor.adjustedLine] = currentLines[cursor.adjustedLine]:gsub("nd", "end")
SetCursorPos(cursor.pos + 1)
end
--Auto-align on scope close.
if cursor.pos == #currentLines[cursor.adjustedLine] and (currentLines[cursor.adjustedLine]:match("^\x25s*end") or currentLines[cursor.adjustedLine]:match("^\x25s*\x25}") or currentLines[cursor.adjustedLine]:match("^\x25s*\x25)") or currentLines[cursor.adjustedLine]:match("^\x25s*until") or currentLines[cursor.adjustedLine]:match("^\x25s*else")) then
local numSpaces = 0
local previousLine = currentLines[cursor.adjustedLine - 1]
local i = 1
while sub(previousLine, i, i) == " " do
numSpaces = numSpaces + 1
i = i + 1
end
if LineOpensScope(previousLine) then
numSpaces = numSpaces + 4
end
numSpaces = math.max(0, numSpaces - 4)
local whiteSpace = ""
for i = 1, numSpaces do
whiteSpace = whiteSpace .. " "
end
local length = #currentLines[cursor.adjustedLine]
currentLines[cursor.adjustedLine] = whiteSpace .. currentLines[cursor.adjustedLine]:gsub("^\x25s*", "")
SetCursorPos(cursor.pos - (length - #currentLines[cursor.adjustedLine]))
end
--Textmacros
local replacedCode = HandleTextMacros(currentLines[cursor.adjustedLine])
if replacedCode and replacedCode ~= currentLines[cursor.adjustedLine] then
local lengthDiff = #replacedCode - #currentLines[cursor.adjustedLine]
currentLines[cursor.adjustedLine] = replacedCode
SetCursorPos(cursor.pos + lengthDiff)
end
--Adjust code scroller and window position on new line.
highestNonEmptyLine[currentTab][step[currentTab]] = math.max(highestNonEmptyLine[currentTab][step[currentTab]], cursor.adjustedLine)
BlzFrameSetMinMaxValue(codeScroller, 0, highestNonEmptyLine[currentTab][step[currentTab]])
SetLineOffset(lineNumberOffset[currentTab])
JumpWindow(cursor.adjustedLine)
coloredCodeLines[currentTab][step[currentTab]][cursor.adjustedLine] = GetColoredText(currentLines[cursor.adjustedLine], cursor.adjustedLine, currentTab)
--Set frame text.
BlzFrameSetText(codeLineFrames[cursor.rawLine], coloredCodeLines[currentTab][step[currentTab]][cursor.adjustedLine])
SetCursorX(sub(currentLines[cursor.adjustedLine], 1, cursor.pos))
BlzFrameSetAlpha(cursorFrame, 255)
end
local function AdjustLineDisplay()
local newValue
timeElapsed = timeElapsed + 0.01
if not BlzFrameIsVisible(codeEditorParent) then
return
end
if not WSDebug.Coroutine[currentTab] or coroutine.status(WSDebug.Coroutine[currentTab]) == "dead" or coroutineIsDead[WSDebug.Coroutine[currentTab]] then
BlzFrameSetVisible(currentLineHighlight, false)
hasCurrentLineHighlight = false
end
if BlzFrameGetText(enterBox) ~= enterBoxText then
ChangeCodeLine()
end
if tabNavigator.hoveringOver then
if not BlzFrameIsVisible(tabNavigator.highlights[tabNavigator.hoveringOver]) and not BlzFrameIsVisible(BlzFrameGetChild(tabNavigator.closeButtons[tabNavigator.hoveringOver], 2)) then
BlzFrameSetVisible(tabNavigator.closeButtons[tabNavigator.hoveringOver], false)
tabNavigator.hoveringOver = nil
end
else
for i = 1, numTabs do
if BlzFrameIsVisible(tabNavigator.highlights[tabNames[i]]) then
tabNavigator.hoveringOver = tabNames[i]
BlzFrameSetVisible(tabNavigator.closeButtons[tabNavigator.hoveringOver], true)
break
end
end
end
if not checkedForVariableDisplay and timeElapsed - mouse.lastMove > 0.35 then
local xs, ys = World2Screen(mouse.x, mouse.y, GetTerrainZ(mouse.x, mouse.y))
local line, pos, xc = GetCursorPos(xs, ys)
line = line + lineNumberOffset[currentTab]
if line and pos and line < lastLineNumber[currentTab] and pos < #codeLines[currentTab][step[currentTab]][line] then
CheckForVariableDisplay(line, pos, lastLineNumber[currentTab], xs)
end
checkedForVariableDisplay = true
end
cursorCounter = cursorCounter + 1
if math.fmod(math.floor(cursorCounter/50), 2) == 0 then
BlzFrameSetAlpha(cursorFrame, 255)
else
BlzFrameSetAlpha(cursorFrame, 0)
end
if timeElapsed - lastKeyStroke > editor.MIN_TIME_BETWEEN_UNDO_STEPS and changeMade then
ConvertAndValidateLines(codeLines[currentTab][step[currentTab]])
if editor.AUTO_SAVE_AFTER_EDIT and highestNonEmptyLine[currentTab][step[currentTab]] >= editor.AUTO_SAVE_MIN_CODE_LINES then
local currentLines = codeLines[currentTab][step[currentTab]]
PreloadGenClear()
PreloadGenStart()
for i = 1, highestNonEmptyLine[currentTab][step[currentTab]] do
Preload(currentLines[i]:gsub("\\\\", "\\"):gsub("||", "|"))
end
PreloadGenEnd(editor.EXPORT_SUBFOLDER .. "\\WSCodeAutoSave" .. currentTab:gsub(" ", "") .. ".txt")
end
changeMade = false
end
newValue = highestNonEmptyLine[currentTab][step[currentTab]] - BlzFrameGetValue(codeScroller)
if newValue ~= lineNumberOffset[currentTab] then
if selection.hasSelection then
HideSelection()
end
lineNumberOffset[currentTab] = newValue
BlzFrameSetVisible(helperFrame, false)
for i = 1, editor.MAX_LINES_ON_SCREEN do
BlzFrameSetText(codeLineFrames[i], coloredCodeLines[currentTab][step[currentTab]][i + lineNumberOffset[currentTab]])
BlzFrameSetText(lineNumbers[i], "|cff999999" .. math.tointeger(i + lineNumberOffset[currentTab]) .. "|r")
BlzFrameSetTexture(BlzFrameGetChild(stopButtons[i], 0), LINE_STATE_ICONS[lines.debugState[currentTab][i + lineNumberOffset[currentTab]]], 0, true)
end
SetCurrentLine()
if cursor.adjustedLine then
cursor.rawLine = cursor.adjustedLine - lineNumberOffset[currentTab]
BlzFrameSetPoint(cursorFrame, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + cursor.x, -editor.CODE_TOP_INSET - cursor.rawLine*editor.LINE_SPACING)
BlzFrameSetVisible(cursorFrame, editBoxFocused and cursor.rawLine >= 1 and cursor.rawLine <= editor.MAX_LINES_ON_SCREEN)
end
if hasError[currentTab] then
BlzFrameSetPoint(errorHighlight, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET, -editor.CODE_TOP_INSET - editor.LINE_SPACING*(errorLineNumber - lineNumberOffset[currentTab]))
BlzFrameSetPoint(errorHighlight, FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPRIGHT, -editor.CODE_RIGHT_INSET, -editor.CODE_TOP_INSET - editor.LINE_SPACING*(errorLineNumber - lineNumberOffset[currentTab] - 1))
end
BlzFrameSetVisible(errorHighlight, hasError[currentTab] and errorLineNumber - lineNumberOffset[currentTab] >= 1 and errorLineNumber - lineNumberOffset[currentTab] <= editor.MAX_LINES_ON_SCREEN)
if selection.hasSelection then
SetSelection(GetTextWidth(sub(codeLines[currentTab][step[currentTab]][selection.endLine], 1, selection.endPos)))
end
if selection.hasSelection and selection.startLine == selection.endLine then
SearchForDuplicates()
end
if brackets.lines[1] then
local rawLine = brackets.lines[1] - lineNumberOffset[currentTab]
if rawLine >= 1 and rawLine <= editor.MAX_LINES_ON_SCREEN then
BlzFrameSetVisible(brackets.highlights[1], true)
BlzFrameSetPoint(brackets.highlights[1], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(codeLines[currentTab][step[currentTab]][brackets.lines[1]], 1, brackets.pos[1] - 1)), -editor.CODE_TOP_INSET - rawLine*editor.LINE_SPACING)
BlzFrameSetPoint(brackets.highlights[1], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(codeLines[currentTab][step[currentTab]][brackets.lines[1]], 1, brackets.pos[1])), -editor.CODE_TOP_INSET - (rawLine - 1)*editor.LINE_SPACING)
else
BlzFrameSetVisible(brackets.highlights[1], false)
end
rawLine = brackets.lines[2] - lineNumberOffset[currentTab]
if rawLine >= 1 and rawLine <= editor.MAX_LINES_ON_SCREEN then
BlzFrameSetVisible(brackets.highlights[2], true)
BlzFrameSetPoint(brackets.highlights[2], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(codeLines[currentTab][step[currentTab]][brackets.lines[2]], 1, brackets.pos[2] - 1)), -editor.CODE_TOP_INSET - rawLine*editor.LINE_SPACING)
BlzFrameSetPoint(brackets.highlights[2], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(codeLines[currentTab][step[currentTab]][brackets.lines[2]], 1, brackets.pos[2])), -editor.CODE_TOP_INSET - (rawLine - 1)*editor.LINE_SPACING)
else
BlzFrameSetVisible(brackets.highlights[2], false)
end
end
if BlzFrameIsVisible(searchBar.parent) then
RenderSearchHighlights()
end
end
end
local function InsertCodeLine()
BlzFrameSetFocus(enterBox, true)
BlzFrameSetVisible(helperFrame, false)
BlzFrameSetVisible(variableViewer.parent, false)
IncrementStep()
local currentLines = codeLines[currentTab][step[currentTab]]
local openTwoLines = brackets.IS_BRACKET[sub(currentLines[cursor.adjustedLine], cursor.pos, cursor.pos)] and brackets.CORRESPONDING_BRACKET[sub(currentLines[cursor.adjustedLine], cursor.pos, cursor.pos)] == sub(currentLines[cursor.adjustedLine], cursor.pos + 1, cursor.pos + 1)
local shift = openTwoLines and 2 or 1
ShiftLines(shift, cursor.adjustedLine + 1)
currentLines[cursor.adjustedLine + shift] = sub(currentLines[cursor.adjustedLine], cursor.pos + 1)
coloredCodeLines[currentTab][step[currentTab]][cursor.adjustedLine + shift] = GetColoredText(currentLines[cursor.adjustedLine + shift], cursor.adjustedLine + shift, currentTab)
currentLines[cursor.adjustedLine] = sub(currentLines[cursor.adjustedLine], 1, cursor.pos)
coloredCodeLines[currentTab][step[currentTab]][cursor.adjustedLine] = GetColoredText(currentLines[cursor.adjustedLine], cursor.adjustedLine, currentTab)
if shift == 2 then
currentLines[cursor.adjustedLine + 1] = ""
coloredCodeLines[currentTab][step[currentTab]][cursor.adjustedLine + 1] = ""
end
lines.debugState[currentTab][cursor.adjustedLine + shift] = 0
BlzFrameSetTexture(BlzFrameGetChild(stopButtons[cursor.rawLine + shift], 0), "transparentmask.blp", 0, true)
local lastLine = currentLines[cursor.adjustedLine]
local __, numSpaces = find(lastLine, "^\x25s*")
if LineOpensScope(lastLine) then
numSpaces = numSpaces + 4
end
if currentLines[cursor.adjustedLine + 1]:match("^\x25s*end")
or currentLines[cursor.adjustedLine + 1]:match("^\x25s*}")
or currentLines[cursor.adjustedLine + 1]:match("^\x25s*\x25)")
or currentLines[cursor.adjustedLine + 1]:match("^\x25s*until")
or currentLines[cursor.adjustedLine + 1]:match("^\x25s*else") then
numSpaces = math.max(0, numSpaces - 4)
end
cursor.rawLine = cursor.rawLine + 1
cursor.adjustedLine = cursor.adjustedLine + 1
SetCursorPos(numSpaces)
highestNonEmptyLine[currentTab][step[currentTab]] = highestNonEmptyLine[currentTab][step[currentTab]] + shift
for __ = 1, numSpaces do
currentLines[cursor.adjustedLine] = " " .. currentLines[cursor.adjustedLine]
end
coloredCodeLines[currentTab][step[currentTab]][cursor.adjustedLine] = GetColoredText(currentLines[cursor.adjustedLine], cursor.adjustedLine, currentTab)
if shift == 2 then
for __ = 1, numSpaces - 4 do
currentLines[cursor.adjustedLine + 1] = " " .. currentLines[cursor.adjustedLine + 1]
end
coloredCodeLines[currentTab][step[currentTab]][cursor.adjustedLine + 1] = GetColoredText(currentLines[cursor.adjustedLine + 1], cursor.adjustedLine + 1, currentTab)
end
SetCursorX(sub(currentLines[cursor.adjustedLine], 1, cursor.pos))
BlzFrameSetMinMaxValue(codeScroller, 0, highestNonEmptyLine[currentTab][step[currentTab]])
SetLineOffset(lineNumberOffset[currentTab])
if not JumpWindow(cursor.adjustedLine) then
UpdateAllLines()
end
SetLocalList(cursor.adjustedLine)
end
--======================================================================================================================
--Variable Viewer
--======================================================================================================================
Value2String = function(object, tableKey, maxLength, showFourCC)
local returnstr
if object == WSDebug.Nil then
returnstr = "nil"
elseif type(object) == "boolean" or object == nil then
returnstr = tostring(object)
elseif type(object) == "number" then
if showFourCC and type(object) == "number" and math.type(object) == "integer" and object >= 808464432 then
returnstr = object .. " ('" .. string.pack(">I4", object) .. "')"
else
returnstr = tostring(object)
end
elseif type(object) == "string" then
object = object:gsub("|", "||")
if tableKey then
returnstr = object
else
returnstr = '"' .. sub(object, 1, maxLength - 2) .. '"'
end
elseif IsHandle[object] then
if IsWidget[object] then
if HandleType[object] == "unit" then
local str = string.gsub(tostring(object), "unit: ", "")
if sub(str, 1,1) == "0" then
returnstr = GetUnitName(object) .. ": " .. sub(str, str:len() - 3, str:len())
else
returnstr = str
end
elseif HandleType[object] == "destructable" then
local str = string.gsub(tostring(object), "destructable: ", "")
if sub(str, 1,1) == "0" then
returnstr = GetDestructableName(object) .. ": " .. sub(str, str:len() - 3, str:len())
else
returnstr = str
end
else
local str = string.gsub(tostring(object), "item: ", "")
if sub(str, 1,1) == "0" then
returnstr = GetItemName(object) .. ": " .. sub(str, str:len() - 3, str:len())
else
returnstr = str
end
end
else
local str = tostring(object)
local address = sub(str, (find(str, ":", nil, true) or 0) + 2, str:len())
returnstr = HandleType[object] .. (sub(address, 1,1) == "0" and (": " .. sub(address, address:len() - 3, address:len())) or (": " .. address))
end
elseif type(object) == "table" and rawget(object, "__name") then
local str = string.gsub(tostring(object), object.__name .. ": ", "")
str = string.sub(str, #str - 3, #str)
returnstr = object.__name .. " " .. str
else
local str = tostring(object)
local colonPos = find(str, ":")
if sub(str, colonPos + 2, colonPos + 2) == "0" then
returnstr = sub(str, 1, colonPos) .. " " .. sub(str, #str - 3, #str)
else
returnstr = str
end
end
if tableKey and type(object) ~= "string" then
return "[" .. sub(returnstr, 1, maxLength - 2) .. "]"
else
return sub(returnstr, 1, maxLength)
end
end
local function SortTableKeys(a, b)
local typeA, typeB = type(a), type(b)
if typeA == typeB then
return tostring(a) < tostring(b)
elseif typeA == "number" or typeB == "number" then
return typeA == "number"
else
return typeA < typeB
end
end
local function ViewVariable()
local frame = BlzGetTriggerFrame()
BlzFrameSetEnable(frame, false)
BlzFrameSetEnable(frame, true)
local whichVar = variableViewer.nameFromFrame[frame]
local inRoot = #viewedVariableTrace == 0
if inRoot then
local level = visibleVarsOfLine[currentTab][lastViewedLineNumber[currentTab] or lastLineNumber[currentTab]][whichVar]
viewedVariable = whichVar
viewedVariableTrace[1] = whichVar
viewedValueTrace[1] = WSDebug.VarTable[currentTab][level][whichVar]
else
viewedVariableTrace[#viewedVariableTrace + 1] = whichVar
viewedValueTrace[#viewedValueTrace + 1] = viewedValueTrace[#viewedValueTrace][whichVar]
end
UpdateVariableViewer(lastViewedLineNumber[currentTab] or lastLineNumber[currentTab])
if variableViewer.linkedWidget[frame] then
if FCL_Anchor then
anchor = FCL_Anchor[GetLocalPlayer()]
end
SetCameraPosition(GetWidgetX(variableViewer.linkedWidget[frame]), GetWidgetY(variableViewer.linkedWidget[frame]))
end
end
local function ViewFunctionParam()
local frame = BlzGetTriggerFrame()
local whichParam = indexFromFrame[frame]
viewedVariable = "Function Parameter " .. whichParam
viewedVariableTrace[1] = "Function Parameter " .. whichParam
viewedValueTrace[1] = functionParams[whichParam]
viewingParamsForFunctionCounter = functionCallCounter
UpdateVariableViewer(lastViewedLineNumber[currentTab] or lastLineNumber[currentTab])
if IsWidget[functionParams[whichParam]] then
if FCL_Anchor then
anchor = FCL_Anchor[GetLocalPlayer()]
end
SetCameraPosition(GetWidgetX(functionParams[whichParam]), GetWidgetY(functionParams[whichParam]))
end
end
UpdateVariableViewer = function(lineNumber)
if not codeEditorParent then
return
end
local varNames = {}
local varValues = {}
local inRoot = #viewedVariableTrace == 0
local lastVariable = viewedVariableTrace[#viewedVariableTrace]
local lastValue = viewedValueTrace[#viewedValueTrace]
BlzFrameSetVisible(variableViewer.parent, true)
local oldNumVisible = numVisibleVars
numVisibleVars = 0
if inRoot then
viewingParamsForFunctionCounter = nil
local visibleVars = visibleVarsOfLine[currentTab][lineNumber]
for var, level in pairs(visibleVars) do
if (not flags.hideConstants or #var == 1 or find(var, "\x25l")) and (not flags.hideGlobals or level ~= -1) then
varNames[#varNames + 1] = var
varValues[var] = WSDebug.VarTable[currentTab][level][var]
varLevelExecution[currentTab][var] = level
numVisibleVars = numVisibleVars + 1
end
end
table.sort(varNames)
else
local viewedVariableIsVisible = viewingParamsForFunctionCounter and viewingParamsForFunctionCounter == functionCallCounter
if not viewedVariableIsVisible then
for var, __ in pairs(visibleVarsOfLine[currentTab][lineNumber]) do
if var == viewedVariable then
viewedVariableIsVisible = true
break
end
end
end
if viewedVariableIsVisible then
if type(lastValue) == "table" then
if lastValue == WSDebug.Nil then
varNames[1] = "nil"
numVisibleVars = 1
else
for var, value in pairs(lastValue) do
varNames[#varNames + 1] = var
varValues[var] = value
numVisibleVars = numVisibleVars + 1
end
if numVisibleVars == 0 then
varNames[1] = "|cffaaaaaaEmpty Table|r"
numVisibleVars = 1
else
table.sort(varNames, SortTableKeys)
end
end
else
numVisibleVars = 1
varNames[1] = lastVariable
varValues[1] = lastValue
end
else
numVisibleVars = 0
end
end
numVisibleVars = math.min(editor.MAX_LINES_ON_SCREEN, numVisibleVars)
if not variableViewerIsExpanded then
return
end
for i = numVisibleVars + 1, oldNumVisible do
BlzFrameSetVisible(variableViewer.frames[i], false)
BlzFrameSetVisible(variableViewer.valueFrames[i], false)
end
BlzFrameSetVisible(buttons.back, not inRoot)
local scale = variableViewer.TEXT_SCALE
for i = oldNumVisible + 1, numVisibleVars do
if not variableViewer.frames[i] then
variableViewer.frames[i] = BlzCreateFrameByType("TEXT", "", variableViewer.parent, "", 0)
BlzFrameSetPoint(variableViewer.frames[i], FRAMEPOINT_BOTTOMLEFT, variableViewer.parent, FRAMEPOINT_TOPLEFT, variableViewer.LINE_HORIZONTAL_INSET/scale, (-variableViewer.LINE_VERTICAL_INSET - i*editor.LINE_SPACING)/scale)
BlzFrameSetPoint(variableViewer.frames[i], FRAMEPOINT_TOPRIGHT, variableViewer.parent, FRAMEPOINT_TOPRIGHT, -variableViewer.LINE_HORIZONTAL_INSET/scale, (-variableViewer.LINE_VERTICAL_INSET - (i - 1)*editor.LINE_SPACING)/scale)
BlzFrameSetScale(variableViewer.frames[i], scale)
BlzFrameSetTextAlignment(variableViewer.frames[i], TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_LEFT)
indexFromFrame[variableViewer.frames[i]] = i
BlzFrameSetEnable(variableViewer.frames[i], false)
variableViewer.valueFrames[i] = BlzCreateFrame("VariableViewerButton", variableViewer.parent, 0, 0)
variableViewer.textFrames[variableViewer.valueFrames[i]] = BlzFrameGetChild(variableViewer.valueFrames[i], 0)
BlzFrameSetEnable(variableViewer.textFrames[variableViewer.valueFrames[i]], false)
BlzFrameSetPoint(variableViewer.valueFrames[i], FRAMEPOINT_BOTTOMLEFT, variableViewer.parent, FRAMEPOINT_TOPLEFT, variableViewer.LINE_HORIZONTAL_INSET/scale, (-variableViewer.LINE_VERTICAL_INSET - i*editor.LINE_SPACING)/scale)
BlzFrameSetPoint(variableViewer.valueFrames[i], FRAMEPOINT_TOPRIGHT, variableViewer.parent, FRAMEPOINT_TOPRIGHT, -variableViewer.LINE_HORIZONTAL_INSET/scale, (-variableViewer.LINE_VERTICAL_INSET - (i - 1)*editor.LINE_SPACING)/scale)
BlzFrameSetScale(variableViewer.valueFrames[i], scale)
BlzFrameSetTextAlignment(variableViewer.textFrames[variableViewer.valueFrames[i]], TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_RIGHT)
BlzFrameSetAllPoints(variableViewer.textFrames[variableViewer.valueFrames[i]], variableViewer.valueFrames[i])
indexFromFrame[variableViewer.valueFrames[i]] = i
local trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, variableViewer.valueFrames[i], FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, ViewVariable)
else
BlzFrameSetVisible(variableViewer.frames[i], true)
BlzFrameSetVisible(variableViewer.valueFrames[i], true)
end
end
BlzFrameSetSize(variableViewer.parent, variableViewer.WIDTH, editor.LINE_SPACING + variableViewer.LINE_VERTICAL_INSET + variableViewer.BOTTOM_SPACE + math.max(numVisibleVars - 1, 0)*editor.LINE_SPACING)
if inRoot then
for i = 1, math.min(#varNames, editor.MAX_LINES_ON_SCREEN) do
BlzFrameSetText(variableViewer.frames[i], (varLevelExecution[currentTab][varNames[i]] == -1 and variableViewer.GLOBAL_COLOR or variableViewer.LOCAL_COLOR) .. varNames[i]:sub(1, variableViewer.VARIABLE_MAX_STRING_LENGTH) .. "|r")
BlzFrameSetText(variableViewer.textFrames[variableViewer.valueFrames[i]], (varLevelExecution[currentTab][varNames[i]] == -1 and variableViewer.GLOBAL_COLOR or variableViewer.LOCAL_COLOR) .. Value2String(varValues[varNames[i]], false, variableViewer.VARIABLE_MAX_STRING_LENGTH))
BlzFrameSetEnable(variableViewer.valueFrames[i], true)
variableViewer.nameFromFrame[variableViewer.valueFrames[i]] = varNames[i]
if varValues[varNames[i]] and IsWidget[varValues[varNames[i]]] then
variableViewer.linkedWidget[variableViewer.valueFrames[i]] = varValues[varNames[i]]
else
variableViewer.linkedWidget[variableViewer.valueFrames[i]] = nil
end
end
BlzFrameSetText(variableViewerTitle, "|cffffcc00Variables|r")
elseif type(lastValue) == "table" then
for i = 1, #varNames do
if varNames[i] == "|cffaaaaaaEmpty Table|r" or varNames[i] == "nil" then
BlzFrameSetText(variableViewer.frames[i], varNames[i])
BlzFrameSetText(variableViewer.textFrames[variableViewer.valueFrames[i]], "")
BlzFrameSetEnable(variableViewer.valueFrames[i], false)
else
BlzFrameSetText(variableViewer.frames[i], variableViewer.LOCAL_COLOR .. Value2String(varNames[i], true, variableViewer.VARIABLE_MAX_STRING_LENGTH) .. "|r")
BlzFrameSetText(variableViewer.textFrames[variableViewer.valueFrames[i]], variableViewer.LOCAL_COLOR .. Value2String(varValues[varNames[i]], false, variableViewer.VARIABLE_MAX_STRING_LENGTH) .. "|r")
BlzFrameSetEnable(variableViewer.valueFrames[i], true)
end
variableViewer.nameFromFrame[variableViewer.valueFrames[i]] = varNames[i]
if varValues[varNames[i]] and IsWidget[varValues[varNames[i]]] then
variableViewer.linkedWidget[variableViewer.valueFrames[i]] = varValues[varNames[i]]
else
variableViewer.linkedWidget[variableViewer.valueFrames[i]] = nil
end
end
BlzFrameSetText(variableViewerTitle, "|cffffcc00" .. tostring(lastVariable) .. "|r")
else
BlzFrameSetText(variableViewer.textFrames[variableViewer.valueFrames[1]], "")
BlzFrameSetText(variableViewer.frames[1], Debug and Debug.original and Debug.original.tostring(varValues[1]) or tostring(varValues[1]))
BlzFrameSetEnable(variableViewer.valueFrames[1], false)
BlzFrameClearAllPoints(variableViewer.frames[1])
BlzFrameSetPoint(variableViewer.frames[1], FRAMEPOINT_TOPLEFT, variableViewer.parent, FRAMEPOINT_TOPLEFT, variableViewer.LINE_HORIZONTAL_INSET/scale, -variableViewer.LINE_VERTICAL_INSET/scale)
BlzFrameSetSize(variableViewer.frames[1], variableViewer.WIDTH - 2*variableViewer.LINE_HORIZONTAL_INSET, 0)
BlzFrameSetSize(variableViewer.parent, variableViewer.WIDTH, BlzFrameGetHeight(variableViewer.frames[1]) + variableViewer.LINE_VERTICAL_INSET + variableViewer.BOTTOM_SPACE)
BlzFrameSetText(variableViewerTitle, "|cffffcc00" .. tostring(lastVariable) .. "|r")
end
if inRoot and (lastViewedFunctionCall or lastFunctionCall) then
BlzFrameSetPoint(variableViewer.functionFrames[1], FRAMEPOINT_TOPLEFT, variableViewer.parent, FRAMEPOINT_TOPLEFT, variableViewer.LINE_HORIZONTAL_INSET/scale, (-variableViewer.LINE_VERTICAL_INSET - numVisibleVars*editor.LINE_SPACING - variableViewer.FUNCTION_CALL_SPACING)/scale)
BlzFrameSetText(variableViewer.functionFrames[2], editor.NATIVE_COLOR .. (lastViewedFunctionCall or lastFunctionCall) .. "|r(")
BlzFrameSetSize(variableViewer.functionFrames[2], 0, 0)
for i = 1, #functionParams do
if not variableViewer.functionParamFrames[i] then
variableViewer.functionParamFrames[i] = BlzCreateFrame("VariableViewerButton", variableViewer.parent, 0, 0)
variableViewer.functionTextFrames[i] = BlzFrameGetChild(variableViewer.functionParamFrames[i], 0)
BlzFrameSetEnable(variableViewer.functionTextFrames[i], false)
if i == 1 then
BlzFrameSetPoint(variableViewer.functionParamFrames[i], FRAMEPOINT_BOTTOMLEFT, variableViewer.functionFrames[2], FRAMEPOINT_BOTTOMLEFT, 0, -editor.LINE_SPACING/scale)
BlzFrameSetPoint(variableViewer.functionParamFrames[i], FRAMEPOINT_TOPRIGHT, variableViewer.functionFrames[2], FRAMEPOINT_TOPLEFT, (variableViewer.WIDTH - 2*variableViewer.LINE_HORIZONTAL_INSET)/scale, -editor.LINE_SPACING/scale)
else
BlzFrameSetPoint(variableViewer.functionParamFrames[i], FRAMEPOINT_BOTTOMLEFT, variableViewer.functionParamFrames[i - 1], FRAMEPOINT_BOTTOMLEFT, 0, -editor.LINE_SPACING/scale)
BlzFrameSetPoint(variableViewer.functionParamFrames[i], FRAMEPOINT_TOPRIGHT, variableViewer.functionParamFrames[i - 1], FRAMEPOINT_TOPRIGHT, 0, -editor.LINE_SPACING/scale)
end
BlzFrameSetScale(variableViewer.functionParamFrames[i], scale)
BlzFrameSetTextAlignment(variableViewer.functionTextFrames[i], TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_LEFT)
BlzFrameSetAllPoints(variableViewer.functionTextFrames[i], variableViewer.functionParamFrames[i])
indexFromFrame[variableViewer.functionParamFrames[i]] = i
local trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, variableViewer.functionParamFrames[i], FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, ViewFunctionParam)
else
BlzFrameSetVisible(variableViewer.functionParamFrames[i], true)
end
if i < #functionParams then
BlzFrameSetText(variableViewer.functionTextFrames[i], " " .. Value2String(functionParams[i], false, variableViewer.FUNCTION_PARAM_MAX_STRING_LENGTH, true) .. ",")
else
BlzFrameSetText(variableViewer.functionTextFrames[i], " " .. Value2String(functionParams[i], false, variableViewer.FUNCTION_PARAM_MAX_STRING_LENGTH, true))
end
BlzFrameSetPoint(variableViewer.functionFrames[3], FRAMEPOINT_TOPLEFT, variableViewer.functionParamFrames[#functionParams], FRAMEPOINT_TOPLEFT, 0, -editor.LINE_SPACING/scale)
end
if #functionParams == 0 then
BlzFrameSetPoint(variableViewer.functionFrames[3], FRAMEPOINT_TOPLEFT, variableViewer.functionFrames[2], FRAMEPOINT_TOPLEFT, 0, -editor.LINE_SPACING/scale)
end
for i = 1, #variableViewer.functionFrames do
BlzFrameSetVisible(variableViewer.functionFrames[i], true)
end
for i = #functionParams + 1, #variableViewer.functionParamFrames do
BlzFrameSetVisible(variableViewer.functionParamFrames[i], false)
end
BlzFrameSetSize(variableViewer.parent, variableViewer.WIDTH, variableViewer.LINE_VERTICAL_INSET + variableViewer.BOTTOM_SPACE + numVisibleVars*editor.LINE_SPACING + 2*variableViewer.FUNCTION_CALL_SPACING + (#functionParams + 3)*editor.LINE_SPACING)
else
for i = 1, #variableViewer.functionFrames do
BlzFrameSetVisible(variableViewer.functionFrames[i], false)
end
for i = 1, #variableViewer.functionParamFrames do
BlzFrameSetVisible(variableViewer.functionParamFrames[i], false)
end
end
end
local function GoBack()
BlzFrameSetEnable(buttons.back, false)
BlzFrameSetEnable(buttons.back, true)
viewedVariableTrace[#viewedVariableTrace] = nil
viewedValueTrace[#viewedValueTrace] = nil
UpdateVariableViewer(lastViewedLineNumber[currentTab] or lastLineNumber[currentTab])
if anchor then
FCL_Lock(anchor)
end
local s = CreateSound("Sound\\Interface\\BigButtonClick.flac" , false, false, false, 10, 10, "DefaultEAXON")
SetSoundChannel(s, 0)
StartSound(s)
KillSoundWhenDone(s)
end
--======================================================================================================================
--Tab Navigation
--======================================================================================================================
SwitchToTab = function(whichTab)
whichTab = type(whichTab) == "number" and tabNames[whichTab] or whichTab
if not tabNavigator.frames[whichTab] then
return
end
if selection.hasSelection then
HideSelection()
end
BlzFrameSetAlpha(tabNavigator.highlights[currentTab], 255)
BlzFrameSetText(tabNavigator.titles[currentTab], editor.TAB_NAVIGATOR_UNSELECTED_COLOR .. currentTab .. "|r")
local oldTab = currentTab
currentTab = whichTab
BlzFrameSetAlpha(tabNavigator.highlights[currentTab], 0)
BlzFrameSetText(tabNavigator.titles[currentTab], editor.TAB_NAVIGATOR_SELECTED_COLOR .. currentTab .. "|r")
BlzFrameSetMinMaxValue(codeScroller, 0, highestNonEmptyLine[currentTab][step[currentTab]])
SetLineOffset(lineNumberOffset[currentTab])
UpdateAllLines()
if BlzFrameIsVisible(searchBar.parent) then
FindSearchItems()
RenderSearchHighlights()
end
BlzFrameSetEnable(buttons.redo, step[currentTab] < maxRedo[currentTab])
BlzFrameSetEnable(buttons.undo, step[currentTab] > 1)
if currentTab == executionTab then
BlzFrameSetVisible(currentLineHighlight, hasCurrentLineHighlight)
if hasCurrentLineHighlight then
SetCurrentLine()
end
BlzFrameSetVisible(errorHighlight, hasError[currentTab] and errorLineNumber - lineNumberOffset[currentTab] >= 1 and errorLineNumber - lineNumberOffset[currentTab] <= editor.MAX_LINES_ON_SCREEN)
else
BlzFrameSetVisible(currentLineHighlight, false)
BlzFrameSetVisible(errorHighlight, false)
end
for i = 1, editor.MAX_LINES_ON_SCREEN do
BlzFrameSetText(lineNumbers[i], "|cff999999" .. math.tointeger(i + lineNumberOffset[currentTab]) .. "|r")
if lines.debugState[currentTab][i + lineNumberOffset[currentTab]] ~= lines.debugState[oldTab][i + lineNumberOffset[oldTab]] then
BlzFrameSetTexture(BlzFrameGetChild(stopButtons[i], 0), LINE_STATE_ICONS[lines.debugState[currentTab][i + lineNumberOffset[currentTab]]], 0, true)
end
end
end
local function ClickTabButton()
local whichFrame = BlzGetTriggerFrame()
BlzFrameSetEnable(whichFrame, false)
BlzFrameSetEnable(whichFrame, true)
if tabNumberFromFrame[whichFrame] ~= tabNumber[currentTab] then
SwitchToTab(tabNumberFromFrame[whichFrame])
end
end
local function AdjustTabWidths()
local spaceRequired = 0
for i = 1, numTabs do
spaceRequired = spaceRequired + tabNavigator.widths[tabNames[i]]
end
local spaceAlloted = (isExpanded and editor.EXPANDED_WIDTH or editor.COLLAPSED_WIDTH) + numTabs*tabNavigator.OVERLAP - tabNavigator.HEIGHT
if spaceRequired > spaceAlloted then
for i = 1, numTabs do
BlzFrameSetSize(tabNavigator.frames[tabNames[i]], tabNavigator.widths[tabNames[i]]*spaceAlloted/spaceRequired, tabNavigator.HEIGHT)
if tabNavigator.widths[tabNames[i]] > tabNavigator.WIDTH then
BlzFrameSetScale(tabNavigator.titles[tabNames[i]], spaceAlloted/spaceRequired)
else
BlzFrameSetScale(tabNavigator.titles[tabNames[i]], 1)
end
end
end
end
local function AddTab(tabName)
numTabs = numTabs + 1
tabNavigator.frames[tabName] = BlzCreateFrame("TabNavigator", codeEditorParent, 0, 0)
tabNavigator.titles[tabName] = BlzFrameGetChild(tabNavigator.frames[tabName], 2)
tabNavigator.highlights[tabName] = BlzFrameGetChild(tabNavigator.frames[tabName], 3)
BlzFrameSetText(tabNavigator.titles[tabName], editor.TAB_NAVIGATOR_UNSELECTED_COLOR .. tabName .. "|r")
BlzFrameSetSize(tabNavigator.titles[tabName], 0, 0)
local width = BlzFrameGetWidth(tabNavigator.titles[tabName])
tabNavigator.widths[tabName] = math.max(tabNavigator.WIDTH, width + 2*tabNavigator.TEXT_INSET)
BlzFrameSetAllPoints(tabNavigator.titles[tabName], tabNavigator.frames[tabName])
BlzFrameSetTextAlignment(tabNavigator.titles[tabName], TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_CENTER)
BlzFrameSetEnable(tabNavigator.titles[tabName], false)
BlzFrameSetSize(tabNavigator.frames[tabName], tabNavigator.widths[tabName], tabNavigator.HEIGHT)
if numTabs == 1 then
BlzFrameSetPoint(tabNavigator.frames[tabName], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, 0, tabNavigator.VERTICAL_SHIFT)
else
BlzFrameSetPoint(tabNavigator.frames[tabName], FRAMEPOINT_BOTTOMLEFT, tabNavigator.frames[tabNames[numTabs - 1]], FRAMEPOINT_BOTTOMRIGHT, -tabNavigator.OVERLAP, 0)
end
BlzFrameSetAllPoints(BlzFrameGetChild(tabNavigator.frames[tabName], 1), tabNavigator.frames[tabName])
BlzFrameSetAlpha(BlzFrameGetChild(tabNavigator.frames[tabName], 1), editor.BLACK_BACKDROP_ALPHA)
tabNames[numTabs] = tabName
tabNumber[tabName] = numTabs
tabNumberFromFrame[tabNavigator.frames[tabNames[numTabs]]] = numTabs
local closeButton = BlzCreateFrame("CloseEditorButton", tabNavigator.frames[tabNames[numTabs]], 0, 0)
BlzFrameSetPoint(closeButton, FRAMEPOINT_BOTTOMLEFT, tabNavigator.frames[tabNames[numTabs]], FRAMEPOINT_TOPRIGHT, -tabNavigator.CLOSE_BUTTON_INSET - tabNavigator.CLOSE_BUTTON_SIZE, -tabNavigator.CLOSE_BUTTON_INSET - tabNavigator.CLOSE_BUTTON_SIZE)
BlzFrameSetPoint(closeButton, FRAMEPOINT_TOPRIGHT, tabNavigator.frames[tabNames[numTabs]], FRAMEPOINT_TOPRIGHT, -tabNavigator.CLOSE_BUTTON_INSET, -tabNavigator.CLOSE_BUTTON_INSET)
local icon = BlzFrameGetChild(closeButton, 0)
local iconClicked = BlzFrameGetChild(closeButton, 1)
local iconHighlight = BlzFrameGetChild(closeButton, 2)
BlzFrameSetAllPoints(icon, closeButton)
BlzFrameSetTexture(icon, "closeEditor.blp", 0, true)
BlzFrameSetTexture(iconClicked, "closeEditor.blp", 0, true)
BlzFrameClearAllPoints(iconHighlight)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_BOTTOMLEFT, closeButton, FRAMEPOINT_BOTTOMLEFT, 0.00375, 0.00375)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_TOPRIGHT, closeButton, FRAMEPOINT_TOPRIGHT, -0.00375, -0.00375)
BlzFrameSetVisible(closeButton, false)
local trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, closeButton, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, CloseTab)
tabNumberFromFrame[closeButton] = numTabs
tabNavigator.closeButtons[tabNames[numTabs]] = closeButton
AdjustTabWidths()
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, tabNavigator.frames[tabNames[numTabs]], FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, ClickTabButton)
BlzFrameSetPoint(addTabFrame, FRAMEPOINT_BOTTOMLEFT, tabNavigator.frames[tabNames[numTabs]], FRAMEPOINT_BOTTOMRIGHT, -tabNavigator.OVERLAP, 0)
SwitchToTab(numTabs)
end
CloseTab = function()
local index = tabNumberFromFrame[BlzGetTriggerFrame()]
local tabName = tabNames[index]
BlzFrameSetVisible(tabNavigator.frames[tabName], false)
if index == numTabs then
BlzFrameSetPoint(addTabFrame, FRAMEPOINT_BOTTOMLEFT, tabNavigator.frames[tabNames[index - 1]], FRAMEPOINT_BOTTOMRIGHT, -tabNavigator.OVERLAP, 0)
elseif index == 1 then
BlzFrameSetPoint(tabNavigator.frames[tabNames[index + 1]], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, 0, tabNavigator.VERTICAL_SHIFT)
else
BlzFrameSetPoint(tabNavigator.frames[tabNames[index + 1]], FRAMEPOINT_BOTTOMLEFT, tabNavigator.frames[tabNames[index - 1]], FRAMEPOINT_BOTTOMRIGHT, -tabNavigator.OVERLAP, 0)
end
for i = index + 1, numTabs do
tabNumber[tabNames[i]] = i - 1
tabNumberFromFrame[tabNavigator.frames[tabNames[i]]] = i - 1
tabNumberFromFrame[tabNavigator.closeButtons[tabNames[i]]] = i - 1
tabNames[i - 1] = tabNames[i]
end
local currentLines = codeLines[tabName][step[tabName]]
for i = 1, highestNonEmptyLine[tabName][step[tabName]] do
currentLines[i] = ""
coloredCodeLines[tabName][step[tabName]][i] = ""
end
highestNonEmptyLine[tabName][step[tabName]] = 0
tabNames[numTabs] = nil
tabNumber[tabName] = nil
if numTabs == 1 then
numTabs = numTabs - 1
AddTab("Main")
SwitchToTab("Main")
else
if currentTab == tabName then
if index == numTabs then
SwitchToTab(tabNames[index - 1])
else
SwitchToTab(tabNames[index])
end
end
numTabs = numTabs - 1
AdjustTabWidths()
end
end
--======================================================================================================================
--Code Execution
--======================================================================================================================
HandleError = function(error)
hasError[executionTab] = true
if not BlzFrameIsVisible(codeEditorParent) then
EnableEditor(user)
end
if currentTab ~= executionTab then
SwitchToTab(executionTab)
end
BlzFrameSetVisible(errorHighlight, true)
errorLineNumber = lastLineNumber[executionTab] > 0 and lastLineNumber[executionTab] or error:match("\"\x25]:(\x25d+):")
local traceback = Debug.traceback()
if find(traceback, '\x25[string "WSCode') then
traceback = sub(traceback, select(2, find(traceback, '\x25[string "WSCode')))
local nextSubPos = find(traceback, "<\x25- ")
if nextSubPos then
traceback = sub(traceback, nextSubPos + 3)
end
traceback = ". Traceback (most recent call first): " .. (traceback or "")
else
traceback = ""
end
local str = (executionTab or "Unknown") .. ", line " .. errorLineNumber .. ": " .. error:gsub("\x25b[]", "", 1):gsub("\x25b::", "", 1) .. traceback
if Debug and Debug.errorHandler then
Debug.errorHandler(str)
else
print("|cffff5555ERROR at " .. str .. "|r")
end
Debug.data.firstError = Debug.data.firstError or str
JumpWindow(errorLineNumber, true, currentTab ~= executionTab)
BlzFrameSetPoint(errorHighlight, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET, -editor.CODE_TOP_INSET - editor.LINE_SPACING*(errorLineNumber - lineNumberOffset[currentTab]))
BlzFrameSetPoint(errorHighlight, FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPRIGHT, -editor.CODE_RIGHT_INSET, -editor.CODE_TOP_INSET - editor.LINE_SPACING*(errorLineNumber - lineNumberOffset[currentTab] - 1))
UpdateVariableViewer(errorLineNumber)
end
local function Continue()
if WSDebug.Coroutine[currentTab] and coroutine.status(WSDebug.Coroutine[currentTab]) == "suspended" and not coroutineIsDead[WSDebug.Coroutine[currentTab]] then
local success, error = coroutine.resume(WSDebug.Coroutine[currentTab])
if success == false then
HandleError(error)
end
end
end
local function NextLine()
lineByLine = true
Continue()
end
local function ContinueExecuting()
lineByLine = false
Continue()
end
local function CompileFunction(rawCode, settings)
------------------------------------------------------------------------------------------------------------------------
--Initialization
------------------------------------------------------------------------------------------------------------------------
lineByLine = false
currentStop = 0
hasCurrentLineHighlight = false
executionTab = settings.tab
lastLineNumber[executionTab] = 0
lastViewedLineNumber[executionTab] = nil
errorLineNumber = 0
hasError[executionTab] = false
timeOfLastBreakpoint = timeElapsed
lastFunctionCall = nil
lastViewedFunctionCall = nil
if buttons.haltALICE then
BlzFrameSetEnable(buttons.haltALICE, true)
end
WSDebug.GenerationCounter[executionTab] = WSDebug.GenerationCounter[executionTab] + 1
for index, __ in pairs(varTable) do
for var, __ in pairs(varTable[index]) do
varTable[index][var] = nil
end
end
for index, __ in pairs(visibleVarsOfLine[executionTab]) do
for var, __ in pairs(visibleVarsOfLine[executionTab][index]) do
visibleVarsOfLine[executionTab][index][var] = nil
end
end
if settings.runInit then
for i = 1, 7 do
for j = 1, #WSDebug.FunctionsToInit[i] do
WSDebug.FunctionsToInit[i][j] = nil
end
end
end
for i = 1, #viewedVariableTrace do
viewedVariableTrace[i] = nil
viewedValueTrace[i] = nil
end
local success, code, originalFunc = ConvertAndValidateLines(rawCode, settings.duringInit, true)
if not success then
return
end
local length = #code
local word
local currentLineNumber = 1
local beginningOfString
local inComment, inSingleQuoteString, inDoubleQuoteString, inMultilineString, numEqualSigns
local tokens = {}
local lineNumberOfToken = {}
local lineIsPersistent = {}
local lineIsEphemeral = {}
local numTokens = 0
local inPersistents = false
------------------------------------------------------------------------------------------------------------------------
--Tokenization
------------------------------------------------------------------------------------------------------------------------
local pos = 1
while pos <= length do
local oneChar
local twoChars
local beginningOfComment
::begin::
if pos > length then
break
end
oneChar = sub(code, pos, pos)
if oneChar == " " then
pos = find(code, "[\x25S\n]", pos + 1)
if not pos then
break
end
goto begin
elseif oneChar == "\n" then
currentLineNumber = currentLineNumber + 1
if inPersistents then
lineIsPersistent[currentLineNumber] = true
end
if not inMultilineString then
pos = pos + 1
inComment = false
else
pos = find(code, "[\n\x25]]", pos + 1)
if not pos then
break
end
end
goto begin
end
if oneChar == "[" and not inComment and not inSingleQuoteString and not inDoubleQuoteString and not inMultilineString then
local i = pos + 1
numEqualSigns = 0
beginningOfString = pos
while sub(code, i, i) == "=" do
numEqualSigns = numEqualSigns + 1
i = i + 1
end
if sub(code, i, i) == "[" then
inMultilineString = true
pos = find(code, "[\n\x25]]", pos + 1)
if not pos then
break
end
goto begin
else
numTokens = numTokens + 1
tokens[numTokens] = "["
lineNumberOfToken[numTokens] = currentLineNumber
pos = pos + 1
goto begin
end
else
twoChars = sub(code, pos, pos + 1)
if twoChars == "--" and not inComment and not inSingleQuoteString and not inDoubleQuoteString and not inMultilineString then
inComment = true
beginningOfComment = pos
if sub(code, pos, pos + 2) == "--[" then
local i = pos + 3
numEqualSigns = 0
while sub(code, i, i) == "=" do
numEqualSigns = numEqualSigns + 1
i = i + 1
end
if sub(code, i, i) == "[" then
inMultilineString = true
pos = i + 1
else
pos = i
end
pos = find(code, "[\n\x25]]", pos)
if not pos then
break
end
goto begin
else
pos = find(code, "\n", pos + 2)
if not pos then
break
end
local commentString = sub(code, beginningOfComment, pos)
if find(commentString, "@persistent") then
if find(commentString, "@persistents") then
inPersistents = true
end
lineIsPersistent[currentLineNumber] = true
elseif find(commentString, "@endpersistents") then
inPersistents = false
end
if find(commentString, "@ephemeral") then
lineIsEphemeral[currentLineNumber] = true
lineIsPersistent[currentLineNumber] = nil
end
goto begin
end
end
end
if inMultilineString and match(sub(code, pos, pos + 1 + numEqualSigns), "\x25]\x25=*\x25]") then
inMultilineString = false
if not inComment then
numTokens = numTokens + 1
tokens[numTokens] = sub(code, beginningOfString, pos + 1 + numEqualSigns)
lineNumberOfToken[numTokens] = currentLineNumber
end
inComment = false
pos = pos + 2 + numEqualSigns
goto begin
elseif inComment then
if inMultilineString then
pos = find(code, "[\n\x25]]", pos + 1)
if not pos then
break
end
else
pos = find(code, "\n", pos + 1)
if not pos then
break
end
end
goto begin
elseif oneChar == "\"" then
if not inSingleQuoteString and not inMultilineString then
if not inDoubleQuoteString then
beginningOfString = pos
pos = find(code, "\"", pos + 1)
inDoubleQuoteString = true
if not pos then
break
end
goto begin
else
numTokens = numTokens + 1
tokens[numTokens] = sub(code, beginningOfString, pos)
lineNumberOfToken[numTokens] = currentLineNumber
inDoubleQuoteString = false
end
end
pos = pos + 1
goto begin
elseif oneChar == "'" then
if not inDoubleQuoteString and not inMultilineString then
if not inSingleQuoteString then
beginningOfString = pos
pos = find(code, "'", pos + 1)
inSingleQuoteString = true
if not pos then
break
end
goto begin
else
numTokens = numTokens + 1
tokens[numTokens] = sub(code, beginningOfString, pos)
lineNumberOfToken[numTokens] = currentLineNumber
inSingleQuoteString = false
end
end
pos = pos + 1
goto begin
end
if sub(code, pos, pos + 2) == "..." then
numTokens = numTokens + 1
tokens[numTokens] = "..."
lineNumberOfToken[numTokens] = currentLineNumber
pos = pos + 3
goto begin
end
if LUA_TWO_CHAR_OPERATORS[twoChars] then
numTokens = numTokens + 1
tokens[numTokens] = twoChars
lineNumberOfToken[numTokens] = currentLineNumber
pos = pos + 2
goto begin
end
if LUA_ONE_CHAR_OPERATORS[oneChar] then
numTokens = numTokens + 1
tokens[numTokens] = oneChar
lineNumberOfToken[numTokens] = currentLineNumber
pos = pos + 1
goto begin
end
word = match(code, "^[\x25w_.:#]*", pos)
if word then
if find(word, "::") then
numTokens = numTokens + 1
tokens[numTokens] = sub(word, 1, -3)
lineNumberOfToken[numTokens] = currentLineNumber
pos = pos + #word - 2
goto begin
end
numTokens = numTokens + 1
tokens[numTokens] = word
lineNumberOfToken[numTokens] = currentLineNumber
if #word == 0 then
pos = pos + 1
end
pos = pos + #word
goto begin
end
pos = pos + 1
end
tokens[#tokens + 1] = ""
lineNumberOfToken[#tokens] = lineNumberOfToken[#tokens - 1] or 1
------------------------------------------------------------------------------------------------------------------------
--Transpilation
------------------------------------------------------------------------------------------------------------------------
local finalCode = setmetatable({}, {__concat = function(self, str) self[#self + 1] = str return self end})
local executionTabString = "'" .. executionTab .. "'"
if not settings.duringInit then
finalCode = finalCode ..
"local OnInit = setmetatable({}, {__index = function() return function() end end}) " ..
"local Require = setmetatable({}, {__index = function() return function() end end, __call = function() end}) " ..
"local wsdebug_result " ..
"local wsdebug_generationCounter = " .. WSDebug.GenerationCounter[executionTab] .. " " ..
"local wsdebug_executionTab = " .. executionTabString .. " "
else
finalCode = finalCode .. "local wsdebug_result local wsdebug_generationCounter = " .. WSDebug.GenerationCounter[executionTab] .. " local wsdebug_executionTab = " .. executionTabString .. " "
end
local stateTrace = {"root"}
local currentState = stateTrace[1]
local stateOfToken
local beginningOfStatementToken = {}
local beginningOfStatementCode = {}
local currentLevel = 0
local nameOfFunction = {}
local levelOfFunction = setmetatable({}, {__index = function() return -1 end})
local lineNumberOfFunction = {}
local functionIsLocal = {}
local indexOfFunction = {}
local tableTrace = {}
local varName, args
local untracedTables = 0
local functionName
local funcIsLocal
local skip
local token
local currentLine = 1
local function IsEndOfStatement(k)
local firstToken = tokens[k - 1]
local secondToken = tokens[k]
return ((k == #tokens or LUA_CONTROL_FLOW_STATEMENTS[secondToken]) and not LUA_CONTROL_FLOW_STATEMENTS[firstToken]) or (firstToken ~= "local" and firstToken ~= "function" and not LUA_CONTROL_FLOW_STATEMENTS[firstToken] and not LUA_OPERATORS[secondToken] and not LUA_BINARY_OPERATORS[firstToken] and not END_OF_STATEMENT_IGNORED_CHARS[sub(secondToken, 1, 1)])
end
local function InsertCode(whichCode, index)
table.insert(finalCode, index, whichCode)
for i = 1, #stateTrace do
if beginningOfStatementCode[i] and beginningOfStatementCode[i] >= index then
beginningOfStatementCode[i] = beginningOfStatementCode[i] + 1
end
end
end
local function ReduceLevel()
for var, __ in pairs(varTable[currentLevel]) do
varTable[currentLevel][var] = nil
end
currentLevel = currentLevel - 1
end
local function AddVariables(vars, isLocal)
for var in vars:gmatch("[^,]+") do
var = var:gsub("^\x25s*local\x25s+", ""):gsub(" ", "")
if find(var, "[.\x25[:]") == nil then
if isLocal then
varTable[currentLevel][var] = true
else
for i = -1, currentLevel do
if varTable[i][var] then
return
end
end
varTable[-1][var] = true
end
end
end
end
local function SetVariables(lineNumber)
for level = -1, currentLevel do
for var, __ in pairs(varTable[level]) do
visibleVarsOfLine[executionTab][lineNumber][var] = level
end
end
end
if settings.debugMode then
------------------------------------------------------------------------------------------------------------------------
--Debug mode
------------------------------------------------------------------------------------------------------------------------
local i = 1
while i <= #tokens do
token = tokens[i]
while lineNumberOfToken[i] > currentLine do
finalCode = finalCode .. "\n"
currentLine = currentLine + 1
end
if currentState == "assignment" and (i == #tokens or (not LUA_OPERATORS[token] and not LUA_BINARY_OPERATORS[tokens[i - 1]] and not END_OF_STATEMENT_IGNORED_CHARS[sub(token, 1, 1)])) then
local vars = ""
local isLocal
for j = beginningOfStatementToken[#stateTrace - 1], beginningOfStatementToken[#stateTrace] - 2 do
vars = vars .. tokens[j] .. " "
end
for j = beginningOfStatementCode[#stateTrace - 1], beginningOfStatementCode[#stateTrace] - 1 do
finalCode[j] = ""
end
if i == #tokens then
finalCode = finalCode.. token
skip = true
end
isLocal = tokens[beginningOfStatementToken[#stateTrace - 1]] == "local"
local isPersistent = (isLocal and settings.persistUpvalues and stateTrace[#stateTrace - 1] == "root" and not lineIsEphemeral[lineNumberOfToken[beginningOfStatementToken[#stateTrace - 1]]])
or (not settings.duringInit and lineIsPersistent[lineNumberOfToken[beginningOfStatementToken[#stateTrace - 1]]] and (stateTrace[#stateTrace - 1] == "root" or stateTrace[#stateTrace - 1] == "functionbody"))
local firstChar = sub(vars, 1, 1)
if firstChar ~= "." and firstChar ~= ":" and firstChar ~= "[" then
if find(vars, ",") then
local varsAsStrings = vars:gsub("^local ", ""):gsub(" ", ""):gsub("\x25s*([^,]+)\x25s*", "'\x251'")
if isPersistent then
InsertCode(" WSDebug.CheckForStop(" .. lineNumberOfToken[beginningOfStatementToken[#stateTrace - 1]] .. ", wsdebug_executionTab, wsdebug_generationCounter)" .. " wsdebug_result = WSDebug.GetVarEx(wsdebug_executionTab, " .. currentLevel .. ", " .. varsAsStrings .. ", ", beginningOfStatementCode[#stateTrace - 1])
else
InsertCode(" WSDebug.CheckForStop(" .. lineNumberOfToken[beginningOfStatementToken[#stateTrace - 1]] .. ", wsdebug_executionTab, wsdebug_generationCounter)" .. " wsdebug_result = table.pack(", beginningOfStatementCode[#stateTrace - 1])
end
finalCode = finalCode .. ") " .. vars .. "= table.unpack(wsdebug_result)" .. " WSDebug.AssignVars(wsdebug_result, " .. (isLocal and currentLevel or -1) .. ", wsdebug_executionTab, " .. lineNumberOfToken[beginningOfStatementToken[#stateTrace - 1]] .. ", " .. varsAsStrings .. ") "
SetVariables(lineNumberOfToken[beginningOfStatementToken[#stateTrace - 1]])
AddVariables(vars, isLocal)
else
local varsAsStrings = vars:gsub("^local ", ""):gsub(" ", ""):gsub("\x25s*([^,]+)\x25s*", "'\x251'")
if isPersistent then
InsertCode(" WSDebug.CheckForStop(" .. lineNumberOfToken[beginningOfStatementToken[#stateTrace - 1]] .. ", wsdebug_executionTab, wsdebug_generationCounter) " .. vars .. " = WSDebug.GetVar(wsdebug_executionTab, " .. currentLevel .. ", " .. varsAsStrings .. ", ", beginningOfStatementCode[#stateTrace - 1])
else
InsertCode(" WSDebug.CheckForStop(" .. lineNumberOfToken[beginningOfStatementToken[#stateTrace - 1]] .. ", wsdebug_executionTab, wsdebug_generationCounter) " .. vars .. " = ", beginningOfStatementCode[#stateTrace - 1])
end
if isPersistent then
finalCode = finalCode .. ") WSDebug.AssignVar(" .. vars:gsub("^local ", "") .. ", " .. (isLocal and currentLevel or -1) .. ", wsdebug_executionTab, " .. lineNumberOfToken[beginningOfStatementToken[#stateTrace - 1]] .. ", " .. varsAsStrings .. ") "
else
finalCode = finalCode .. " WSDebug.AssignVar(" .. vars:gsub("^local ", "") .. ", " .. (isLocal and currentLevel or -1) .. ", wsdebug_executionTab, " .. lineNumberOfToken[beginningOfStatementToken[#stateTrace - 1]] .. ", " .. varsAsStrings .. ") "
end
SetVariables(lineNumberOfToken[beginningOfStatementToken[#stateTrace - 1]])
AddVariables(vars, isLocal)
end
else
InsertCode(vars .. " = ", beginningOfStatementCode[#stateTrace - 1])
end
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
beginningOfStatementToken[#stateTrace] = i
beginningOfStatementCode[#stateTrace] = #finalCode + 1
end
if token == "goto" then
finalCode = finalCode
.. " WSDebug.CheckForStop(" .. lineNumberOfToken[i] .. ", wsdebug_executionTab, wsdebug_generationCounter) "
SetVariables(lineNumberOfToken[i])
end
if currentState == "root" or currentState == "functionbody" then
if stateOfToken ~= currentState and stateOfToken ~= "index" then
beginningOfStatementToken[#stateTrace] = i
beginningOfStatementCode[#stateTrace] = #finalCode + 1
elseif IsEndOfStatement(i) then
if tokens[i - 2] ~= "goto" then
if (sub(tokens[i - 1], 1, 1) == '"' or sub(tokens[i - 1], 1, 1) == "'") then
InsertCode("WSDebug.CheckForStop(" .. lineNumberOfToken[i - 1] .. ", wsdebug_executionTab, wsdebug_generationCounter) ", beginningOfStatementCode[#stateTrace])
SetVariables(lineNumberOfToken[i - 1])
else
local vars = ""
for j = beginningOfStatementToken[#stateTrace], i - 1 do
if tokens[j] ~= "local" then
vars = vars .. tokens[j]
end
end
local isLocal = tokens[beginningOfStatementToken[#stateTrace]] == "local"
local varsAsStrings = vars:gsub("\x25s*([^,]+)\x25s*", "'\x251'")
local isPersistent = (isLocal and settings.persistUpvalues and stateTrace[#stateTrace] == "root" and not lineIsEphemeral[lineNumberOfToken[i - 1]])
or (not settings.duringInit and lineIsPersistent[lineNumberOfToken[i - 1]] and (stateTrace[#stateTrace] == "root" or stateTrace[#stateTrace - 1] == "functionbody"))
if isPersistent then
finalCode = finalCode .. "wsdebug_result = WSDebug.GetVarEx(wsdebug_executionTab, " .. currentLevel .. ", " .. varsAsStrings .. ") "
finalCode = finalCode .. vars .. " = table.unpack(wsdebug_result)"
.. " WSDebug.CheckForStop(" .. lineNumberOfToken[i - 1] .. ", wsdebug_executionTab, wsdebug_generationCounter)"
.. " WSDebug.AssignVars(wsdebug_result," .. currentLevel .. ", wsdebug_executionTab, 0, " .. varsAsStrings .. ") "
else
finalCode = finalCode
.. " WSDebug.CheckForStop(" .. lineNumberOfToken[i - 1] .. ", wsdebug_executionTab, wsdebug_generationCounter)"
.. " WSDebug.AssignVars(nil, " .. currentLevel .. ", wsdebug_executionTab, 0, " .. varsAsStrings .. ") "
end
SetVariables(lineNumberOfToken[i - 1])
AddVariables(vars, isLocal)
end
end
beginningOfStatementToken[#stateTrace] = i
beginningOfStatementCode[#stateTrace] = #finalCode + 1
end
elseif currentState == "until" and tokens[i - 1] ~= "until" and IsEndOfStatement(i) then
ReduceLevel()
finalCode = finalCode .. ") "
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
beginningOfStatementToken[#stateTrace] = i
beginningOfStatementCode[#stateTrace] = #finalCode + 1
end
stateOfToken = currentState
stateOfToken = currentState
if currentState == "args" and token ~= ")" then
args = args .. token
end
if token == "::" then
if currentState == "gotodef" then
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
finalCode = finalCode .. token
else
currentState = "gotodef"
stateTrace[#stateTrace + 1] = currentState
finalCode = finalCode .. token
end
elseif currentState == "gotodef" then
finalCode = finalCode .. token
elseif LUA_OPERATORS[token] or LUA_KEYWORDS[token] then
if token == "(" then
if currentState == "functiondef" or currentState == "reversefunctiondef" or currentState == "anonymousfunctiondef" or currentState == "wrappedfunctiondef" or currentState == "wrappedreversefunctiondef" then
currentState = "args"
stateTrace[#stateTrace + 1] = "args"
finalCode = finalCode .. token
args = ""
elseif (not LUA_OPERATORS[tokens[i - 1]] and not LUA_CONTROL_FLOW_STATEMENTS[tokens[i - 1]]) or tokens[i - 1] == "]" then
currentState = "params"
stateTrace[#stateTrace + 1] = currentState
if stateTrace[#stateTrace - 1] == "root" or stateTrace[#stateTrace - 1] == "functionbody" then
for j = #stateTrace, 1, -1 do
if stateTrace[j] == "root" or stateTrace[j] == "functionbody" then
InsertCode("WSDebug.CheckForStop(" .. lineNumberOfToken[i] .. ", wsdebug_executionTab, wsdebug_generationCounter) ", beginningOfStatementCode[j])
SetVariables(lineNumberOfToken[i])
break
end
end
end
if CREATOR_DESTRUCTOR_PAIRS[tokens[i - 1]] then
if FRAME_RETURNER_FUNCTIONS[tokens[i - 1]] then
currentState = "doublehandlewrapper"
stateTrace[#stateTrace + 1] = currentState
finalCode[#finalCode] = "WSDebug.ValidateFrame(WSDebug.StoreHandle('" .. tokens[i - 1] .. "', wsdebug_executionTab, " .. tokens[i - 1] .. "(WSDebug.CatchParams('" .. tokens[i - 1] .. "', wsdebug_executionTab, "
else
currentState = "handlewrapper"
stateTrace[#stateTrace + 1] = currentState
finalCode[#finalCode] = "WSDebug.StoreHandle('" .. tokens[i - 1] .. "', wsdebug_executionTab, " .. tokens[i - 1] .. "(WSDebug.CatchParams('" .. tokens[i - 1] .. "', wsdebug_executionTab, "
end
elseif FRAME_RETURNER_FUNCTIONS[tokens[i - 1]] then
currentState = "handlewrapper"
stateTrace[#stateTrace + 1] = currentState
finalCode[#finalCode] = "WSDebug.ValidateFrame(" .. tokens[i - 1] .. "(WSDebug.CatchParams('" .. tokens[i - 1] .. "', wsdebug_executionTab, "
elseif settings.runInit and tokens[i - 1]:match("^OnInit.") ~= nil then
finalCode[#finalCode] = "WSDebug.Execute('" .. tokens[i - 1]:match("\x25.(.*)") .. "', "
currentState = "onInitWrapper"
stateTrace[#stateTrace] = currentState
else
finalCode[#finalCode] = tokens[i - 1] .. "(WSDebug.CatchParams('" .. tokens[i - 1] .. "', wsdebug_executionTab, "
end
else
currentState = "parenthesis"
stateTrace[#stateTrace + 1] = currentState
finalCode = finalCode .. token
end
elseif token == ")" then
if currentState == "parenthesis" then
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
finalCode = finalCode .. token .. " "
elseif currentState == "args" then
stateTrace[#stateTrace] = nil
currentState = "functionbody"
stateTrace[#stateTrace + 1] = currentState
levelOfFunction[#stateTrace] = currentLevel
nameOfFunction[#stateTrace] = functionName
functionIsLocal[#stateTrace] = funcIsLocal
lineNumberOfFunction[#stateTrace] = lineNumberOfToken[i]
currentLevel = currentLevel + 1
local argsAsStrings = args:gsub("\x25s*([^,]+)\x25s*", "'\x251'")
if functionName then
local colonSyntax = find(functionName, ":") ~= nil
finalCode = finalCode .. ") "
.. "if not coroutine.isyieldable(coroutine.running()) and (WSDebug.Coroutine[wsdebug_executionTab] == nil or coroutine.status(WSDebug.Coroutine[wsdebug_executionTab]) ~= 'suspended') and wsdebug_generationCounter == WSDebug.GenerationCounter[wsdebug_executionTab] then "
.. "return " .. (args ~= "" and ("WSDebug.HandleCoroutine(" .. functionName:gsub(":", ".") .. ", wsdebug_executionTab, " .. (colonSyntax and "self, " or "") .. args .. ")") or
"WSDebug.HandleCoroutine(" .. functionName:gsub(":", ".") .. (colonSyntax and ", wsdebug_executionTab, self" or "") .. ")")
.. " end "
.. (#args > 0 and (" WSDebug.AssignVars(table.pack(" .. args .. "), " .. currentLevel .. ", wsdebug_executionTab, 0, " .. argsAsStrings .. ")") or "")
if #args > 0 then
AddVariables(args, true)
end
if settings.parse and varTable[-1][functionName] then
FUNCTION_PREVIEW[functionName] = args
end
functionName = nil
else
numFunctions = numFunctions + 1
indexOfFunction[#stateTrace - 1] = numFunctions
finalCode = finalCode .. ") "
.. "if not coroutine.isyieldable(coroutine.running()) and (WSDebug.Coroutine[wsdebug_executionTab] == nil or coroutine.status(WSDebug.Coroutine[wsdebug_executionTab]) ~= 'suspended') and wsdebug_generationCounter == WSDebug.GenerationCounter[wsdebug_executionTab] then "
.. "return " .. (args ~= "" and ("WSDebug.HandleCoroutine(WSDebug.FuncList[" .. numFunctions .. "], wsdebug_executionTab, " .. args .. ")") or
("WSDebug.HandleCoroutine(WSDebug.FuncList[" .. numFunctions .. "], wsdebug_executionTab)"))
.. " end "
.. (#args > 0 and (" WSDebug.AssignVars(table.pack(" .. args .. "), " .. currentLevel .. ", wsdebug_executionTab, 0, " .. argsAsStrings .. ")") or "")
if #args > 0 then
AddVariables(args, true)
end
end
elseif currentState == "params" or currentState == "handlewrapper" or currentState == "doublehandlewrapper" or currentState == "onInitWrapper" then
if tokens[i - 1] == "(" then
local j = #finalCode
while true do
if finalCode[j]:match("', ") then
finalCode[j] = sub(finalCode[j], 1, -3)
break
end
j = j - 1
end
end
if currentState == "handlewrapper" then
finalCode = finalCode .. ") "
stateTrace[#stateTrace] = nil
elseif currentState == "doublehandlewrapper" then
finalCode = finalCode .. ")) "
stateTrace[#stateTrace] = nil
end
if currentState == "onInitWrapper" then
finalCode = finalCode .. ") "
else
finalCode = finalCode .. ")) "
end
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
else
finalCode = finalCode .. token .. " "
end
elseif token == "function" then
if tokens[i + 1] == "(" then
if tokens[i - 1] == "=" then
currentState = "reversefunctiondef"
if compiler.WRAP_IN_CALLER_FUNCTIONS then
local tableStr = ""
for j = 1, #tableTrace do
tableStr = tableStr .. tableTrace[j] .. "."
end
local name
if tokens[i - 2] == "]" then
name = tokens[i - 4] .. tokens[i - 3]:gsub('"', '\\"'):gsub("'", "\\'") .. tokens[i - 2]
else
name = tokens[i - 2]
end
if sub(name, 1, 2) ~= "__" then
currentState = "wrappedreversefunctiondef"
finalCode = finalCode .. "WSDebug.GetWrapper('" .. tableStr .. name .. "', wsdebug_executionTab, wsdebug_generationCounter, WSDebug.StoreFunc(function"
else
finalCode = finalCode .. "WSDebug.StoreFunc(function"
end
else
finalCode = finalCode .. "WSDebug.StoreFunc(function"
end
else
currentState = "anonymousfunctiondef"
finalCode = finalCode .. "WSDebug.StoreFunc(function"
end
else
currentState = "functiondef"
funcIsLocal = tokens[i - 1] == "local"
finalCode = finalCode .. token .. " "
end
stateTrace[#stateTrace + 1] = currentState
beginningOfStatementToken[#stateTrace] = i + 1
elseif token == "if" or token == "while" then
currentState = "condition"
stateTrace[#stateTrace + 1] = currentState
finalCode = finalCode .. " " .. token .. " " .. "WSDebug.CheckForStop(" .. lineNumberOfToken[i] .. ", wsdebug_executionTab, wsdebug_generationCounter) and ("
SetVariables(lineNumberOfToken[i])
currentLevel = currentLevel + 1
elseif token == "elseif" then
if currentState == "returnstatement" then
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
end
currentState = "condition"
stateTrace[#stateTrace + 1] = currentState
finalCode = finalCode .. " " .. token .. " " .. "WSDebug.CheckForStop(" .. lineNumberOfToken[i] .. ", wsdebug_executionTab, wsdebug_generationCounter) and ("
SetVariables(lineNumberOfToken[i])
elseif token == "until" then
currentState = "until"
stateTrace[#stateTrace + 1] = currentState
finalCode = finalCode .. " " .. token .. " " .. "WSDebug.CheckForStop(" .. lineNumberOfToken[i] .. ", wsdebug_executionTab, wsdebug_generationCounter) and ("
SetVariables(lineNumberOfToken[i])
elseif token == "else" then
if currentState == "returnstatement" then
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
end
finalCode = finalCode .. "elseif WSDebug.CheckForStop(" .. lineNumberOfToken[i] .. ", wsdebug_executionTab, wsdebug_generationCounter) then "
SetVariables(lineNumberOfToken[i])
beginningOfStatementToken[#stateTrace] = i + 1
beginningOfStatementCode[#stateTrace] = #finalCode + 1
elseif token == "for" then
currentState = "loopheader"
stateTrace[#stateTrace + 1] = currentState
finalCode = finalCode .. "WSDebug.CheckForStop(" .. lineNumberOfToken[i] .. ", wsdebug_executionTab, wsdebug_generationCounter) " .. token .. " "
SetVariables(lineNumberOfToken[i])
beginningOfStatementToken[#stateTrace] = i + 1
currentLevel = currentLevel + 1
elseif token == "then" or token == "do" then
if currentState == "condition" then
finalCode = finalCode .. ") " .. token .. " "
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
elseif currentState == "loopheader" then
finalCode = finalCode .. " " .. token .. " "
if tokens[beginningOfStatementToken[#stateTrace] + 1] == "," then
varName = (tokens[beginningOfStatementToken[#stateTrace]] .. "," .. tokens[beginningOfStatementToken[#stateTrace] + 2])
local varsAsStrings = varName:gsub("\x25s*([^,]+)\x25s*", "'\x251'"):gsub(" ", "")
finalCode = finalCode .. "WSDebug.AssignVars({" .. varName .. "}, " .. currentLevel .. ", wsdebug_executionTab, 0, " .. varsAsStrings .. ") "
else
varName = tokens[beginningOfStatementToken[#stateTrace]]
local varsAsStrings = varName:gsub("\x25s*([^,]+)\x25s*", "'\x251'"):gsub(" ", "")
finalCode = finalCode .. "WSDebug.AssignVar(" .. varName .. ", " .. currentLevel .. ", wsdebug_executionTab, 0, " .. varsAsStrings .. ") "
end
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
AddVariables(varName, true)
else
finalCode = finalCode .. "do "
currentLevel = currentLevel + 1
beginningOfStatementToken[#stateTrace] = i + 1
beginningOfStatementCode[#stateTrace] = #finalCode + 1
end
elseif token == "repeat" then
currentLevel = currentLevel + 1
finalCode = finalCode .. token
.. " WSDebug.CheckForStop(" .. lineNumberOfToken[i] .. ", wsdebug_executionTab, wsdebug_generationCounter) "
SetVariables(lineNumberOfToken[i])
beginningOfStatementToken[#stateTrace] = i + 1
beginningOfStatementCode[#stateTrace] = #finalCode + 1
elseif token == "return" then
finalCode = finalCode
.. " WSDebug.CheckForStop(" .. lineNumberOfToken[i] .. ", wsdebug_executionTab, wsdebug_generationCounter)"
.. " " .. token .. " "
SetVariables(lineNumberOfToken[i])
currentState = "returnstatement"
stateTrace[#stateTrace + 1] = currentState
elseif token == "end" then
local noStop
if currentState == "returnstatement" then
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
beginningOfStatementToken[#stateTrace] = i + 1
beginningOfStatementCode[#stateTrace] = #finalCode + 1
noStop = true
end
ReduceLevel()
if currentLevel == levelOfFunction[#stateTrace] then
local name = nameOfFunction[#stateTrace]
local isLocal = functionIsLocal[#stateTrace]
local lineNumber = lineNumberOfFunction[#stateTrace]
levelOfFunction[#stateTrace] = -1
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
if currentState == "reversefunctiondef" or currentState == "wrappedreversefunctiondef" or currentState == "anonymousfunctiondef" then
if noStop then
finalCode = finalCode .. token .. ", " .. indexOfFunction[#stateTrace] .. ")"
else
finalCode = finalCode .. "WSDebug.CheckForStop(" .. lineNumberOfToken[i] .. ", wsdebug_executionTab, wsdebug_generationCounter) " .. token .. ", " .. indexOfFunction[#stateTrace] .. ")"
end
else
if noStop then
finalCode = finalCode .. token .. " "
else
finalCode = finalCode .. "WSDebug.CheckForStop(" .. lineNumberOfToken[i] .. ", wsdebug_executionTab, wsdebug_generationCounter) " .. token .. " "
end
end
if currentState == "wrappedfunctiondef" or currentState == "wrappedreversefunctiondef" then
finalCode = finalCode .. ")"
end
if (currentState == "functiondef" or currentState == "wrappedfunctiondef") and name then
finalCode = finalCode .. " WSDebug.AssignVar(" .. name .. ", " .. (isLocal and currentLevel or -1) .. ", wsdebug_executionTab, " .. lineNumber .. ", '" .. name .. "') "
AddVariables(name, isLocal)
end
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
SetVariables(lineNumberOfToken[i])
else
finalCode = finalCode .. token .. " "
beginningOfStatementToken[#stateTrace] = i + 1
beginningOfStatementCode[#stateTrace] = #finalCode + 1
end
elseif token == "{" then
currentState = "tabledef"
stateTrace[#stateTrace + 1] = currentState
finalCode = finalCode .. token
if tokens[i - 1] == "=" then
if tokens[i - 2] == "]" then
tableTrace[#tableTrace + 1] = tokens[i - 4] .. tokens[i - 3]:gsub('"', '\\"'):gsub("'", "\\'") .. tokens[i - 2]
else
tableTrace[#tableTrace + 1] = tokens[i - 2]
end
else
untracedTables = untracedTables + 1
end
elseif token == "}" then
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
finalCode = finalCode .. token
if untracedTables > 0 then
untracedTables = untracedTables - 1
else
tableTrace[#tableTrace] = nil
end
elseif token == "=" then
if currentState ~= "tabledef" and currentState ~= "loopheader" then
currentState = "assignment"
stateTrace[#stateTrace + 1] = currentState
finalCode = finalCode .. token .. " "
beginningOfStatementToken[#stateTrace] = i + 1
beginningOfStatementCode[#stateTrace] = #finalCode + 1
else
finalCode = finalCode .. token .. " "
end
elseif token == "[" and (currentState == "root" or currentState == "functionbody") then
currentState = "index"
stateTrace[#stateTrace + 1] = currentState
finalCode = finalCode .. token
elseif token == "]" and currentState == "index" then
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
finalCode = finalCode .. token
elseif token == "," then
finalCode = finalCode .. token .. " "
elseif not skip then
if currentState == "functiondef" then
functionName = token
if find(functionName, ":") == nil then
if tokens[i - 2] == "local" then
finalCode[#finalCode] = functionName
if compiler.WRAP_IN_CALLER_FUNCTIONS then
currentState = "wrappedfunctiondef"
stateTrace[#stateTrace] = currentState
finalCode = finalCode .. " " .. functionName .. " = " .. "WSDebug.GetWrapper('" .. functionName .. "', wsdebug_executionTab, wsdebug_generationCounter, function"
else
finalCode = finalCode .. " " .. functionName .. " = " .. "function"
end
else
finalCode[#finalCode] = functionName
if compiler.WRAP_IN_CALLER_FUNCTIONS then
currentState = "wrappedfunctiondef"
stateTrace[#stateTrace] = currentState
finalCode = finalCode .. " = " .. "nil " .. functionName .. " = " .. "WSDebug.GetWrapper('" .. functionName .. "', wsdebug_executionTab, wsdebug_generationCounter, function"
else
finalCode = finalCode .. " = " .. "function"
end
end
else
finalCode = finalCode .. token .. " "
end
else
finalCode = finalCode .. token .. " "
end
end
elseif not skip then
if currentState == "functiondef" then
functionName = token
if find(functionName, ":") == nil then
if tokens[i - 2] == "local" then
finalCode[#finalCode] = functionName
if compiler.WRAP_IN_CALLER_FUNCTIONS then
currentState = "wrappedfunctiondef"
stateTrace[#stateTrace] = currentState
finalCode = finalCode .. " " .. functionName .. " = " .. "WSDebug.GetWrapper('" .. functionName .. "', wsdebug_executionTab, wsdebug_generationCounter, function"
else
finalCode = finalCode .. " " .. functionName .. " = " .. "function"
end
else
finalCode[#finalCode] = functionName
if compiler.WRAP_IN_CALLER_FUNCTIONS then
currentState = "wrappedfunctiondef"
stateTrace[#stateTrace] = currentState
finalCode = finalCode .. " = " .. "nil " .. functionName .. " = " .. "WSDebug.GetWrapper('" .. functionName .. "', wsdebug_executionTab, wsdebug_generationCounter, function"
else
finalCode = finalCode .. " = " .. "function"
end
end
else
finalCode = finalCode .. token .. " "
end
else
finalCode = finalCode .. token .. " "
end
end
i = i + 1
end
SetVariables(lineNumberOfToken[#tokens] + 1)
if settings.runInit then
finalCode = finalCode .. [[
for j = 1, 7 do
for k = 1, #WSDebug.FunctionsToInit[j] do
WSDebug.FunctionsToInit[j][k]()
end
end
]]
end
finalCode = finalCode .. "WSDebug.End(" .. lineNumberOfToken[#tokens] + 1 .. ", wsdebug_executionTab, wsdebug_generationCounter)"
else
------------------------------------------------------------------------------------------------------------------------
--No debug mode
------------------------------------------------------------------------------------------------------------------------
local localDefs = {}
local closeHandleWrapperAt = {}
local openParentheses = 0
for i = 1, #tokens do
token = tokens[i]
while lineNumberOfToken[i] > currentLine do
finalCode = finalCode .. "\n"
currentLine = currentLine + 1
end
if token == "(" then
openParentheses = openParentheses + 1
if settings.runInit and tokens[i - 1]:match("^OnInit.") ~= nil then
finalCode[#finalCode] = "WSDebug.Execute('" .. tokens[i - 1]:match("\x25.(.*)") .. "', "
elseif compiler.STORE_HANDLES_IN_NO_DEBUG_MODE and CREATOR_DESTRUCTOR_PAIRS[tokens[i - 1]] and tokens[i - 2] ~= "function" then
currentState = "handlewrapper"
stateTrace[#stateTrace + 1] = currentState
finalCode[#finalCode] = "WSDebug.StoreHandle('" .. tokens[i - 1] .. "', wsdebug_executionTab, " .. tokens[i - 1]
finalCode = finalCode .. token
closeHandleWrapperAt[#stateTrace] = openParentheses
else
finalCode = finalCode .. token
end
elseif token == ")" then
if currentState == "handlewrapper" and closeHandleWrapperAt[#stateTrace] == openParentheses then
stateTrace[#stateTrace] = nil
currentState = stateTrace[#stateTrace]
finalCode = finalCode .. ")) "
elseif currentState == "functiondef" or currentState == "reversefunctiondef" then
currentState = "functionbody"
stateTrace[#stateTrace + 1] = currentState
finalCode = finalCode .. ") "
else
finalCode = finalCode .. ") "
end
openParentheses = openParentheses - 1
elseif token == "{" then
if tokens[i - 1] == "=" then
if tokens[i - 2] == "]" then
tableTrace[#tableTrace + 1] = tokens[i - 4] .. tokens[i - 3]:gsub('"', '\\"'):gsub("'", "\\'") .. tokens[i - 2]
else
tableTrace[#tableTrace + 1] = tokens[i - 2]
end
else
untracedTables = untracedTables + 1
end
finalCode = finalCode .. "{"
elseif token == "}" then
if untracedTables > 0 then
untracedTables = untracedTables - 1
else
tableTrace[#tableTrace] = nil
end
finalCode = finalCode .. "} "
elseif token == "local" and settings.parse then
local j = i + 1
if tokens[j] == "function" then
j = j + 1
end
repeat
localDefs[tokens[j]] = true
j = j + 2
until tokens[j - 1] ~= ","
finalCode = finalCode .. "local "
elseif token == "function" then
if settings.parse and untracedTables == 0 then
local name
local j
if tokens[i - 1] == "=" then
name = tokens[i - 2]
j = i + 2
else
name = tokens[i + 1]
j = i + 3
end
if name ~= "(" then
for k = #tableTrace, 1, -1 do
name = tableTrace[k] .. "." .. name
end
local dotPoint = find(name, "\x25.")
if not (dotPoint and localDefs[sub(name, 1, dotPoint - 1)] or localDefs[name]) then
args = ""
while tokens[j] ~= ")" do
if tokens[j] == "," then
args = args .. ", "
else
args = args .. tokens[j]
end
j = j + 1
end
args = args .. ","
FUNCTION_PREVIEW[name] = args
end
end
end
finalCode = finalCode .. "function "
else
finalCode = finalCode .. token .. " "
end
end
if settings.runInit then
finalCode = finalCode .. [[
for j = 1, 7 do
for k = 1, #WSDebug.FunctionsToInit[j] do
WSDebug.FunctionsToInit[j][k]()
end
end
]]
end
end
------------------------------------------------------------------------------------------------------------------------
--Execute function
------------------------------------------------------------------------------------------------------------------------
local functionString = table.concat(finalCode)
local func, err = load(functionString, ("WSCode " .. (tabNames[executionTab] or executionTab or "Unknown")), "t")
if not func then
if err and err:match("too many local variables") then
print("|cffff5555ERROR at WSCode " .. (executionTab or "Unknown") .. ": Code chunk could not be transpiled because the local variable limit (200) was exceeded by adding debug variables.|r")
else
print("|cffff5555ERROR at WSCode " .. (executionTab or "Unknown") .. ": Raw code did compile, but transpiled code did not. Please report this bug along with the code chunk and settings that caused it. The function was compiled in raw form.|r")
end
if settings.duringInit then
func = originalFunc
else
return
end
end
if settings.cleanHandles then
for handle, creator in pairs(handles[executionTab]) do
_G[CREATOR_DESTRUCTOR_PAIRS[creator]](handle)
handles[executionTab][handle] = nil
end
end
if Debug and Debug.original then
WSDebug.Coroutine[executionTab] = Debug.original.coroutine.create(func)
else
WSDebug.Coroutine[executionTab] = coroutine.create(func)
end
success, err = coroutine.resume(WSDebug.Coroutine[executionTab])
if success == false then
if settings.duringInit then
if Debug and Debug.errorHandler then
Debug.errorHandler(err)
else
print("|cffff5555ERROR at " .. (executionTab or "Unknown") .. ": " .. err)
end
initError = initError or {
fileName = executionTab or "Unknown",
lineNumber = lastLineNumber[executionTab] > 0 and lastLineNumber[executionTab] or err:match(":(\x25d+):")
}
else
HandleError(err)
end
end
end
--======================================================================================================================
--Top Buttons
--======================================================================================================================
local function CreateToggleExpandButton(type, texture, whichParent)
local button = BlzCreateFrame(type, whichParent, 0, 0)
local icon = BlzFrameGetChild(button, 0)
local iconClicked = BlzFrameGetChild(button, 1)
local iconHighlight = BlzFrameGetChild(button, 2)
BlzFrameSetAllPoints(icon, button)
BlzFrameSetTexture(icon, texture, 0, true)
BlzFrameSetTexture(iconClicked, texture, 0, true)
BlzFrameClearAllPoints(iconHighlight)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_BOTTOMLEFT, button, FRAMEPOINT_BOTTOMLEFT, 0.00375, 0.00375)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_TOPRIGHT, button, FRAMEPOINT_TOPRIGHT, -0.00375, -0.00375)
local trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, button, FRAMEEVENT_CONTROL_CLICK)
if whichParent == codeEditorParent then
BlzFrameSetPoint(button, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CLOSE_BUTTON_INSET, -editor.CLOSE_BUTTON_INSET - editor.CLOSE_BUTTON_SIZE)
BlzFrameSetPoint(button, FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CLOSE_BUTTON_INSET + editor.CLOSE_BUTTON_SIZE, -editor.CLOSE_BUTTON_INSET)
TriggerAddAction(trig, ToggleExpand)
buttons.expand = button
else
BlzFrameSetPoint(button, FRAMEPOINT_BOTTOMLEFT, variableViewer.parent, FRAMEPOINT_TOPRIGHT, -editor.CLOSE_BUTTON_INSET - editor.CLOSE_BUTTON_SIZE, -editor.CLOSE_BUTTON_INSET - editor.CLOSE_BUTTON_SIZE)
BlzFrameSetPoint(button, FRAMEPOINT_TOPRIGHT, variableViewer.parent, FRAMEPOINT_TOPRIGHT, -editor.CLOSE_BUTTON_INSET, -editor.CLOSE_BUTTON_INSET)
TriggerAddAction(trig, ToggleVariableViewerExpand)
buttons.expandVariableViewer = button
end
end
ToggleExpand = function()
BlzFrameSetEnable(buttons.expand, false)
BlzFrameSetEnable(buttons.expand, true)
isExpanded = not isExpanded
if isExpanded then
BlzDestroyFrame(buttons.expand)
CreateToggleExpandButton("CollapseEditorButton", "collapseEditor.blp", codeEditorParent)
BlzFrameSetAbsPoint(codeEditorParent, FRAMEPOINT_TOPLEFT, editor.EXPANDED_X, editor.Y_TOP)
BlzFrameSetSize(codeEditorParent, editor.EXPANDED_WIDTH, editor.MAX_LINES_ON_SCREEN*editor.LINE_SPACING + editor.CODE_TOP_INSET + editor.CODE_BOTTOM_INSET)
UpdateAllLines()
else
BlzDestroyFrame(buttons.expand)
CreateToggleExpandButton("ExpandEditorButton", "expandEditor.blp", codeEditorParent)
BlzFrameSetAbsPoint(codeEditorParent, FRAMEPOINT_TOPLEFT, editor.COLLAPSED_X, editor.Y_TOP)
BlzFrameSetSize(codeEditorParent, editor.COLLAPSED_WIDTH, editor.MAX_LINES_ON_SCREEN*editor.LINE_SPACING + editor.CODE_TOP_INSET + editor.CODE_BOTTOM_INSET)
UpdateAllLines()
end
local spaceRequired = 0
for i = 1, numTabs do
spaceRequired = spaceRequired + tabNavigator.widths[i] - tabNavigator.OVERLAP
end
AdjustTabWidths()
end
ToggleVariableViewerExpand = function()
BlzFrameSetEnable(buttons.expandVariableViewer, false)
BlzFrameSetEnable(buttons.expandVariableViewer, true)
variableViewerIsExpanded = not variableViewerIsExpanded
if variableViewerIsExpanded then
BlzDestroyFrame(buttons.expandVariableViewer)
CreateToggleExpandButton("CollapseEditorButton", "collapseEditor.blp", variableViewer.parent)
numVisibleVars = 0
UpdateVariableViewer(lastViewedLineNumber[currentTab] or lastLineNumber[currentTab])
BlzFrameSetVisible(variableViewerTitle, true)
BlzFrameSetVisible(buttons.back, #viewedVariableTrace > 0)
for i = 1, #variableViewer.functionFrames do
BlzFrameSetVisible(variableViewer.functionFrames[i], true)
end
for i = 1, #functionParams do
BlzFrameSetVisible(variableViewer.functionParamFrames[i], true)
end
else
BlzDestroyFrame(buttons.expandVariableViewer)
CreateToggleExpandButton("ExpandEditorButton", "expandEditor.blp", variableViewer.parent)
BlzFrameSetSize(variableViewer.parent, editor.CLOSE_BUTTON_SIZE + 2*editor.CLOSE_BUTTON_INSET, editor.CLOSE_BUTTON_SIZE + 2*editor.CLOSE_BUTTON_INSET)
for i = 1, numVisibleVars do
BlzFrameSetVisible(variableViewer.frames[i], false)
BlzFrameSetVisible(variableViewer.valueFrames[i], false)
end
for i = 1, #variableViewer.functionFrames do
BlzFrameSetVisible(variableViewer.functionFrames[i], false)
end
for i = 1, #variableViewer.functionParamFrames do
BlzFrameSetVisible(variableViewer.functionParamFrames[i], false)
end
BlzFrameSetVisible(variableViewerTitle, false)
BlzFrameSetVisible(buttons.back, false)
end
end
local function HideCodeEditor()
BlzFrameSetEnable(buttons.hideEditor, false)
BlzFrameSetEnable(buttons.hideEditor, true)
BlzFrameSetVisible(codeEditorParent, false)
editor.ON_DISABLE()
if compiler.WRAP_ALL_BLIZZARD_API_POINTS == "editoropen" then
coroutineWrapEnabled = false
end
end
--======================================================================================================================
--Benchmark
--======================================================================================================================
local function GetMeanAndStdDev()
local mean = 0
local numResults = #benchmark.results
for __, value in ipairs(benchmark.results) do
mean = mean + value
end
mean = mean / numResults
local varianceTotal = 0
for __, value in ipairs(benchmark.results) do
varianceTotal = varianceTotal + (value - mean)^2
end
local stddev = math.sqrt(1/(numResults - 1)*varianceTotal)
local stddevOfMean = stddev/math.sqrt(numResults)
return mean, stddevOfMean
end
local function BenchmarkLoop()
local time = os.clock()
if benchmark.gauge then
for __ = 1, benchmark.iterations do
DoNothing()
end
else
for __ = 1, benchmark.iterations do
benchmark.func()
end
end
local elapsedTime = os.clock() - time
if elapsedTime < 0.005 then
benchmark.iterations = 10*benchmark.iterations
elseif elapsedTime < 0.05 then
benchmark.iterations = (benchmark.iterations*0.055/elapsedTime) // 1
else
if benchmark.gauge then
benchmark.emptyFunctionNanoseconds = elapsedTime*10^9/benchmark.iterations
benchmark.gauge = false
else
local executionTime = ((elapsedTime*10^9/benchmark.iterations) - benchmark.emptyFunctionNanoseconds) // 1
table.insert(benchmark.results, executionTime)
ClearTextMessages()
local mean, stddev = GetMeanAndStdDev()
if stddev > 0 then
if mean < 10000 then
print("Execution time of code chunk is: " .. string.format("\x25.1f", mean) .. " +/- " .. string.format("\x25.1f", stddev) .. " |cff00ff00nano|rseconds.")
elseif mean < 10000000 then
print("Execution time of code chunk is: " .. string.format("\x25.1f", mean/1000) .. " +/- " .. string.format("\x25.1f", stddev/1000) .. " |cffffff00micro|rseconds.")
else
print("Execution time of code chunk is: " .. string.format("\x25.1f", mean/1000000) .. " +/- " .. string.format("\x25.1f", stddev/1000000) .. " |cffff8800milli|rseconds.")
end
else
if mean < 10000 then
print("Execution time of code chunk is: " .. mean .. " |cff00ff00nano|rseconds.")
elseif mean < 10000000 then
print("Execution time of code chunk is: " .. mean//1000 .. " |cffffff00micro|rseconds.")
else
print("Execution time of code chunk is: " .. mean//1000000 .. " |cffff8800milli|rseconds.")
end
end
benchmark.gauge = true
end
benchmark.iterations = 10
end
TimerStart(benchmark.TIMER, elapsedTime + 0.01, false, BenchmarkLoop)
end
local function Benchmark()
if benchmark.func then
PauseTimer(benchmark.TIMER)
for i = 1, #benchmark.results do
benchmark.results[i] = nil
end
BlzFrameSetTexture(BlzFrameGetChild(buttons.benchmark, 0), "Benchmark.blp", 0, true)
BlzFrameSetTexture(BlzFrameGetChild(buttons.benchmark, 1), "Benchmark.blp", 0, true)
print("\nBenchmark aborted.")
benchmark.func = nil
else
local adjustedCodeLines = {}
local currentLines = codeLines[currentTab][step[currentTab]]
adjustedCodeLines[1] = currentLines[1]
for i = 2, highestNonEmptyLine[currentTab][step[currentTab]] do
adjustedCodeLines[i] = "\n" .. currentLines[i]
end
local func, error = load(table.concat(adjustedCodeLines), "WSCode " .. (tabNames[currentTab] or "Main"), "t")
if not func then
if Debug then Debug.throwError(error) end
return
end
BlzFrameSetTexture(BlzFrameGetChild(buttons.benchmark, 0), "EndBenchmark.blp", 0, true)
BlzFrameSetTexture(BlzFrameGetChild(buttons.benchmark, 1), "EndBenchmark.blp", 0, true)
benchmark.iterations = 10
benchmark.func = func
benchmark.gauge = true
BenchmarkLoop()
end
end
--======================================================================================================================
--Search Bar
--======================================================================================================================
local function ShowSearchBar()
BlzFrameSetEnable(buttons.search, false)
BlzFrameSetEnable(buttons.search, true)
BlzFrameSetVisible(searchBar.parent, true)
BlzFrameSetText(searchBar.textField, "")
BlzFrameSetFocus(enterBox, false)
BlzFrameSetFocus(searchBar.textField, true)
end
RenderSearchHighlights = function()
local currentLines = codeLines[currentTab][step[currentTab]]
local numVisible = 0
for i = 1, searchBar.numFinds do
if searchBar.lines[i] > lineNumberOffset[currentTab] and searchBar.lines[i] <= lineNumberOffset[currentTab] + editor.MAX_LINES_ON_SCREEN then
numVisible = numVisible + 1
searchBar.highlights[numVisible] = searchBar.highlights[numVisible] or GetTextHighlightFrame("05", 120, "searchHighlight")
BlzFrameSetPoint(searchBar.highlights[numVisible], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(currentLines[searchBar.lines[i]], 1, searchBar.startPos[i])), -editor.CODE_TOP_INSET - (searchBar.lines[i] - lineNumberOffset[currentTab])*editor.LINE_SPACING)
BlzFrameSetPoint(searchBar.highlights[numVisible], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET + GetTextWidth(sub(currentLines[searchBar.lines[i]], 1, searchBar.endPos[i])), -editor.CODE_TOP_INSET - (searchBar.lines[i] - lineNumberOffset[currentTab] - 1)*editor.LINE_SPACING)
if i == searchBar.searchPos then
BlzFrameSetTexture(searchBar.highlights[numVisible], "ReplaceableTextures\\TeamColor\\TeamColor05.blp", 0, true)
BlzFrameSetAlpha(searchBar.highlights[numVisible], 150)
else
BlzFrameSetTexture(searchBar.highlights[numVisible], "ReplaceableTextures\\TeamColor\\TeamColor23.blp", 0, true)
BlzFrameSetAlpha(searchBar.highlights[numVisible], 120)
end
end
end
for i = numVisible + 1, #searchBar.highlights do
ReturnTextHighlightFrame(searchBar.highlights[i])
searchBar.highlights[i] = nil
end
end
FindSearchItems = function()
if searchBar.text == "" then
searchBar.numFinds = 0
RenderSearchHighlights()
BlzFrameSetEnable(searchBar.searchDownButton, false)
BlzFrameSetEnable(searchBar.searchUpButton, false)
BlzFrameSetText(searchBar.numResults, "|cffaaaaaaNo results|r")
return
end
local text = searchBar.text
local currentLines = codeLines[currentTab][step[currentTab]]
local str, j, start, stop
local numFinds = 0
local searchPos = 1
for i = 1, highestNonEmptyLine[currentTab][step[currentTab]] do
str = currentLines[i]
j = 1
start, stop = str:find(text)
while start do
numFinds = numFinds + 1
searchBar.lines[numFinds] = i
searchBar.startPos[numFinds] = start - 1
searchBar.endPos[numFinds] = stop
if cursor.adjustedLine and (i < cursor.adjustedLine or i == cursor.adjustedLine and cursor.pos < stop) then
searchPos = searchPos + 1
end
j = j + 1
start, stop = str:find(text, stop + 1)
end
end
searchBar.numFinds = numFinds
if searchBar.numFinds > 0 then
if cursor.adjustedLine then
searchBar.searchPos = math.min(searchPos, numFinds)
else
searchBar.searchPos = 1
end
JumpWindow(searchBar.lines[searchBar.searchPos])
BlzFrameSetText(searchBar.numResults, searchBar.searchPos .. " of " .. searchBar.numFinds)
BlzFrameSetEnable(searchBar.searchDownButton, true)
BlzFrameSetEnable(searchBar.searchUpButton, true)
else
searchBar.searchPos = nil
BlzFrameSetText(searchBar.numResults, "|cffff0000No results|r")
BlzFrameSetEnable(searchBar.searchDownButton, false)
BlzFrameSetEnable(searchBar.searchUpButton, false)
end
end
local function OnSearchBarEdit()
searchBar.text = BlzGetTriggerFrameText()
FindSearchItems()
RenderSearchHighlights()
end
local function SearchUp()
if searchBar.searchPos then
searchBar.searchPos = searchBar.searchPos - 1
if searchBar.searchPos == 0 then
searchBar.searchPos = searchBar.numFinds
end
BlzFrameSetText(searchBar.numResults, searchBar.searchPos .. " of " .. searchBar.numFinds)
JumpWindow(searchBar.lines[searchBar.searchPos])
RenderSearchHighlights()
end
end
local function SearchDown()
if searchBar.searchPos then
searchBar.searchPos = searchBar.searchPos + 1
if searchBar.searchPos > searchBar.numFinds then
searchBar.searchPos = 1
end
BlzFrameSetText(searchBar.numResults, searchBar.searchPos .. " of " .. searchBar.numFinds)
JumpWindow(searchBar.lines[searchBar.searchPos])
RenderSearchHighlights()
end
end
Pull = function(fileName)
for i = 1, #fileNames do
if fileNames[i] == fileName then
StoreCode(fileNames[i], files[i])
BlzFrameSetMinMaxValue(codeScroller, 0, highestNonEmptyLine[currentTab][step[currentTab]])
BlzFrameSetValue(codeScroller, highestNonEmptyLine[currentTab][step[currentTab]])
UpdateAllLines()
return
end
end
print("|cffff5555ERROR: No file found with the name " .. fileName .. ".|r")
end
--======================================================================================================================
--Init
--======================================================================================================================
EnableEditor = function(whichPlayer)
whichPlayer = whichPlayer GetTriggerPlayer()
user = whichPlayer
if compiler.WRAP_ALL_BLIZZARD_API_POINTS == "editoropen" then
coroutineWrapEnabled = true
end
if not BlzLoadTOCFile("CodeEditor.toc") then
error("CodeEditor.toc failed to load.")
end
if codeEditorParent then
editor.ON_ENABLE()
BlzFrameSetVisible(codeEditorParent, true)
return
end
if not World2Screen then
if Debug then
Debug.throwError("Missing requirement: World2Screen.")
end
return
end
if not HandleType then
if Debug then
Debug.throwError("Missing requirement: HandleType.")
end
return
end
------------------------------------------------------------------------------------------------------------------------
--Code Editor
------------------------------------------------------------------------------------------------------------------------
codeEditorParent = BlzCreateFrame("CodeEditor", editor.GET_PARENT(), 0, 0)
if GetHandleId(codeEditorParent) == 0 then
if Debug then
Debug.throwError("Missing import: CodeEditor.fdf.")
end
return
end
BlzFrameSetLevel(codeEditorParent, 2)
local backdrop = BlzFrameGetChild(codeEditorParent, 0)
BlzFrameSetAllPoints(backdrop, codeEditorParent)
BlzFrameSetAlpha(backdrop, editor.BLACK_BACKDROP_ALPHA)
local border = BlzFrameGetChild(codeEditorParent, 1)
BlzFrameSetAllPoints(border, codeEditorParent)
local titleFrame = BlzCreateFrameByType("TEXT", "", codeEditorParent, "", 0)
BlzFrameSetText(titleFrame, "|cffffcc00Warcraft Studio Code|r")
BlzFrameSetEnable(titleFrame, false)
BlzFrameSetSize(titleFrame, 0, 0)
BlzFrameSetPoint(titleFrame, FRAMEPOINT_TOP, codeEditorParent, FRAMEPOINT_TOP, 0, editor.TITLE_VERTICAL_SHIFT)
errorHighlight = BlzCreateFrameByType("BACKDROP", "", codeEditorParent, "", 0)
BlzFrameSetTexture(errorHighlight, "ReplaceableTextures\\TeamColor\\TeamColor00.blp", 0, true)
BlzFrameSetAlpha(errorHighlight, 128)
BlzFrameSetEnable(errorHighlight, false)
BlzFrameSetVisible(errorHighlight, false)
codeScroller = BlzCreateFrame("VerticalSlider", codeEditorParent, 0, 0)
if GetHandleId(codeScroller) == 0 then
if Debug then
Debug.throwError("Missing import: VerticalSlider.fdf.")
end
return
end
BlzFrameSetPoint(codeScroller, FRAMEPOINT_TOP, codeEditorParent, FRAMEPOINT_TOPRIGHT, -editor.CODE_SCROLLER_HORIZONTAL_INSET, -editor.CODE_TOP_INSET)
BlzFrameSetSize(codeScroller, 0.012, editor.MAX_LINES_ON_SCREEN*editor.LINE_SPACING)
BlzFrameSetMinMaxValue(codeScroller, 0, highestNonEmptyLine[currentTab][step[currentTab]])
BlzFrameSetValue(codeScroller, highestNonEmptyLine[currentTab][step[currentTab]])
BlzFrameSetStepSize(codeScroller, 1)
BlzFrameSetLevel(codeScroller, 3)
lineNumberOffset[currentTab] = 0
local trigStop = CreateTrigger()
local trigEnter = CreateTrigger()
for i = 1, editor.MAX_LINES_ON_SCREEN do
if editor.USE_MONOSPACE_FONT then
codeLineFrames[i] = BlzCreateFrame("Consolas", codeEditorParent, 0, 0)
else
codeLineFrames[i] = BlzCreateFrameByType("TEXT", "", codeEditorParent, "", 0)
end
BlzFrameSetPoint(codeLineFrames[i], FRAMEPOINT_TOPLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.CODE_LEFT_INSET, -editor.CODE_TOP_INSET - (i-1)*editor.LINE_SPACING)
BlzFrameSetPoint(codeLineFrames[i], FRAMEPOINT_BOTTOMRIGHT, codeEditorParent, FRAMEPOINT_TOPRIGHT, 1.0, -editor.CODE_TOP_INSET - i*editor.LINE_SPACING)
BlzFrameSetTextAlignment(codeLineFrames[i], TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_LEFT)
local currentLines = codeLines[currentTab][step[currentTab]]
if i <= highestNonEmptyLine[currentTab][step[currentTab]] then
coloredCodeLines[currentTab][step[currentTab]][i] = GetColoredText(currentLines[i], i, 1)
if i <= editor.MAX_LINES_ON_SCREEN then
BlzFrameSetText(codeLineFrames[i], coloredCodeLines[currentTab][step[currentTab]][i])
end
else
BlzFrameSetText(codeLineFrames[i], "")
end
BlzFrameSetLevel(codeLineFrames[i], 1)
lineNumbers[i] = BlzCreateFrameByType("TEXT", "", codeEditorParent, "", 0)
BlzFrameSetPoint(lineNumbers[i], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.LINE_NUMBER_HORIZONTAL_INSET/editor.LINE_NUMBER_SCALE, (-editor.CODE_TOP_INSET - i*editor.LINE_SPACING)/editor.LINE_NUMBER_SCALE)
BlzFrameSetPoint(lineNumbers[i], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPRIGHT, -editor.CODE_LEFT_INSET/editor.LINE_NUMBER_SCALE, (-editor.CODE_TOP_INSET - (i - 1)*editor.LINE_SPACING)/editor.LINE_NUMBER_SCALE)
BlzFrameSetTextAlignment(lineNumbers[i], TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_LEFT)
BlzFrameSetEnable(lineNumbers[i], false)
BlzFrameSetScale(lineNumbers[i], editor.LINE_NUMBER_SCALE)
BlzFrameSetText(lineNumbers[i], "|cff999999" .. i .. "|r")
indexFromFrame[lineNumbers[i]] = i
stopButtons[i] = BlzCreateFrame("CodeEditorButton", codeEditorParent, 0, 0)
BlzFrameSetPoint(stopButtons[i], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.STOP_BUTTON_HORIZONTAL_INSET, -editor.CODE_TOP_INSET - i*editor.LINE_SPACING)
BlzFrameSetPoint(stopButtons[i], FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, editor.STOP_BUTTON_HORIZONTAL_INSET + editor.LINE_SPACING, -editor.CODE_TOP_INSET - (i-1)*editor.LINE_SPACING)
BlzFrameSetAllPoints(BlzFrameGetChild(stopButtons[i], 0), stopButtons[i])
BlzFrameSetTexture(BlzFrameGetChild(stopButtons[i], 0), LINE_STATE_ICONS[lines.debugState[currentTab][i]], 0, true)
BlzTriggerRegisterFrameEvent(trigStop, stopButtons[i], FRAMEEVENT_CONTROL_CLICK)
indexFromFrame[stopButtons[i]] = i
end
enterBox = BlzCreateFrame("EscMenuEditBoxTemplate", codeEditorParent, 0, 0)
BlzFrameSetPoint(enterBox, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_BOTTOMLEFT, editor.CODE_LEFT_INSET - 0.002, editor.CODE_BOTTOM_INSET - 0.002)
BlzFrameSetPoint(enterBox, FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPRIGHT, -editor.CODE_RIGHT_INSET + 0.002, -editor.CODE_TOP_INSET + 0.002)
BlzFrameSetLevel(enterBox, 2)
BlzFrameSetAlpha(enterBox, 0)
BlzTriggerRegisterFrameEvent(trigEnter, enterBox, FRAMEEVENT_EDITBOX_ENTER)
BlzFrameSetText(enterBox, " ")
local trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, enterBox, FRAMEEVENT_MOUSE_WHEEL)
TriggerAddAction(trig, function()
if BlzGetTriggerFrameValue() > 0 then
BlzFrameSetValue(codeScroller, BlzFrameGetValue(codeScroller) + 2)
else
BlzFrameSetValue(codeScroller, BlzFrameGetValue(codeScroller) - 2)
end
end)
cursorFrame = BlzCreateFrameByType("BACKDROP", "", codeEditorParent, "", 0)
BlzFrameSetSize(cursorFrame, 0.0008, editor.LINE_SPACING)
BlzFrameSetTexture(cursorFrame, "ReplaceableTextures\\TeamColor\\TeamColor21.blp", 0, true)
BlzFrameSetEnable(cursorFrame, false)
BlzFrameSetVisible(cursorFrame, false)
TriggerAddAction(trigEnter, InsertCodeLine)
TriggerAddAction(trigStop, PutStop)
helperFrame = BlzCreateFrame("TextMessage", codeEditorParent, 0, 0)
if GetHandleId(helperFrame) == 0 then
if Debug then
Debug.throwError("Missing import: NeatTextMessage.fdf.")
end
end
helperText = BlzFrameGetChild(helperFrame, 0)
functionPreviewBox = BlzFrameGetChild(helperFrame, 1)
BlzFrameSetAllPoints(functionPreviewBox, helperFrame)
BlzFrameSetVisible(helperFrame, false)
BlzFrameSetLevel(helperFrame, 3)
BlzFrameSetLevel(helperText, 1)
BlzFrameSetEnable(helperFrame, false)
BlzFrameSetEnable(helperText, false)
BlzFrameSetEnable(functionPreviewBox, false)
if editor.USE_MONOSPACE_FONT then
widthTestFrame = BlzCreateFrame("Consolas", BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), 0, 0)
else
widthTestFrame = BlzCreateFrameByType("TEXT", "", BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), "", 0)
end
BlzFrameSetVisible(widthTestFrame, false)
currentLineHighlight = BlzCreateFrameByType("BACKDROP", "", codeEditorParent, "", 0)
BlzFrameSetTexture(currentLineHighlight, "currentLine.blp", 0, true)
BlzFrameSetVisible(currentLineHighlight, false)
BlzFrameSetAbsPoint(codeEditorParent, FRAMEPOINT_TOPLEFT, editor.COLLAPSED_X, editor.Y_TOP)
BlzFrameSetSize(codeEditorParent, editor.COLLAPSED_WIDTH, editor.MAX_LINES_ON_SCREEN*editor.LINE_SPACING + editor.CODE_TOP_INSET + editor.CODE_BOTTOM_INSET)
CreateToggleExpandButton("ExpandEditorButton", "expandEditor.blp", codeEditorParent)
local closeButton = BlzCreateFrame("CloseEditorButton", codeEditorParent, 0, 0)
BlzFrameSetPoint(closeButton, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPRIGHT, -editor.CLOSE_BUTTON_INSET - editor.CLOSE_BUTTON_SIZE, -editor.CLOSE_BUTTON_INSET - editor.CLOSE_BUTTON_SIZE)
BlzFrameSetPoint(closeButton, FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPRIGHT, -editor.CLOSE_BUTTON_INSET, -editor.CLOSE_BUTTON_INSET)
local icon = BlzFrameGetChild(closeButton, 0)
local iconClicked = BlzFrameGetChild(closeButton, 1)
local iconHighlight = BlzFrameGetChild(closeButton, 2)
BlzFrameSetAllPoints(icon, closeButton)
BlzFrameSetTexture(icon, "closeEditor.blp", 0, true)
BlzFrameSetTexture(iconClicked, "closeEditor.blp", 0, true)
BlzFrameClearAllPoints(iconHighlight)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_BOTTOMLEFT, closeButton, FRAMEPOINT_BOTTOMLEFT, 0.00375, 0.00375)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_TOPRIGHT, closeButton, FRAMEPOINT_TOPRIGHT, -0.00375, -0.00375)
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, closeButton, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, HideCodeEditor)
------------------------------------------------------------------------------------------------------------------------
--Bottom Bar
------------------------------------------------------------------------------------------------------------------------
local function Execute()
BlzFrameSetEnable(buttons.compile, false)
BlzFrameSetEnable(buttons.compile, true)
BlzFrameSetVisible(currentLineHighlight, false)
BlzFrameSetVisible(errorHighlight, false)
BlzFrameSetVisible(variableViewer.parent, flags.debugMode)
for i = 1, numVisibleVars do
BlzFrameSetVisible(variableViewer.frames[i], false)
BlzFrameSetVisible(variableViewer.valueFrames[i], false)
end
numVisibleVars = 0
if variableViewerIsExpanded then
BlzFrameSetSize(variableViewer.parent, variableViewer.WIDTH, editor.LINE_SPACING + 2*variableViewer.LINE_VERTICAL_INSET)
end
for i = 1, editor.MAX_LINES_ON_SCREEN do
BlzFrameSetFocus(codeLineFrames[i], false)
BlzFrameSetEnable(codeLineFrames[i], false)
BlzFrameSetEnable(codeLineFrames[i], true)
end
local lines = {}
local currentLines = codeLines[currentTab][step[currentTab]]
for i = 1, highestNonEmptyLine[currentTab][step[currentTab]] do
lines[i] = currentLines[i]
end
canJumpToError = true
CompileFunction(lines, {
debugMode = flags.debugMode,
runInit = flags.runInit,
persistUpvalues = flags.persistUpvalues,
cleanHandles = flags.cleanHandles,
tab = currentTab
})
end
local function Undo()
BlzFrameSetEnable(buttons.undo, false)
BlzFrameSetEnable(buttons.undo, true)
BlzFrameSetVisible(variableViewer.parent, false)
BlzFrameSetVisible(errorHighlight, false)
hasError[currentTab] = false
if step[currentTab] > 1 then
step[currentTab] = step[currentTab] - 1
UpdateAllLines()
BlzFrameSetEnable(buttons.redo, true)
if step[currentTab] == 1 then
BlzFrameSetEnable(buttons.undo, false)
end
BlzFrameSetMinMaxValue(codeScroller, 0, highestNonEmptyLine[currentTab][step[currentTab]])
end
SetLineOffset(lineNumberOffset[currentTab])
JumpWindow(lineNumberOfEdit[currentTab][step[currentTab] + 1])
cursor.adjustedLine = lineNumberOfEdit[currentTab][step[currentTab] + 1]
SetCursorPos(posOfEdit[currentTab][step[currentTab] + 1])
cursor.rawLine = cursor.adjustedLine - lineNumberOffset[currentTab]
SetCursorX(codeLines[currentTab][step[currentTab]][cursor.adjustedLine])
BlzFrameSetFocus(enterBox, true)
editBoxFocused = true
BlzFrameSetVisible(cursorFrame, true)
changeMade = true
end
local function Redo()
BlzFrameSetEnable(buttons.redo, false)
BlzFrameSetEnable(buttons.redo, true)
BlzFrameSetVisible(variableViewer.parent, false)
BlzFrameSetVisible(errorHighlight, false)
hasError[currentTab] = false
if step[currentTab] < maxRedo[currentTab] then
step[currentTab] = step[currentTab] + 1
UpdateAllLines()
if step[currentTab] == maxRedo[currentTab] then
BlzFrameSetEnable(buttons.redo, false)
end
BlzFrameSetEnable(buttons.undo, true)
BlzFrameSetMinMaxValue(codeScroller, 0, highestNonEmptyLine[currentTab][step[currentTab]])
end
SetLineOffset(lineNumberOffset[currentTab])
JumpWindow(lineNumberOfEdit[currentTab][step[currentTab]])
cursor.adjustedLine = lineNumberOfEdit[currentTab][step[currentTab]]
SetCursorPos(posOfEdit[currentTab][step[currentTab]])
cursor.rawLine = cursor.adjustedLine - lineNumberOffset[currentTab]
SetCursorX(codeLines[currentTab][step[currentTab]][cursor.adjustedLine])
editBoxFocused = true
BlzFrameSetFocus(enterBox, true)
BlzFrameSetVisible(cursorFrame, true)
changeMade = true
end
local function ClearCode()
BlzFrameSetEnable(buttons.clearButton, false)
BlzFrameSetEnable(buttons.clearButton, true)
BlzFrameSetVisible(variableViewer.parent, false)
BlzFrameSetVisible(errorHighlight, false)
hasError[currentTab] = false
IncrementStep()
local currentLines = codeLines[currentTab][step[currentTab]]
for i = 1, highestNonEmptyLine[currentTab][step[currentTab] - 1] do
currentLines[i] = ""
coloredCodeLines[currentTab][step[currentTab]][i] = ""
end
highestNonEmptyLine[currentTab][step[currentTab]] = 0
BlzFrameSetMinMaxValue(codeScroller, 0, 0)
UpdateAllLines()
end
local function Export()
BlzFrameSetEnable(buttons.export, false)
BlzFrameSetEnable(buttons.export, true)
local currentLines = codeLines[currentTab][step[currentTab]]
local hasCode
for i = 1, highestNonEmptyLine[currentTab][step[currentTab]] do
if find(currentLines[i], "^\x25s*$") == nil then
hasCode = true
break
end
end
if not hasCode then
print("|cffff5555ERROR: Could not export code. Tab is empty")
PlayError()
return
end
if FileIO then
local saveString = setmetatable({}, {__concat = function(self, str) self[#self + 1] = str return self end})
for i = 1, highestNonEmptyLine[currentTab][step[currentTab]] do
if currentLines[i]:match("^\x25s*$") ~= nil then
saveString = saveString .. "\n"
else
saveString = saveString .. currentLines[i]:gsub("\x25[\x25[", "[!?["):gsub("\x25]\x25]", "]!?]") .. "\n"
end
end
FileIO.Save(editor.EXPORT_SUBFOLDER .. "\\WSCodeLoadFile" .. currentTab:gsub(" ", "") .. ".txt", table.concat(saveString):gsub("\\\\", "\\"))
end
PreloadGenClear()
PreloadGenStart()
for i = 1, highestNonEmptyLine[currentTab][step[currentTab]] do
Preload(currentLines[i]:gsub("\\\\", "\\"):gsub("||", "|"))
end
PreloadGenEnd(editor.EXPORT_SUBFOLDER .. "\\WSCodeExport" .. currentTab:gsub(" ", "") .. ".txt")
print("Written code to CustomMapData\\" .. editor.EXPORT_SUBFOLDER .. "\\WSCodeExport" .. currentTab:gsub(" ", "") .. ".txt")
end
local function LoadFromFile()
BlzFrameSetEnable(buttons.load, false)
BlzFrameSetEnable(buttons.load, true)
BlzFrameSetEnable(buttons.undo, true)
BlzFrameSetEnable(buttons.redo, false)
local str = FileIO.Load(editor.EXPORT_SUBFOLDER .. "\\WSCodeLoadFile" .. currentTab:gsub(" ", "") .. ".txt")
if not str then
print("|cffff5555ERROR: No file found at path " .. editor.EXPORT_SUBFOLDER .. "\\WSCodeLoadFile" .. currentTab:gsub(" ", "") .. ".|r")
return
end
IncrementStep()
local currentLines = codeLines[currentTab][step[currentTab]]
local i = highestNonEmptyLine[currentTab][step[currentTab]] == 0 and 0 or (highestNonEmptyLine[currentTab][step[currentTab]] + 1)
local matchPos
local startPos = 1
while true do
matchPos = find(str, "\n", startPos)
if not matchPos then
break
end
i = i + 1
currentLines[i] = sub(str, startPos, matchPos - 1):gsub("\x25[!\x25?\x25[", "[["):gsub("\x25]!\x25?\x25]", "]]")
coloredCodeLines[currentTab][step[currentTab]][i] = GetColoredText(currentLines[i], i, currentTab)
if i >= 1 and i <= editor.MAX_LINES_ON_SCREEN then
BlzFrameSetText(codeLineFrames[i - lineNumberOffset[currentTab]], coloredCodeLines[currentTab][step[currentTab]][i])
end
startPos = matchPos + 1
end
highestNonEmptyLine[currentTab][step[currentTab]] = i
BlzFrameSetMinMaxValue(codeScroller, 0, highestNonEmptyLine[currentTab][step[currentTab]])
SetLineOffset(lineNumberOffset[currentTab])
UpdateAllLines()
end
local function ToggleHideEditor()
if not codeEditorParent or not BlzFrameIsVisible(codeEditorParent) then
EnableEditor(user)
else
BlzFrameSetVisible(codeEditorParent, false)
editor.ON_DISABLE()
if compiler.WRAP_ALL_BLIZZARD_API_POINTS == "editoropen" then
coroutineWrapEnabled = false
end
end
end
local function ToggleHaltAlice()
if not aliceIsHalted then
ALICE_Halt()
else
ALICE_Resume()
end
end
local maxButtons = 10
if FileIO then
maxButtons = maxButtons + 1
end
if ALICE_Config then
maxButtons = maxButtons + 2
end
local function CreateBottomButton(index, texture, callback, tooltip, extendedTooltip)
local button = BlzCreateFrame("BottomBarButton", codeEditorParent, 0, 0)
BlzFrameSetPoint(button, FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_BOTTOM, (index - 1 - maxButtons/2)*editor.BOTTOM_BUTTON_SIZE, editor.BOTTOM_BUTTON_VERTICAL_SHIFT)
BlzFrameSetPoint(button, FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_BOTTOM, (index - maxButtons/2)*editor.BOTTOM_BUTTON_SIZE, editor.BOTTOM_BUTTON_VERTICAL_SHIFT + editor.BOTTOM_BUTTON_SIZE)
local icon = BlzFrameGetChild(button,0)
local iconClicked = BlzFrameGetChild(button,1)
BlzFrameClearAllPoints(iconClicked)
BlzFrameSetPoint(iconClicked, FRAMEPOINT_BOTTOMLEFT, icon, FRAMEPOINT_BOTTOMLEFT, 0.0005, 0.0005)
BlzFrameSetPoint(iconClicked, FRAMEPOINT_TOPRIGHT, icon, FRAMEPOINT_TOPRIGHT, -0.0005, -0.0005)
BlzFrameSetTexture(icon, texture, 0, true)
BlzFrameSetTexture(iconClicked, texture, 0, true)
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, button, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, callback)
local tooltipFrame = BlzCreateFrame("CustomTooltip", codeEditorParent, 0, 0)
if GetHandleId(tooltipFrame) == 0 then
error("Missing import: CustomTooltip.fdf.")
end
BlzFrameSetPoint(tooltipFrame, FRAMEPOINT_BOTTOMRIGHT, codeEditorParent, FRAMEPOINT_BOTTOMRIGHT, -editor.BOTTOM_BUTTON_VERTICAL_SHIFT - editor.BOTTOM_BUTTON_SIZE, editor.BOTTOM_BUTTON_VERTICAL_SHIFT + editor.BOTTOM_BUTTON_SIZE)
BlzFrameSetTooltip(button, tooltipFrame)
local title = BlzFrameGetChild(tooltipFrame, 0)
local text = BlzFrameGetChild(tooltipFrame, 1)
BlzFrameSetText(title, "|cffffcc00" .. tooltip .. "|r")
BlzFrameSetText(text, extendedTooltip)
BlzFrameSetSize(text, 0.22 - 0.012, 0.0)
BlzFrameSetSize(tooltipFrame, 0.22, BlzFrameGetHeight(text) + 0.035)
return button
end
local function PullButton()
BlzFrameSetEnable(buttons.pull, false)
BlzFrameSetEnable(buttons.pull, true)
if not BlzFrameIsVisible(nameDialog.parent) then
ClearTextMessages()
BlzFrameSetVisible(nameDialog.parent, true)
BlzFrameSetFocus(nameDialog.textField, true)
BlzFrameSetText(nameDialog.title, "|cffffcc00Enter Script Name|r")
BlzFrameSetText(nameDialog.textField, "")
end
end
local function ClearAllStops()
BlzFrameSetEnable(buttons.clearStops, false)
BlzFrameSetEnable(buttons.clearStops, true)
for i = 1, highestNonEmptyLine[currentTab][step[currentTab]] do
if lines.debugState[currentTab][i] ~= 0 then
lines.debugState[currentTab][i] = 0
BlzFrameSetTexture(BlzFrameGetChild(stopButtons[i - lineNumberOffset[currentTab]], 0), LINE_STATE_ICONS[0], 0, true)
end
end
end
buttons.compile = CreateBottomButton(1, "ReplaceableTextures\\CommandButtons\\BTNCleavingAttack.blp", Execute, "Execute", "Executes the code in the current currentTab after applying transformations based on the flags set.")
buttons.flags = CreateBottomButton(2, "ReplaceableTextures\\CommandButtons\\BTNRallyPoint.blp", function() BlzFrameSetEnable(buttons.flags, false) BlzFrameSetEnable(buttons.flags, true) BlzFrameSetVisible(flagsMenuParent, not BlzFrameIsVisible(flagsMenuParent)) end, "Flags", "Set various flags that influence code compilation and execution.")
buttons.undo = CreateBottomButton(3, "ReplaceableTextures\\CommandButtons\\BTNReplay-Loop.blp", Undo, "Undo", "Undo the last edit.")
BlzFrameSetEnable(buttons.undo, false)
BlzFrameSetTexture(BlzFrameGetChild(buttons.undo, 2), "ReplaceableTextures\\CommandButtonsDisabled\\DISBTNReplay-Loop.blp", 0, true)
buttons.redo = CreateBottomButton(4, "Redo.blp", Redo, "Redo", "Redo the last edit.")
BlzFrameSetEnable(buttons.redo, false)
BlzFrameSetTexture(BlzFrameGetChild(buttons.redo, 2), "RedoDisabled.blp", 0, true)
CreateBottomButton(5, "ReplaceableTextures\\CommandButtons\\BTNDemolish.blp", ClearCode, "Clear", "Clears the code in the current currentTab.")
buttons.search = CreateBottomButton(6, "ReplaceableTextures\\CommandButtons\\BTNReveal.blp", ShowSearchBar, "Search", "Open the search bar.")
buttons.clearStops = CreateBottomButton(7, "ReplaceableTextures\\CommandButtons\\BTNDispelMagic.blp", ClearAllStops, "Remove Breakpoints", "Removes all breakpoints and spyglasses from the current currentTab.")
CreateBottomButton(8, "ReplaceableTextures\\CommandButtons\\BTNUnload.blp", Export, "Export Code", "Exports the code in the current currentTab to a save file in the Warcraft 3\\CustomMapData\\ folder, where the subfolder can be set in the editor. Generates one file that is used by WS Code to reload the code and one file that should be used to export the code to an external editor. The file suffix is based on the current currentTab's name.")
if FileIO then
CreateBottomButton(9, "ReplaceableTextures\\CommandButtons\\BTNLoad.blp", LoadFromFile, "Load From File", "Loads the code previously saved to a file with the Export button. The file loaded is based on the name of the current currentTab.")
buttons.pull = CreateBottomButton(10, "ReplaceableTextures\\CommandButtons\\BTNNagaUnBurrow.blp", PullButton, "Pull Script", "Loads a script file into a new currentTab. The script file must have been parsed by WSCode.Parse and is referenced by its name set in the Debug.beginFile call.")
buttons.benchmark = CreateBottomButton(11, "Benchmark.blp", Benchmark, "Benchmark", "Evaluates the execution time of the code in the current currentTab with no transformations. Do not create any leaking handles within the code you are benchmarking. Stop the benchmark by pressing this button again.")
if ALICE_Config then
buttons.nextStep = CreateBottomButton(12, "ReplaceableTextures\\CommandButtons\\BTNReplay-Play.blp", ALICE_NextStep, "Go To Next Step", "Go to the next step in the ALICE cycle.")
BlzFrameSetTexture(BlzFrameGetChild(buttons.nextStep, 2), "ReplaceableTextures\\CommandButtonsDisabled\\DISBTNReplay-Play.blp", 0, true)
BlzFrameSetEnable(buttons.nextStep, false)
buttons.haltALICE = CreateBottomButton(13, "ReplaceableTextures\\CommandButtons\\BTNReplay-Pause.blp", ToggleHaltAlice, "Pause/Resume", "Pause or resume the ALICE cycle.")
BlzFrameSetTexture(BlzFrameGetChild(buttons.haltALICE, 2), "ReplaceableTextures\\CommandButtonsDisabled\\DISBTNReplay-Pause.blp", 0, true)
BlzFrameSetEnable(buttons.nextStep, false)
end
else
buttons.pull = CreateBottomButton(9, "ReplaceableTextures\\CommandButtons\\BTNNagaUnBurrow.blp", PullButton, "Pull Script", "Loads a script file into a new currentTab. The script file must have been parsed by WSCode.Parse and is referenced by its name set in the Debug.beginFile call.")
buttons.benchmark = CreateBottomButton(10, "Benchmark.blp", Benchmark, "Benchmark", "Evaluates the execution time of the code in the current currentTab with no transformations. Do not create any leaking handles within the code you are benchmarking. Stop the benchmark by pressing this button again.")
if ALICE_Config then
buttons.nextStep = CreateBottomButton(11, "ReplaceableTextures\\CommandButtons\\BTNReplay-Play.blp", ALICE_NextStep, "Go To Next Step", "Go to the next step in the ALICE cycle.")
BlzFrameSetTexture(BlzFrameGetChild(buttons.nextStep, 2), "ReplaceableTextures\\CommandButtonsDisabled\\DISBTNReplay-Play.blp", 0, true)
BlzFrameSetEnable(buttons.nextStep, false)
buttons.haltALICE = CreateBottomButton(12, "ReplaceableTextures\\CommandButtons\\BTNReplay-Pause.blp", ToggleHaltAlice, "Pause/Resume", "Pause or resume the ALICE cycle.")
BlzFrameSetTexture(BlzFrameGetChild(buttons.haltALICE, 2), "ReplaceableTextures\\CommandButtonsDisabled\\DISBTNReplay-Pause.blp", 0, true)
BlzFrameSetEnable(buttons.nextStep, false)
end
end
if ALICE_Config then
local oldHalt = ALICE_Halt
ALICE_Halt = function(pauseGame)
oldHalt(pauseGame)
BlzFrameSetEnable(buttons.nextStep, true)
aliceIsHalted = true
BlzFrameSetTexture(BlzFrameGetChild(buttons.haltALICE, 0), "ReplaceableTextures\\CommandButtons\\BTNReplay-SpeedUp.blp", 0, true)
BlzFrameSetTexture(BlzFrameGetChild(buttons.haltALICE, 1), "ReplaceableTextures\\CommandButtons\\BTNReplay-SpeedUp.blp", 0, true)
BlzFrameSetTexture(BlzFrameGetChild(buttons.haltALICE, 2), "ReplaceableTextures\\CommandButtonsDisabled\\DISBTNReplay-SpeedUp.blp", 0, true)
end
local oldResume = ALICE_Resume
ALICE_Resume = function()
oldResume()
BlzFrameSetEnable(buttons.nextStep, false)
aliceIsHalted = false
BlzFrameSetTexture(BlzFrameGetChild(buttons.haltALICE, 0), "ReplaceableTextures\\CommandButtons\\BTNReplay-Pause.blp", 0, true)
BlzFrameSetTexture(BlzFrameGetChild(buttons.haltALICE, 1), "ReplaceableTextures\\CommandButtons\\BTNReplay-Pause.blp", 0, true)
BlzFrameSetTexture(BlzFrameGetChild(buttons.haltALICE, 2), "ReplaceableTextures\\CommandButtonsDisabled\\DISBTNReplay-Pause.blp", 0, true)
end
end
------------------------------------------------------------------------------------------------------------------------
--Flags Menu
------------------------------------------------------------------------------------------------------------------------
flagsMenuParent = BlzCreateFrame("CodeEditor", codeEditorParent, 0, 0)
BlzFrameSetLevel(flagsMenuParent, 3)
BlzFrameSetVisible(flagsMenuParent, false)
backdrop = BlzFrameGetChild(flagsMenuParent, 0)
BlzFrameSetAllPoints(backdrop, flagsMenuParent)
BlzFrameSetAlpha(backdrop, editor.BLACK_BACKDROP_ALPHA)
border = BlzFrameGetChild(flagsMenuParent, 1)
BlzFrameSetAllPoints(border, flagsMenuParent)
BlzFrameSetPoint(flagsMenuParent, FRAMEPOINT_BOTTOM, buttons.flags, FRAMEPOINT_TOP, 0, 0)
local function CreateFlagsButton(position, name, initiallyEnabled, callback)
local button = BlzCreateFrame("VariableViewerButton", flagsMenuParent, 0, 0)
local text = BlzFrameGetChild(button, 0)
BlzFrameSetEnable(text, false)
BlzFrameSetPoint(button, FRAMEPOINT_BOTTOMLEFT, flagsMenuParent, FRAMEPOINT_TOPLEFT, flagsMenu.TEXT_INSET, -flagsMenu.TEXT_INSET - position*editor.LINE_SPACING)
BlzFrameSetPoint(button, FRAMEPOINT_TOPRIGHT, flagsMenuParent, FRAMEPOINT_TOPLEFT, flagsMenu.WIDTH - flagsMenu.TEXT_INSET, -flagsMenu.TEXT_INSET - (position - 1)*editor.LINE_SPACING)
BlzFrameSetTextAlignment(text, TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_LEFT)
BlzFrameSetAllPoints(text, button)
indexFromFrame[button] = position
BlzFrameSetText(text, initiallyEnabled and "|cffffcc00" .. name .. "|r" or "|cff999999" .. name .. "|r")
local isEnabled = initiallyEnabled
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, button, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, function()
callback()
isEnabled = not isEnabled
BlzFrameSetText(text, isEnabled and "|cffffcc00" .. name .. "|r" or "|cff999999" .. name .. "|r")
end)
end
CreateFlagsButton(1, "Debug Mode", editor.DEBUG_MODE_ON_BY_DEFAULT, function()
flags.debugMode = not flags.debugMode
end)
flags.debugMode = editor.DEBUG_MODE_ON_BY_DEFAULT
CreateFlagsButton(2, "Run OnInit", editor.RUN_INIT_ON_BY_DEFAULT, function()
flags.runInit = not flags.runInit
end)
flags.runInit = editor.RUN_INIT_ON_BY_DEFAULT
CreateFlagsButton(3, "Clean Handles", editor.CLEAN_HANDLES_ON_BY_DEFAULT, function()
flags.cleanHandles = not flags.cleanHandles
end)
flags.cleanHandles = editor.CLEAN_HANDLES_ON_BY_DEFAULT
CreateFlagsButton(4, "Hide Globals", editor.HIDE_GLOBALS_ON_BY_DEFAULT, function()
flags.hideGlobals = not flags.hideGlobals
UpdateVariableViewer(lastViewedLineNumber[currentTab] or lastLineNumber[currentTab])
end)
flags.hideGlobals = editor.HIDE_GLOBALS_ON_BY_DEFAULT
CreateFlagsButton(5, "Hide Constants", editor.HIDE_CONSTANTS_ON_BY_DEFAULT, function()
flags.hideConstants = not flags.hideConstants
UpdateVariableViewer(lastViewedLineNumber[currentTab] or lastLineNumber[currentTab])
end)
flags.hideConstants = editor.HIDE_CONSTANTS_ON_BY_DEFAULT
CreateFlagsButton(6, "Persist Upvalues", editor.PERSIST_UPVALUES_ON_BY_DEFAULT, function()
flags.persistUpvalues = not flags.persistUpvalues
UpdateVariableViewer(lastViewedLineNumber[currentTab] or lastLineNumber[currentTab])
end)
flags.persistUpvalues = editor.PERSIST_UPVALUES_ON_BY_DEFAULT
if ALICE_Config then
CreateFlagsButton(7, "Auto-Halt ALICE", editor.HALT_ALICE_ON_BREAKPOINT_ON_BY_DEFAULT, function()
flags.haltAliceOnBreakpoint = not flags.haltAliceOnBreakpoint
end)
flags.haltAliceOnBreakpoint = editor.HALT_ALICE_ON_BREAKPOINT_ON_BY_DEFAULT
BlzFrameSetSize(flagsMenuParent, flagsMenu.WIDTH, 2*flagsMenu.TEXT_INSET + 7*editor.LINE_SPACING)
else
BlzFrameSetSize(flagsMenuParent, flagsMenu.WIDTH, 2*flagsMenu.TEXT_INSET + 6*editor.LINE_SPACING)
end
flags.minX = (2 - maxButtons/2 - 0.5)*editor.BOTTOM_BUTTON_SIZE - flagsMenu.WIDTH/2
flags.maxX = (2 - maxButtons/2 - 0.5)*editor.BOTTOM_BUTTON_SIZE + flagsMenu.WIDTH/2
flags.maxY = editor.Y_TOP - editor.CODE_TOP_INSET - editor.MAX_LINES_ON_SCREEN*editor.LINE_SPACING - editor.CODE_BOTTOM_INSET + BlzFrameGetHeight(flagsMenuParent)
------------------------------------------------------------------------------------------------------------------------
--Variable Viewer
------------------------------------------------------------------------------------------------------------------------
variableViewer.parent = BlzCreateFrame("CodeEditor", codeEditorParent, 0, 0)
BlzFrameSetLevel(variableViewer.parent, 2)
BlzFrameSetPoint(variableViewer.parent, FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPLEFT, 0, 0)
BlzFrameSetSize(variableViewer.parent, variableViewer.WIDTH, editor.LINE_SPACING + 2*variableViewer.LINE_VERTICAL_INSET)
BlzFrameSetVisible(variableViewer.parent, hasCodeOnInit)
backdrop = BlzFrameGetChild(variableViewer.parent, 0)
BlzFrameSetAllPoints(backdrop, variableViewer.parent)
BlzFrameSetAlpha(backdrop, editor.BLACK_BACKDROP_ALPHA)
border = BlzFrameGetChild(variableViewer.parent, 1)
BlzFrameSetAllPoints(border, variableViewer.parent)
buttons.back = BlzCreateFrameByType("GLUETEXTBUTTON", "", variableViewer.parent, "ScriptDialogButton", 0)
BlzFrameSetPoint(buttons.back, FRAMEPOINT_BOTTOMLEFT, variableViewer.parent, FRAMEPOINT_BOTTOMLEFT, variableViewer.WIDTH/2/0.8 - 0.025, -0.01)
BlzFrameSetPoint(buttons.back, FRAMEPOINT_TOPRIGHT, variableViewer.parent, FRAMEPOINT_BOTTOMLEFT, variableViewer.WIDTH/2/0.8 + 0.025, 0.015)
BlzFrameSetText(buttons.back, "Back")
BlzFrameSetScale(buttons.back, 0.8)
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, buttons.back, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, GoBack)
BlzFrameSetVisible(buttons.back, false)
CreateToggleExpandButton("CollapseEditorButton", "collapseEditor.blp", variableViewer.parent)
variableViewerTitle = BlzCreateFrameByType("TEXT", "", variableViewer.parent, "", 0)
BlzFrameSetText(variableViewerTitle, "|cffffcc00Variables")
BlzFrameSetSize(variableViewerTitle, 0, 0)
BlzFrameSetPoint(variableViewerTitle, FRAMEPOINT_TOPLEFT, variableViewer.parent, FRAMEPOINT_TOPLEFT, variableViewer.LINE_HORIZONTAL_INSET, editor.CHECKBOX_VERTICAL_SHIFT + editor.CHECKBOX_TEXT_VERTICAL_SHIFT)
BlzFrameSetEnable(variableViewerTitle, false)
variableViewer.functionFrames[1] = BlzCreateFrameByType("TEXT", "", variableViewer.parent, "", 0)
BlzFrameSetVisible(variableViewer.functionFrames[1], false)
BlzFrameSetText(variableViewer.functionFrames[1], "|cffffcc00Last Function Call|r")
BlzFrameSetScale(variableViewer.functionFrames[1], variableViewer.TEXT_SCALE)
BlzFrameSetSize(variableViewer.functionFrames[1], 0, 0)
BlzFrameSetEnable(variableViewer.functionFrames[1], false)
variableViewer.functionFrames[2] = BlzCreateFrameByType("TEXT", "", variableViewer.parent, "", 0)
BlzFrameSetPoint(variableViewer.functionFrames[2], FRAMEPOINT_TOPLEFT, variableViewer.functionFrames[1], FRAMEPOINT_TOPLEFT, 0, -editor.LINE_SPACING - variableViewer.FUNCTION_CALL_SPACING)
BlzFrameSetVisible(variableViewer.functionFrames[2], false)
BlzFrameSetEnable(variableViewer.functionFrames[2], false)
BlzFrameSetScale(variableViewer.functionFrames[2], variableViewer.TEXT_SCALE)
variableViewer.functionFrames[3] = BlzCreateFrameByType("TEXT", "", variableViewer.parent, "", 0)
BlzFrameSetVisible(variableViewer.functionFrames[3], false)
BlzFrameSetText(variableViewer.functionFrames[3], ")")
BlzFrameSetSize(variableViewer.functionFrames[3], 0, 0)
BlzFrameSetEnable(variableViewer.functionFrames[3], false)
BlzFrameSetScale(variableViewer.functionFrames[3], variableViewer.TEXT_SCALE)
------------------------------------------------------------------------------------------------------------------------
--Context Menu
------------------------------------------------------------------------------------------------------------------------
contextMenu.parent = BlzCreateFrame("CodeEditor", codeEditorParent, 0, 0)
BlzFrameSetLevel(contextMenu.parent, 3)
BlzFrameSetVisible(contextMenu.parent, false)
BlzFrameSetSize(contextMenu.parent, flagsMenu.WIDTH, 2*flagsMenu.TEXT_INSET + 6*editor.LINE_SPACING)
backdrop = BlzFrameGetChild(contextMenu.parent, 0)
BlzFrameSetAllPoints(backdrop, contextMenu.parent)
BlzFrameSetAlpha(backdrop, editor.BLACK_BACKDROP_ALPHA)
border = BlzFrameGetChild(contextMenu.parent, 1)
BlzFrameSetAllPoints(border, contextMenu.parent)
local function CreateContextButton(position, name, callback)
local button = BlzCreateFrame("VariableViewerButton", contextMenu.parent, 0, 0)
local text = BlzFrameGetChild(button, 0)
BlzFrameSetEnable(text, false)
BlzFrameSetPoint(button, FRAMEPOINT_BOTTOMLEFT, contextMenu.parent, FRAMEPOINT_TOPLEFT, flagsMenu.TEXT_INSET, -flagsMenu.TEXT_INSET - position*editor.LINE_SPACING)
BlzFrameSetPoint(button, FRAMEPOINT_TOPRIGHT, contextMenu.parent, FRAMEPOINT_TOPLEFT, flagsMenu.WIDTH - flagsMenu.TEXT_INSET, -flagsMenu.TEXT_INSET - (position - 1)*editor.LINE_SPACING)
BlzFrameSetTextAlignment(text, TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_LEFT)
BlzFrameSetAllPoints(text, button)
indexFromFrame[button] = position
BlzFrameSetText(text, name)
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, button, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, callback)
return button
end
contextMenu.gotodef = CreateContextButton(1, "Go to Definition", function()
local currentLines = codeLines[currentTab][step[currentTab]]
local word = sub(currentLines[selection.startLine], math.min(selection.startPos, selection.endPos) + 1, math.max(selection.startPos, selection.endPos))
local start, stop
for i = 1, highestNonEmptyLine[currentTab][step[currentTab]] do
--local var1, var, word / word = / function word
if find(currentLines[i], "local\x25s+([\x25w_\x25s,]-)\x25f[\x25a]" .. word .. "\x25f[^\x25w_]") ~= nil or find(currentLines[i], word .. "\x25s*=[^=]") ~= nil or find(currentLines[i], "function\x25s+" .. word) ~= nil then
start, stop = find(currentLines[i], word)
end
if start then
selection.startLine = i
selection.endLine = i
selection.startPos = start - 1
selection.endPos = stop
SetSelection(GetTextWidth(sub(currentLines[i], 1, stop)))
cursor.adjustedLine = selection.startLine
cursor.rawLine = selection.startLine - lineNumberOffset[currentTab]
SetCursorPos(selection.endPos)
SetCursorX(sub(codeLines[currentTab][step[currentTab]][cursor.adjustedLine], 1, selection.endPos))
JumpWindow(i)
BlzFrameSetVisible(contextMenu.parent, false)
BlzFrameSetFocus(enterBox, true)
editBoxFocused = true
BlzFrameSetVisible(cursorFrame, true)
return
end
end
PlayError()
BlzFrameSetVisible(contextMenu.parent, false)
end)
contextMenu.execute = CreateContextButton(2, "Execute", function()
local lines = ConvertSelectionToLines()
CompileFunction(lines, {
tab = currentTab
})
BlzFrameSetVisible(contextMenu.parent, false)
end)
contextMenu.evaluate = CreateContextButton(3, "Evaluate", function()
local lines = ConvertSelectionToLines()
if #lines > 1 then
print("|cffff5555ERROR: Cannot evaluate expression spanning multiple lines.|r")
PlayError()
return
end
local line = lines[1]
local alteredLine = line
local finds = {}
local stringList = {}
line = line:gsub('"[^"]*"', function(str)
stringList[#stringList + 1] = str
return "__DSTRING" .. #stringList
end)
line = line:gsub("'[^']*'", function(str)
stringList[#stringList + 1] = str
return "__SSTRING" .. #stringList
end)
for match in line:gmatch("\x25f[\x25a_]([\x25w_]+)") do
if not finds[match] and not LUA_KEYWORDS[match] then
local level = visibleVarsOfLine[currentTab][lastLineNumber[currentTab]][match]
if level then
alteredLine = alteredLine:gsub("\x25f[\x25a_]" .. match .. "\x25f[^\x25w_]", "WSDebug.VarTable[" .. currentTab .. "][" .. level .. "][\"" .. match .. "\"]")
end
end
finds[match] = true
end
for i = 1, #stringList do
alteredLine = alteredLine:gsub("__DSTRING" .. i, '"' .. stringList[i] .. '"'):gsub("__SSTRING" .. i, "'" .. stringList[i] .. "'")
end
local func = load("print([==[" .. line .. "\n>> ]==] .." .. alteredLine .. ")", "WSCodeEvaluate", "t")
if not func then
print("|cffff5555ERROR: Invalid expression.|r")
return
end
func()
BlzFrameSetVisible(contextMenu.parent, false)
end)
contextMenu.cut = CreateContextButton(4, "Cut", function()
clipboard = ConvertSelectionToLines()
IncrementStep()
DeleteSelection()
BlzFrameSetVisible(contextMenu.parent, false)
end)
contextMenu.copy = CreateContextButton(5, "Copy", function()
clipboard = ConvertSelectionToLines()
BlzFrameSetVisible(contextMenu.parent, false)
end)
contextMenu.paste = CreateContextButton(6, "Paste", function()
if clipboard and #clipboard > 0 then
IncrementStep()
if selection.hasSelection then
DeleteSelection()
end
local text = clipboard[1]
for i = 2, #clipboard do
text = text .. "\n" .. clipboard[i]
end
ChangeCodeLine(text)
BlzFrameSetVisible(contextMenu.parent, false)
BlzFrameSetFocus(enterBox, true)
end
end)
------------------------------------------------------------------------------------------------------------------------
--Tab Navigators
------------------------------------------------------------------------------------------------------------------------
trig = CreateTrigger()
TriggerAddAction(trig, ClickTabButton)
local name = tabNames[1]
tabNavigator.frames[name] = BlzCreateFrame("TabNavigator", codeEditorParent, 0, 0)
tabNavigator.titles[name] = BlzFrameGetChild(tabNavigator.frames[name], 2)
tabNavigator.highlights[name] = BlzFrameGetChild(tabNavigator.frames[name], 3)
BlzFrameSetAlpha(tabNavigator.highlights[name], 0)
BlzFrameSetText(tabNavigator.titles[name], editor.TAB_NAVIGATOR_SELECTED_COLOR .. name .. "|r")
BlzFrameSetSize(tabNavigator.titles[name], 0, 0)
local width = BlzFrameGetWidth(tabNavigator.titles[name])
tabNavigator.widths[name] = math.max(tabNavigator.WIDTH, width + 2*tabNavigator.TEXT_INSET)
BlzFrameSetAllPoints(tabNavigator.titles[name], tabNavigator.frames[name])
BlzFrameSetTextAlignment(tabNavigator.titles[name], TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_CENTER)
BlzFrameSetEnable(tabNavigator.titles[name], false)
BlzFrameSetSize(tabNavigator.frames[name], tabNavigator.widths[name], tabNavigator.HEIGHT)
BlzFrameSetPoint(tabNavigator.frames[name], FRAMEPOINT_BOTTOMLEFT, codeEditorParent, FRAMEPOINT_TOPLEFT, 0, tabNavigator.VERTICAL_SHIFT)
BlzFrameSetAllPoints(BlzFrameGetChild(tabNavigator.frames[name], 1), tabNavigator.frames[name])
BlzFrameSetAlpha(BlzFrameGetChild(tabNavigator.frames[name], 1), editor.BLACK_BACKDROP_ALPHA)
BlzTriggerRegisterFrameEvent(trig, tabNavigator.frames[name], FRAMEEVENT_CONTROL_CLICK)
tabNumberFromFrame[tabNavigator.frames[name]] = 1
for t = 2, numTabs do
name = tabNames[t]
tabNavigator.frames[name] = BlzCreateFrame("TabNavigator", codeEditorParent, 0, 0)
tabNavigator.titles[name] = BlzFrameGetChild(tabNavigator.frames[name], 2)
tabNavigator.highlights[name] = BlzFrameGetChild(tabNavigator.frames[name], 3)
BlzFrameSetText(tabNavigator.titles[name], editor.TAB_NAVIGATOR_UNSELECTED_COLOR .. name .. "|r")
BlzFrameSetSize(tabNavigator.titles[name], 0, 0)
width = BlzFrameGetWidth(tabNavigator.titles[name])
tabNavigator.widths[name] = math.max(tabNavigator.WIDTH, width + 2*tabNavigator.TEXT_INSET)
BlzFrameSetAllPoints(tabNavigator.titles[name], tabNavigator.frames[name])
BlzFrameSetTextAlignment(tabNavigator.titles[name], TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_CENTER)
BlzFrameSetEnable(tabNavigator.titles[name], false)
BlzFrameSetSize(tabNavigator.frames[name], tabNavigator.widths[name], tabNavigator.HEIGHT)
BlzFrameSetPoint(tabNavigator.frames[name], FRAMEPOINT_BOTTOMLEFT, tabNavigator.frames[tabNames[t - 1]], FRAMEPOINT_BOTTOMRIGHT, -tabNavigator.OVERLAP, 0)
BlzFrameSetAllPoints(BlzFrameGetChild(tabNavigator.frames[name], 1), tabNavigator.frames[name])
BlzFrameSetAlpha(BlzFrameGetChild(tabNavigator.frames[name], 1), editor.BLACK_BACKDROP_ALPHA)
BlzTriggerRegisterFrameEvent(trig, tabNavigator.frames[name], FRAMEEVENT_CONTROL_CLICK)
tabNumberFromFrame[tabNavigator.frames[name]] = t
end
for t = 1, numTabs do
closeButton = BlzCreateFrame("CloseEditorButton", tabNavigator.frames[tabNames[t]], 0, 0)
BlzFrameSetPoint(closeButton, FRAMEPOINT_BOTTOMLEFT, tabNavigator.frames[tabNames[t]], FRAMEPOINT_TOPRIGHT, -tabNavigator.CLOSE_BUTTON_INSET - tabNavigator.CLOSE_BUTTON_SIZE, -tabNavigator.CLOSE_BUTTON_INSET - tabNavigator.CLOSE_BUTTON_SIZE)
BlzFrameSetPoint(closeButton, FRAMEPOINT_TOPRIGHT, tabNavigator.frames[tabNames[t]], FRAMEPOINT_TOPRIGHT, -tabNavigator.CLOSE_BUTTON_INSET, -tabNavigator.CLOSE_BUTTON_INSET)
icon = BlzFrameGetChild(closeButton, 0)
iconClicked = BlzFrameGetChild(closeButton, 1)
iconHighlight = BlzFrameGetChild(closeButton, 2)
BlzFrameSetAllPoints(icon, closeButton)
BlzFrameSetTexture(icon, "closeEditor.blp", 0, true)
BlzFrameSetTexture(iconClicked, "closeEditor.blp", 0, true)
BlzFrameClearAllPoints(iconHighlight)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_BOTTOMLEFT, closeButton, FRAMEPOINT_BOTTOMLEFT, 0.00375, 0.00375)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_TOPRIGHT, closeButton, FRAMEPOINT_TOPRIGHT, -0.00375, -0.00375)
BlzFrameSetVisible(closeButton, false)
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, closeButton, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, CloseTab)
tabNumberFromFrame[closeButton] = t
tabNavigator.closeButtons[tabNames[t]] = closeButton
end
addTabFrame = BlzCreateFrame("TabNavigator", codeEditorParent, 0, 0)
addTabTitle = BlzFrameGetChild(addTabFrame, 2)
BlzFrameSetText(addTabTitle, editor.TAB_NAVIGATOR_SELECTED_COLOR .. "+" .. "|r")
BlzFrameSetAllPoints(addTabTitle, addTabFrame)
BlzFrameSetTextAlignment(addTabTitle, TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_CENTER)
BlzFrameSetEnable(addTabTitle, false)
BlzFrameSetSize(addTabFrame, tabNavigator.HEIGHT, tabNavigator.HEIGHT)
BlzFrameSetPoint(addTabFrame, FRAMEPOINT_BOTTOMLEFT, tabNavigator.frames[tabNames[numTabs]], FRAMEPOINT_BOTTOMRIGHT, -tabNavigator.OVERLAP, 0)
BlzFrameSetAllPoints(BlzFrameGetChild(addTabFrame, 1), addTabFrame)
BlzFrameSetAlpha(BlzFrameGetChild(addTabFrame, 1), editor.BLACK_BACKDROP_ALPHA)
AdjustTabWidths()
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, addTabFrame, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, function()
BlzFrameSetEnable(addTabFrame, false)
BlzFrameSetEnable(addTabFrame, true)
if not BlzFrameIsVisible(nameDialog.parent) then
ClearTextMessages()
BlzFrameSetVisible(nameDialog.parent, true)
BlzFrameSetFocus(nameDialog.textField, true)
BlzFrameSetText(nameDialog.title, "|cffffcc00Enter Tab Name|r")
for i = numTabs + 1, 1, -1 do
local occupado = false
for j = 1, numTabs do
if tabNames[j] == "Tab " .. i then
occupado = true
break
end
end
if not occupado then
BlzFrameSetText(nameDialog.textField, "Tab " .. i)
return
end
end
end
end)
------------------------------------------------------------------------------------------------------------------------
--Enter Name Dialog
------------------------------------------------------------------------------------------------------------------------
nameDialog.parent = BlzCreateFrame("CodeEditor", codeEditorParent, 0, 0)
BlzFrameSetLevel(nameDialog.parent, 3)
BlzFrameSetAbsPoint(nameDialog.parent, FRAMEPOINT_CENTER, 0.4, 0.3)
BlzFrameSetSize(nameDialog.parent, nameDialog.WIDTH, nameDialog.HEIGHT)
backdrop = BlzFrameGetChild(nameDialog.parent, 0)
BlzFrameSetAllPoints(backdrop, nameDialog.parent)
BlzFrameSetAlpha(backdrop, editor.BLACK_BACKDROP_ALPHA)
border = BlzFrameGetChild(nameDialog.parent, 1)
BlzFrameSetAllPoints(border, nameDialog.parent)
nameDialog.title = BlzCreateFrameByType("TEXT", "", nameDialog.parent, "", 0)
BlzFrameSetPoint(nameDialog.title, FRAMEPOINT_TOPRIGHT, nameDialog.parent, FRAMEPOINT_TOPRIGHT, 0, -nameDialog.TITLE_VERTICAL_SHIFT)
BlzFrameSetPoint(nameDialog.title, FRAMEPOINT_BOTTOMLEFT, nameDialog.parent, FRAMEPOINT_BOTTOMLEFT, 0, nameDialog.TITLE_VERTICAL_SHIFT)
BlzFrameSetTextAlignment(nameDialog.title, TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_CENTER)
BlzFrameSetEnable(nameDialog.title, false)
nameDialog.textField = BlzCreateFrame("EscMenuEditBoxTemplate", nameDialog.parent, 0, 0)
BlzFrameSetPoint(nameDialog.textField, FRAMEPOINT_BOTTOMLEFT, nameDialog.parent, FRAMEPOINT_TOPLEFT, nameDialog.ENTER_BOX_HORIZONTAL_INSET, -nameDialog.ENTER_BOX_VERTICAL_SHIFT - nameDialog.ENTER_BOX_HEIGHT)
BlzFrameSetPoint(nameDialog.textField, FRAMEPOINT_TOPRIGHT, nameDialog.parent, FRAMEPOINT_TOPRIGHT, -nameDialog.ENTER_BOX_HORIZONTAL_INSET, -nameDialog.ENTER_BOX_VERTICAL_SHIFT)
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, nameDialog.textField, FRAMEEVENT_EDITBOX_ENTER)
TriggerAddAction(trig, function()
BlzFrameSetVisible(nameDialog.parent, false)
BlzFrameSetVisible(helperFrame, false)
BlzFrameSetFocus(nameDialog.textField, false)
if find(BlzFrameGetText(nameDialog.title), "Tab") then
local enteredName = BlzGetTriggerFrameText()
for i = 1, #fileNames do
if fileNames[i] == enteredName then
print("|cffff5555ERROR: A file with that name already exists. If you want to add that script to the editor, use Pull Script button instead.|r")
return
end
end
for i = 1, numTabs do
if tabNames[i] == enteredName then
print("|cffff5555ERROR: A tab with that name already exists.")
return
end
end
AddTab(enteredName)
else
Pull(BlzGetTriggerFrameText())
end
end)
local closeNameDialogButton = BlzCreateFrame("CloseEditorButton", nameDialog.parent, 0, 0)
BlzFrameSetPoint(closeNameDialogButton, FRAMEPOINT_BOTTOMLEFT, nameDialog.parent, FRAMEPOINT_TOPRIGHT, -searchBar.SEARCH_BUTTON_INSET - searchBar.BUTTON_SIZE, -searchBar.SEARCH_BUTTON_INSET - searchBar.BUTTON_SIZE)
BlzFrameSetPoint(closeNameDialogButton, FRAMEPOINT_TOPRIGHT, nameDialog.parent, FRAMEPOINT_TOPRIGHT, -searchBar.SEARCH_BUTTON_INSET, -searchBar.SEARCH_BUTTON_INSET)
icon = BlzFrameGetChild(closeNameDialogButton, 0)
iconClicked = BlzFrameGetChild(closeNameDialogButton, 1)
iconHighlight = BlzFrameGetChild(closeNameDialogButton, 2)
BlzFrameSetAllPoints(icon, closeNameDialogButton)
BlzFrameSetTexture(icon, "closeEditor.blp", 0, true)
BlzFrameSetTexture(iconClicked, "closeEditor.blp", 0, true)
BlzFrameClearAllPoints(iconHighlight)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_BOTTOMLEFT, closeNameDialogButton, FRAMEPOINT_BOTTOMLEFT, 0.00375, 0.00375)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_TOPRIGHT, closeNameDialogButton, FRAMEPOINT_TOPRIGHT, -0.00375, -0.00375)
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, closeNameDialogButton, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, function()
BlzFrameSetVisible(nameDialog.parent, false)
BlzFrameSetVisible(helperFrame, false)
BlzFrameSetFocus(nameDialog.textField, false)
end)
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, nameDialog.textField, FRAMEEVENT_EDITBOX_TEXT_CHANGED)
TriggerAddAction(trig, function()
CheckForAutoCompleteSuggestions(BlzGetTriggerFrameText(), nil, true)
end)
BlzFrameSetVisible(nameDialog.parent, false)
------------------------------------------------------------------------------------------------------------------------
--Search Bar
------------------------------------------------------------------------------------------------------------------------
searchBar.parent = BlzCreateFrame("CodeEditor", codeEditorParent, 0, 0)
BlzFrameSetLevel(searchBar.parent, 2)
BlzFrameSetPoint(searchBar.parent, FRAMEPOINT_TOPRIGHT, codeEditorParent, FRAMEPOINT_TOPRIGHT, -searchBar.HORIZONTAL_INSET, -searchBar.VERTICAL_INSET)
BlzFrameSetSize(searchBar.parent, searchBar.WIDTH, searchBar.HEIGHT)
BlzFrameSetVisible(searchBar.parent, false)
backdrop = BlzFrameGetChild(searchBar.parent, 0)
BlzFrameSetAllPoints(backdrop, searchBar.parent)
BlzFrameSetAlpha(backdrop, editor.BLACK_BACKDROP_ALPHA)
border = BlzFrameGetChild(searchBar.parent, 1)
BlzFrameSetAllPoints(border, searchBar.parent)
BlzFrameSetVisible(border, false)
searchBar.textField = BlzCreateFrame("SearchBarEditBox", searchBar.parent, 0, 0)
BlzFrameSetPoint(searchBar.textField, FRAMEPOINT_BOTTOMLEFT, searchBar.parent, FRAMEPOINT_BOTTOMLEFT, searchBar.SEARCH_FIELD_LEFT_INSET, searchBar.SEARCH_FIELD_BOTTOM_INSET)
BlzFrameSetPoint(searchBar.textField, FRAMEPOINT_TOPRIGHT, searchBar.parent, FRAMEPOINT_TOPRIGHT, -searchBar.SEARCH_FIELD_RIGHT_INSET, -searchBar.SEARCH_FIELD_TOP_INSET)
BlzFrameSetVisible(BlzFrameGetChild(searchBar.textField, 0), false)
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, searchBar.textField, FRAMEEVENT_EDITBOX_TEXT_CHANGED)
TriggerAddAction(trig, OnSearchBarEdit)
local closeSearchBarButton = BlzCreateFrame("CloseEditorButton", searchBar.parent, 0, 0)
BlzFrameSetPoint(closeSearchBarButton, FRAMEPOINT_BOTTOMLEFT, searchBar.parent, FRAMEPOINT_TOPRIGHT, -searchBar.SEARCH_BUTTON_INSET - searchBar.BUTTON_SIZE, -searchBar.SEARCH_BUTTON_INSET - searchBar.BUTTON_SIZE)
BlzFrameSetPoint(closeSearchBarButton, FRAMEPOINT_TOPRIGHT, searchBar.parent, FRAMEPOINT_TOPRIGHT, -searchBar.SEARCH_BUTTON_INSET, -searchBar.SEARCH_BUTTON_INSET)
icon = BlzFrameGetChild(closeSearchBarButton, 0)
iconClicked = BlzFrameGetChild(closeSearchBarButton, 1)
iconHighlight = BlzFrameGetChild(closeSearchBarButton, 2)
BlzFrameSetAllPoints(icon, closeSearchBarButton)
BlzFrameSetTexture(icon, "closeEditor.blp", 0, true)
BlzFrameSetTexture(iconClicked, "closeEditor.blp", 0, true)
BlzFrameClearAllPoints(iconHighlight)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_BOTTOMLEFT, closeSearchBarButton, FRAMEPOINT_BOTTOMLEFT, 0.00375, 0.00375)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_TOPRIGHT, closeSearchBarButton, FRAMEPOINT_TOPRIGHT, -0.00375, -0.00375)
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, closeSearchBarButton, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, function()
BlzFrameSetVisible(searchBar.parent, false)
BlzFrameSetFocus(searchBar.textField, false)
for i = 1, #searchBar.highlights do
ReturnTextHighlightFrame(searchBar.highlights[i])
searchBar.highlights[i] = nil
end
end)
searchBar.searchDownButton = BlzCreateFrame("SearchDownButton", searchBar.parent, 0, 0)
BlzFrameSetPoint(searchBar.searchDownButton, FRAMEPOINT_BOTTOMLEFT, searchBar.parent, FRAMEPOINT_TOPRIGHT, -searchBar.SEARCH_BUTTON_INSET - 2*searchBar.BUTTON_SIZE, -searchBar.SEARCH_BUTTON_INSET - searchBar.BUTTON_SIZE)
BlzFrameSetPoint(searchBar.searchDownButton, FRAMEPOINT_TOPRIGHT, searchBar.parent, FRAMEPOINT_TOPRIGHT, -searchBar.SEARCH_BUTTON_INSET - searchBar.BUTTON_SIZE, -searchBar.SEARCH_BUTTON_INSET)
icon = BlzFrameGetChild(searchBar.searchDownButton, 0)
iconClicked = BlzFrameGetChild(searchBar.searchDownButton, 1)
iconHighlight = BlzFrameGetChild(searchBar.searchDownButton, 2)
BlzFrameSetAllPoints(icon, searchBar.searchDownButton)
BlzFrameSetTexture(icon, "searchArrowDown.blp", 0, true)
BlzFrameSetTexture(iconClicked, "searchArrowDown.blp", 0, true)
BlzFrameClearAllPoints(iconHighlight)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_BOTTOMLEFT, searchBar.searchDownButton, FRAMEPOINT_BOTTOMLEFT, 0.00375, 0.00375)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_TOPRIGHT, searchBar.searchDownButton, FRAMEPOINT_TOPRIGHT, -0.00375, -0.00375)
BlzFrameSetEnable(searchBar.searchDownButton, false)
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, searchBar.searchDownButton, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, SearchDown)
searchBar.searchUpButton = BlzCreateFrame("SearchUpButton", searchBar.parent, 0, 0)
BlzFrameSetPoint(searchBar.searchUpButton, FRAMEPOINT_BOTTOMLEFT, searchBar.parent, FRAMEPOINT_TOPRIGHT, -searchBar.SEARCH_BUTTON_INSET - 3*searchBar.BUTTON_SIZE, -searchBar.SEARCH_BUTTON_INSET - searchBar.BUTTON_SIZE)
BlzFrameSetPoint(searchBar.searchUpButton, FRAMEPOINT_TOPRIGHT, searchBar.parent, FRAMEPOINT_TOPRIGHT, -searchBar.SEARCH_BUTTON_INSET - 2*searchBar.BUTTON_SIZE, -searchBar.SEARCH_BUTTON_INSET)
icon = BlzFrameGetChild(searchBar.searchUpButton, 0)
iconClicked = BlzFrameGetChild(searchBar.searchUpButton, 1)
iconHighlight = BlzFrameGetChild(searchBar.searchUpButton, 2)
BlzFrameSetAllPoints(icon, searchBar.searchUpButton)
BlzFrameSetTexture(icon, "searchArrowUp.blp", 0, true)
BlzFrameSetTexture(iconClicked, "searchArrowUp.blp", 0, true)
BlzFrameClearAllPoints(iconHighlight)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_BOTTOMLEFT, searchBar.searchUpButton, FRAMEPOINT_BOTTOMLEFT, 0.00375, 0.00375)
BlzFrameSetPoint(iconHighlight, FRAMEPOINT_TOPRIGHT, searchBar.searchUpButton, FRAMEPOINT_TOPRIGHT, -0.00375, -0.00375)
BlzFrameSetEnable(searchBar.searchUpButton, false)
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, searchBar.searchUpButton, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trig, SearchUp)
searchBar.numResults = BlzCreateFrameByType("TEXT", "", searchBar.parent, "", 0)
BlzFrameSetPoint(searchBar.numResults, FRAMEPOINT_BOTTOMLEFT, searchBar.parent, FRAMEPOINT_BOTTOMLEFT, searchBar.NUM_FINDS_LEFT_INSET/0.825, searchBar.NUM_FINDS_TOP_INSET/0.825)
BlzFrameSetPoint(searchBar.numResults, FRAMEPOINT_TOPRIGHT, searchBar.parent, FRAMEPOINT_TOPRIGHT, -searchBar.NUM_FINDS_LEFT_INSET/0.825, -searchBar.NUM_FINDS_TOP_INSET/0.825)
BlzFrameSetScale(searchBar.numResults, 0.825)
BlzFrameSetEnable(searchBar.numResults, false)
BlzFrameSetTextAlignment(searchBar.numResults, TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_LEFT)
BlzFrameSetText(searchBar.numResults, "|cffaaaaaaNo results|r")
------------------------------------------------------------------------------------------------------------------------
--Control Triggers
------------------------------------------------------------------------------------------------------------------------
local function CreateControlTriggers(player)
if editor.NEXT_LINE_HOTKEY then
trig = CreateTrigger()
BlzTriggerRegisterPlayerKeyEvent(trig, player, _G["OSKEY_" .. editor.NEXT_LINE_HOTKEY], editor.NEXT_LINE_METAKEY, true)
TriggerAddAction(trig, NextLine)
end
if editor.CONTINUE_HOTKEY then
trig = CreateTrigger()
BlzTriggerRegisterPlayerKeyEvent(trig, player, _G["OSKEY_" .. editor.CONTINUE_HOTKEY], editor.CONTINUE_METAKEY, true)
TriggerAddAction(trig, ContinueExecuting)
end
if editor.TOGGLE_HIDE_EDITOR_HOTKEY then
trig = CreateTrigger()
BlzTriggerRegisterPlayerKeyEvent(trig, player, _G["OSKEY_" .. editor.TOGGLE_HIDE_EDITOR_HOTKEY], editor.TOGGLE_HIDE_EDITOR_METAKEY, true)
TriggerAddAction(trig, ToggleHideEditor)
end
trig = CreateTrigger()
TriggerRegisterPlayerEvent(trig, player, EVENT_PLAYER_MOUSE_MOVE)
TriggerAddAction(trig, function()
mouse.x = BlzGetTriggerPlayerMouseX()
mouse.y = BlzGetTriggerPlayerMouseY()
end)
trig = CreateTrigger()
TriggerRegisterPlayerEvent(trig, player, EVENT_PLAYER_MOUSE_DOWN)
TriggerAddAction(trig, OnMouseClick)
trig = CreateTrigger()
TriggerRegisterPlayerEvent(trig, player, EVENT_PLAYER_MOUSE_UP)
TriggerAddAction(trig, OnMouseRelease)
trig = CreateTrigger()
TriggerRegisterPlayerEvent(trig, player, EVENT_PLAYER_MOUSE_MOVE)
TriggerAddAction(trig, OnMouseMove)
--Any registered key event means the enter box is not focused.
trig = CreateTrigger()
for name, oskey in pairs(_G) do
if find(name, "OSKEY_") then
for metakey = 0, 3 do
BlzTriggerRegisterPlayerKeyEvent(trig, player, oskey, metakey, true)
end
end
end
TriggerAddAction(trig, function()
editBoxFocused = false
BlzFrameSetVisible(cursorFrame, false)
end)
end
if whichPlayer then
CreateControlTriggers(whichPlayer)
else
for i = 0, 23 do
if GetPlayerSlotState(Player(i)) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(Player(i)) == MAP_CONTROL_USER then
CreateControlTriggers(Player(i))
end
end
end
------------------------------------------------------------------------------------------------------------------------
--Misc
------------------------------------------------------------------------------------------------------------------------
TimerStart(CreateTimer(), 0.01, true, AdjustLineDisplay)
local tableTrace = ""
local level = 0
local function GetAllTableFields(source, target)
level = level + 1
for key, value in pairs(source) do
if type(key) == "string" and key ~= "_G" and level <= 4 then
target[#target + 1] = key
if type(value) == "table" then
target[key] = {}
tableTrace = tableTrace .. "." .. key
GetAllTableFields(value, target[key])
end
end
end
table.sort(target)
level = level - 1
end
GetAllTableFields(_G, globalLookupTable)
for i = 1, #globalLookupTable do
if not globalLookupTable[globalLookupTable[i]] then
--globalLookupTable has both a sequence and a dictionary. A table key means global is a table. Otherwise, true.
globalLookupTable[globalLookupTable[i]] = true
end
end
if not GetTerrainZ then
local moveableLoc = Location(0, 0)
GetTerrainZ = function(x, y)
MoveLocation(moveableLoc, x, y)
return GetLocationZ(moveableLoc)
end
end
brackets.highlights[1] = GetTextHighlightFrame("08", 130, "bracketHighlight")
BlzFrameSetVisible(brackets.highlights[1], false)
brackets.highlights[2] = GetTextHighlightFrame("08", 130, "bracketHighlight")
BlzFrameSetVisible(brackets.highlights[2], false)
if hasCodeOnInit then
UpdateVariableViewer(lastLineNumber[currentTab])
end
if editor.LEGAL_CHARACTERS then
local legalCharacters = editor.LEGAL_CHARACTERS
editor.LEGAL_CHARACTERS = {}
for i = 1, #legalCharacters do
editor.LEGAL_CHARACTERS[sub(legalCharacters, i, i)] = true
end
end
benchmark.TIMER = CreateTimer()
editor.ON_ENABLE()
end
local old = MarkGameStarted
MarkGameStarted = function()
old()
local player
local trig = CreateTrigger()
local name
for i = 0, 23 do
player = Player(i)
name = GetPlayerName(player)
for __, creator in ipairs(editor.MAP_CREATORS) do
if find(name, creator) then
user = user or player
TriggerRegisterPlayerChatEvent(trig, player, "-wscode", true)
break
end
end
end
TriggerAddAction(trig, EnableEditor)
if initError then
JumpToError(initError.fileName, initError.lineNumber)
elseif Debug and Debug.data.firstError then
local fileName = Debug.data.firstError:match("ERROR at (.+):\x25d+:")
local lineNumber = Debug.data.firstError:match(fileName .. ":(\x25d+):")
if fileName and lineNumber then
JumpToError(fileName, lineNumber)
end
elseif editor.AUTO_INITIALIZE and not codeEditorParent then
EnableEditor(user)
BlzFrameSetVisible(codeEditorParent, false)
end
end
--This table is global so that it is accessible within the executed code. Not part of the API.
WSDebug = {
CheckForStop = function(lineNumber, whichTab, generationCounter)
if not coroutine.isyieldable(coroutine.running()) or generationCounter ~= WSDebug.GenerationCounter[whichTab] then
return true
end
currentStop = lineNumber
lastLineNumber[whichTab] = lineNumber
executionTab = whichTab
WSDebug.Coroutine[executionTab] = coroutine.running()
if lines.debugState[executionTab][lineNumber] == 2 or (lineByLine and timeElapsed == timeOfLastBreakpoint) then
if not BlzFrameIsVisible(codeEditorParent) then
EnableEditor(user)
end
if currentTab ~= executionTab then
SwitchToTab(executionTab)
end
BlzFrameSetVisible(currentLineHighlight, true)
hasCurrentLineHighlight = true
BlzFrameSetVisible(errorHighlight, false)
JumpWindow(currentStop, true, currentTab ~= executionTab)
SetCurrentLine()
lastViewedLineNumber[whichTab] = nil
lastViewedFunctionCall = nil
UpdateVariableViewer(lineNumber)
if flags.haltAliceOnBreakpoint then
ALICE_Halt()
BlzFrameSetEnable(buttons.haltALICE, false)
end
editor.ON_BREAK(tabNames[whichTab])
coroutine.yield()
if flags.haltAliceOnBreakpoint then
ALICE_Resume()
BlzFrameSetEnable(buttons.haltALICE, true)
end
editor.ON_RESUME(tabNames[whichTab])
timeOfLastBreakpoint = timeElapsed
hasCurrentLineHighlight = false
BlzFrameSetVisible(currentLineHighlight, false)
elseif lines.debugState[executionTab][lineNumber] == 1 then
if not BlzFrameIsVisible(codeEditorParent) then
EnableEditor(user)
end
lastViewedLineNumber[whichTab] = lineNumber
lastViewedFunctionCall = lastFunctionCall
if currentTab == executionTab then
UpdateVariableViewer(lineNumber)
end
end
return true
end,
AssignVars = function(valueTable, level, codeTab, lineNumber, ...)
if coroutine.running() ~= WSDebug.Coroutine[codeTab] then
return
end
local whichVar
for i = 1, select("#", ...) do
whichVar = select(i, ...)
if find(whichVar, "[.\x25[:]") == nil then
if level == -1 then
level = visibleVarsOfLine[codeTab][lineNumber][whichVar] or -1
end
WSDebug.VarTable[codeTab][level][whichVar] = valueTable and valueTable[i] or WSDebug.Nil
end
end
end,
AssignVar = function(value, level, codeTab, lineNumber, varName)
if coroutine.running() ~= WSDebug.Coroutine[codeTab] then
return
end
if find(varName, "[.\x25[:]") == nil then
if level == -1 then
level = visibleVarsOfLine[codeTab][lineNumber][varName] or -1
end
WSDebug.VarTable[codeTab][level][varName] = value or WSDebug.Nil
end
end,
GetVar = function(codeTab, level, varName, value)
local val = WSDebug.VarTable[codeTab][level][varName] or value
if val == WSDebug.Nil then
return nil
end
return val
end,
GetVarEx = function(codeTab, level, ...)
local numVars = select("#", ...)//2
local varName
local valueTable = {}
for i = 1, numVars do
varName = select(i, ...)
valueTable[i] = WSDebug.VarTable[codeTab][level][varName] or select(i + numVars, ...)
if valueTable[i] == WSDebug.Nil then
valueTable[i] = nil
end
end
return valueTable
end,
CatchParams = function(funcName, whichTab, ...)
if not lastViewedLineNumber[whichTab] and not hasCurrentLineHighlight then
functionCallCounter = functionCallCounter + 1
lastFunctionCall = funcName
local num = select("#", ...)
for i = 1, num do
functionParams[i] = select(i, ...)
end
for i = num + 1, #functionParams do
functionParams[i] = nil
end
end
return ...
end,
HandleCoroutine = function(whichFunc, whichTab, ...)
if Debug and Debug.original then
WSDebug.Coroutine[whichTab] = Debug.original.coroutine.create(whichFunc)
else
WSDebug.Coroutine[whichTab] = coroutine.create(whichFunc)
end
local result = table.pack(coroutine.resume(WSDebug.Coroutine[whichTab], ...))
if result[1] == false then
HandleError(result[2])
elseif result[1] == true then
return select(2, table.unpack(result))
end
end,
GetWrapper = function(funcName, whichTab, whichGenCounter, whichFunc)
if whichGenCounter > wrapperGenerationCounter[whichTab][funcName] then
wrapperFunc[whichTab][funcName] = wrapperFunc[whichTab][funcName] or function(...) return wrappedFunc[whichTab][funcName](...) end
wrappedFunc[whichTab][funcName] = whichFunc
wrapperGenerationCounter[whichTab][funcName] = whichGenCounter
elseif wrapperFunc[whichTab][funcName] then
return whichFunc
else
wrapperFunc[whichTab][funcName] = function(...) return wrappedFunc[whichTab][funcName](...) end
wrappedFunc[whichTab][funcName] = whichFunc
end
return wrapperFunc[whichTab][funcName]
end,
Execute = function(initPoint, ...)
WSDebug.FunctionsToInit[INIT_POINTS[initPoint]][#WSDebug.FunctionsToInit[INIT_POINTS[initPoint]] + 1] = select(-1, ...)
end,
FunctionsToInit = setmetatable({}, tab2D),
StoreHandle = function(creator, whichTab, ...)
local handle = select(1, ...)
handles[whichTab][handle] = creator
return ...
end,
ValidateFrame = function(frame)
if compiler.VALIDATE_FRAMES and frame and GetHandleId(frame) == 0 then
error("Function returned invalid frame.")
end
return frame
end,
StoreFunc = function(whichFunction, index)
WSDebug.FuncList[index] = whichFunction
return whichFunction
end,
FuncList = {},
End = function(lineNumber, whichTab, generationCounter)
lastLineNumber[whichTab] = lineNumber
if not lastViewedLineNumber[whichTab] and variableViewerIsExpanded and generationCounter == WSDebug.GenerationCounter[whichTab] then
UpdateVariableViewer(lineNumber)
end
end,
Nil = {},
GenerationCounter = setmetatable({}, {__index = function(self, key) self[key] = 0 return 0 end}),
Vars = {},
VarTable = setmetatable({}, {__index = function(self, key) self[key] = setmetatable({}, tab2D) return self[key] end}),
Coroutine = {}
}
--======================================================================================================================
--API
--======================================================================================================================
StoreCode = function(tabName, functionBody)
if not tabNumber[tabName] then
if codeEditorParent then
AddTab(tabName)
SwitchToTab(tabName)
elseif tabNames[1] == "Main" then
tabNames[1] = tabName
tabNumber[tabName] = 1
tabNumber.Main = nil
if currentTab == "Main" then
currentTab = tabName
end
else
numTabs = numTabs + 1
tabNumber[tabName] = numTabs
tabNames[numTabs] = tabName
end
end
local newLines
if type(functionBody) == "string" then
newLines = ConvertStringToLines(functionBody)
for i = 1, #newLines do
newLines[i] = newLines[i]:gsub("\t", " "):gsub("\n", ""):gsub("\r", "")
end
else
newLines = functionBody
end
local oldCodeSize = highestNonEmptyLine[tabName][step[tabName]]
if oldCodeSize > 0 then
for i = oldCodeSize + 2, #newLines + oldCodeSize + 1 do
codeLines[tabName][step[tabName]][i] = newLines[i - (oldCodeSize + 1)]
coloredCodeLines[tabName][step[tabName]][i] = GetColoredText(codeLines[tabName][step[tabName]][i], i, tabName)
end
codeLines[tabName][step[tabName]][oldCodeSize + 1] = ""
coloredCodeLines[tabName][step[tabName]][oldCodeSize + 1] = ""
highestNonEmptyLine[tabName][step[tabName]] = #newLines + oldCodeSize + 1
else
for i = 1, #newLines do
codeLines[tabName][step[tabName]][i] = newLines[i]
coloredCodeLines[tabName][step[tabName]][i] = GetColoredText(codeLines[tabName][step[tabName]][i], i, tabName)
end
highestNonEmptyLine[tabName][step[tabName]] = #newLines
end
return newLines, tabName
end
WSCode = {
---Stores the provided string in the code editor upon map launch.
---@param tabName string
---@param functionBody string
---@overload fun(functionBody: string)
Store = function(tabName, functionBody)
StoreCode(tabName, functionBody)
end,
---Stores the provided string in the code editor upon map launch, then transpiles it into its debug form and executes it.
---@param tabName string
---@param functionBody string
---@overload fun(functionBody: string)
Transpile = function(tabName, functionBody)
local newLines, tabName = StoreCode(tabName, functionBody)
hasCodeOnInit = true
CompileFunction(newLines, {
debugMode = true,
duringInit = true,
tab = tabName,
})
flags.debugMode = false
numVisibleVars = 0
for i = 1, #variableViewer.frames do
variableViewer.frames[i] = nil
end
end,
---Stores the provided string in the code editor upon map launch, then executes it.
---@param tabName string
---@param functionBody string
---@overload fun(functionBody: string)
Compile = function(tabName, functionBody)
local newLines, tabName = StoreCode(tabName, functionBody)
hasCodeOnInit = true
CompileFunction(newLines, {
duringInit = true,
tab = tabName,
})
numVisibleVars = 0
for i = 1, #variableViewer.frames do
variableViewer.frames[i] = nil
end
end,
---Executes the provided code.
---@param functionBody string
Execute = function(functionBody)
local func, error = load(functionBody, "WSCodeFunc", "t")
if not func then
if Debug then Debug.throwError(error) end
return
end
func()
end,
---Parses the provided code, searching for function definitions to generate function previews, then executes the code in chunks delimited by either Debug.beginFile or @beginFile tokens, which must be followed by the script name. Parsed scripts can be added to the editor with the Pull Script button. A script will automatically be transpiled into debug form and added to the editor if a @debug token is found anywhere in the script. All functions set in editor. CREATOR_DESTRUCTOR_PAIRS will be wrapped to capture the created handle, so that it can be cleaned up when the code is recompiled with the "Clean Handles" flag enabled. Designed to read in the entire map script.
---@param functionBody string
Parse = function(functionBody)
local newLines = ConvertStringToLines(functionBody)
for i = 1, #newLines do
newLines[i] = newLines[i]:gsub("\t", " ")
end
local beginningOfFile = 1
local fileName = "anonymous"
local debug = {}
local transpile = {}
local store = {}
local inGuiTrigger = false
local function ParseFile(endOfFile, autoIndent)
local debugthis = false
local transpilethis = false
local storethis = false
local atSign
local lines = {}
local k = 1
for j = beginningOfFile, endOfFile do
lines[k] = newLines[j]
atSign = find(lines[k], "@") ~= nil
debugthis = debugthis or (atSign and find(lines[k], "@debug") ~= nil or inGuiTrigger and find(lines[k], "udg_wsdebug = true") ~= nil)
transpilethis = transpilethis or atSign and find(lines[k], "@transpile") ~= nil
storethis = storethis or find(lines[k], "@store") ~= nil
k = k + 1
end
files[#files + 1] = lines
fileNames[#fileNames + 1] = fileName
debug[#debug + 1] = debugthis
store[#store + 1] = storethis
transpile[#transpile + 1] = transpilethis
local fourSpaces = " "
if autoIndent then
for i = 1, #lines do
lines[i] = match(lines[i], "^\x25s*(.*)") --remove eventual indentation
end
local level = 0
for i = 1, #lines do
if find(lines[i], "\x25f[\x25a_]end\x25s*$") ~= nil
or find(lines[i], "\x25f[\x25a_]until\x25s*") ~= nil then
level = level - 1
end
if find(lines[i], "\x25s*else\x25*") ~= nil or find(lines[i], "\x25s*elseif[\x25s\x25(]") ~= nil then
for __ = 1, level - 1 do
lines[i] = fourSpaces .. lines[i]
end
else
for __ = 1, level do
lines[i] = fourSpaces .. lines[i]
end
end
if find(lines[i], "\x25f[\x25a_]for[\x25s\x25(]") ~= nil
or find(lines[i], "\x25f[\x25a_]do\x25f[^\x25w_]") ~= nil
or find(lines[i], "\x25f[\x25a_]if[\x25s\x25(]") ~= nil
or find(lines[i], "\x25f[\x25a_]function\x25f[\x25s\x25(]") then
level = level + 1
end
end
end
end
newLines[#newLines + 1] = ""
local size = #newLines
for i = 1, size do
if find(newLines[i], "beginFile") and (find(newLines[i], "Debug.beginFile") or find(newLines[i], "@beginFile")) then
ParseFile(i - 1)
beginningOfFile = i
fileName = newLines[i]:match("Debug\x25.beginFile\x25s*\x25(?\x25s*[\"']([^\"']+)[\"']") or newLines[i]:match("@beginFile\x25s*[\"']([^\"']+)[\"']")
inGuiTrigger = false
elseif find(newLines[i], "endFile") and (find(newLines[i], "Debug.endFile") or find(newLines[i], "@endFile")) or i == size then
ParseFile(i)
beginningOfFile = i + 1
fileName = "anonymous"
inGuiTrigger = false
elseif find(newLines[i], "function Trig_") then
local newFileName = match(newLines[i], 'function Trig_([\x25w_]+)_\x25f[Actions|Conditions|Func0]'):gsub("_", " ")
if newFileName ~= fileName then
ParseFile(i - 1, true)
beginningOfFile = i
fileName = newFileName
inGuiTrigger = true
end
end
end
for i = 1, #files do
for j = 1, #files[i] do
if find(files[i][j], "^\x25s*$") == nil then
if debug[i] then
StoreCode(fileNames[i], files[i])
CompileFunction(files[i], {
tab = fileNames[i],
parse = FUNCTION_PREVIEW ~= nil,
duringInit = true,
debugMode = true
})
elseif transpile[i] then
CompileFunction(files[i], {
tab = fileNames[i],
parse = FUNCTION_PREVIEW ~= nil,
duringInit = true,
debugMode = true
})
elseif store[i] then
StoreCode(fileNames[i], files[i])
else
CompileFunction(files[i], {
tab = fileNames[i],
parse = FUNCTION_PREVIEW ~= nil,
duringInit = true
})
end
break
end
end
numVisibleVars = 0
end
orderedFileNames = table.pack(table.unpack(fileNames))
table.sort(orderedFileNames)
for i = 1, #variableViewer.frames do
variableViewer.frames[i] = nil
end
end,
---Adds a breakpoint to a line in the specified currentTab. The currentTab can be referenced either by its currentTab index or its name.
---@param whichTab string | integer
---@param lineNumber integer
AddBreakPoint = function(whichTab, lineNumber)
local tabName = whichTab
if type(whichTab) == "string" then
whichTab = tabNumber[whichTab]
end
if not whichTab then
if Debug then
Debug.throwError("Unrecognized currentTab name " .. tabName .. ".")
end
return
end
lines.debugState[whichTab][lineNumber] = 2
if BlzFrameIsVisible(codeEditorParent) and currentTab == whichTab and lineNumberOffset[currentTab] < lineNumber and lineNumberOffset[currentTab] >= lineNumber - editor.MAX_LINES_ON_SCREEN then
BlzFrameSetTexture(BlzFrameGetChild(stopButtons[lineNumber], 0), LINE_STATE_ICONS[2], 0, true)
end
end,
---Halts exeuction of the code when it reaches this line and switches to the currentTab it is executed in. Script must be compiled in debug mode. A breakpoint added this way cannot be disabled.
BreakHere = function()
if not coroutine.isyieldable(coroutine.running()) then
if Debug then
Debug.throwError("Coroutine is not yieldable.")
end
return
end
if executionTab == "anonymous" or not lastLineNumber[executionTab] then
if Debug then
Debug.throwError("Could not identify breakpoint location.")
end
return
end
local alreadyActive
local tabIndex
for j = 1, numTabs do
if tabNames[j] == executionTab then
alreadyActive = true
tabIndex = j
break
end
end
for i = 0, 23 do
if user then
break
end
for __, creator in ipairs(editor.MAP_CREATORS) do
if find(GetPlayerName(Player(i)), creator) then
user = Player(i)
break
end
end
end
if alreadyActive then
EnableEditor(user)
if currentTab ~= tabIndex then
SwitchToTab(tabIndex)
end
else
local found
for i = 1, #fileNames do
if fileNames[i] == executionTab then
EnableEditor(user)
Pull(executionTab)
found = true
break
end
end
if not found then
return
end
end
currentStop = lastLineNumber[executionTab]
WSDebug.Coroutine[tabIndex] = coroutine.running()
BlzFrameSetVisible(currentLineHighlight, true)
hasCurrentLineHighlight = true
BlzFrameSetVisible(errorHighlight, false)
JumpWindow(currentStop, true, currentTab ~= executionTab)
SetCurrentLine()
lastViewedLineNumber[executionTab] = nil
UpdateVariableViewer(lastLineNumber[executionTab])
if flags.haltAliceOnBreakpoint then
ALICE_Halt()
end
coroutine.yield()
if flags.haltAliceOnBreakpoint then
ALICE_Resume()
end
timeOfLastBreakpoint = timeElapsed
hasCurrentLineHighlight = false
BlzFrameSetVisible(currentLineHighlight, false)
end,
---Shows or hides the code editor.
---@param enable boolean
Show = function(enable)
if enable then
EnableEditor(user)
else
BlzFrameSetVisible(codeEditorParent, false)
end
end,
---Returns whether the code editor has been created.
---@return boolean
IsEnabled = function()
return codeEditorParent ~= nil
end,
---Returns whether the code editor is visible.
---@return boolean
IsVisible = function()
return BlzFrameIsVisible(codeEditorParent)
end,
---Returns whether the code editor is expanded.
---@return boolean
IsExpanded = function()
return isExpanded
end
}
end
FUNCTION_PREVIEW = {
BJDebugMsg = "msg,",
RMinBJ = "a, b,",
RMaxBJ = "a, b,",
RAbsBJ = "a,",
RSignBJ = "a,",
IMinBJ = "a, b,",
IMaxBJ = "a, b,",
IAbsBJ = "a,",
ISignBJ = "a,",
SinBJ = "degrees,",
CosBJ = "degrees,",
TanBJ = "degrees,",
AsinBJ = "degrees,",
AcosBJ = "degrees,",
AtanBJ = "degrees,",
Atan2BJ = "y, x,",
AngleBetweenPoints = "locA, locB,",
DistanceBetweenPoints = "locA, locB,",
PolarProjectionBJ = "source, dist, angle,",
GetRandomDirectionDeg = ",",
GetRandomPercentageBJ = ",",
GetRandomLocInRect = "whichRect,",
ModuloInteger = "dividend, divisor,",
ModuloReal = "dividend, divisor,",
OffsetLocation = "loc, dx, dy,",
OffsetRectBJ = "r, dx, dy,",
RectFromCenterSizeBJ = "center, width, height,",
RectContainsCoords = "r, x, y,",
RectContainsLoc = "r, loc,",
RectContainsUnit = "r, whichUnit,",
RectContainsItem = "whichItem, r,",
ConditionalTriggerExecute = "trig,",
TriggerExecuteBJ = "trig, checkConditions,",
PostTriggerExecuteBJ = "trig, checkConditions,",
QueuedTriggerCheck = ",",
QueuedTriggerGetIndex = "trig,",
QueuedTriggerRemoveByIndex = "trigIndex,",
QueuedTriggerAttemptExec = ",",
QueuedTriggerAddBJ = "trig, checkConditions,",
QueuedTriggerRemoveBJ = "trig,",
QueuedTriggerDoneBJ = ",",
QueuedTriggerClearBJ = ",",
QueuedTriggerClearInactiveBJ = ",",
QueuedTriggerCountBJ = ",",
IsTriggerQueueEmptyBJ = ",",
IsTriggerQueuedBJ = "trig,",
GetForLoopIndexA = ",",
SetForLoopIndexA = "newIndex,",
GetForLoopIndexB = ",",
SetForLoopIndexB = "newIndex,",
PolledWait = "duration,",
IntegerTertiaryOp = "flag, valueA, valueB,",
DoNothing = ",",
CommentString = "commentString,",
StringIdentity = "theString,",
GetBooleanAnd = "valueA, valueB,",
GetBooleanOr = "valueA, valueB,",
PercentToInt = "percentage, max,",
PercentTo255 = "percentage,",
GetTimeOfDay = ",",
SetTimeOfDay = "whatTime,",
SetTimeOfDayScalePercentBJ = "scalePercent,",
GetTimeOfDayScalePercentBJ = ",",
PlaySound = "soundName,",
CompareLocationsBJ = "A, B,",
CompareRectsBJ = "A, B,",
GetRectFromCircleBJ = "center, radius,",
GetCurrentCameraSetup = ",",
CameraSetupApplyForPlayer = "doPan, whichSetup, whichPlayer, duration,",
CameraSetupApplyForPlayerSmooth = "doPan, whichSetup, whichPlayer, forcedDuration, easeInDuration, easeOutDuration, smoothFactor,",
CameraSetupGetFieldSwap = "whichField, whichSetup,",
SetCameraFieldForPlayer = "whichPlayer, whichField, value, duration,",
SetCameraTargetControllerNoZForPlayer = "whichPlayer, whichUnit, xoffset, yoffset, inheritOrientation,",
SetCameraPositionForPlayer = "whichPlayer, x, y,",
SetCameraPositionLocForPlayer = "whichPlayer, loc,",
RotateCameraAroundLocBJ = "degrees, loc, whichPlayer, duration,",
PanCameraToForPlayer = "whichPlayer, x, y,",
PanCameraToLocForPlayer = "whichPlayer, loc,",
PanCameraToTimedForPlayer = "whichPlayer, x, y, duration,",
PanCameraToTimedLocForPlayer = "whichPlayer, loc, duration,",
PanCameraToTimedLocWithZForPlayer = "whichPlayer, loc, zOffset, duration,",
SmartCameraPanBJ = "whichPlayer, loc, duration,",
SetCinematicCameraForPlayer = "whichPlayer, cameraModelFile,",
ResetToGameCameraForPlayer = "whichPlayer, duration,",
CameraSetSourceNoiseForPlayer = "whichPlayer, magnitude, velocity,",
CameraSetTargetNoiseForPlayer = "whichPlayer, magnitude, velocity,",
CameraSetEQNoiseForPlayer = "whichPlayer, magnitude,",
CameraClearNoiseForPlayer = "whichPlayer,",
GetCurrentCameraBoundsMapRectBJ = ",",
GetCameraBoundsMapRect = ",",
GetPlayableMapRect = ",",
GetEntireMapRect = ",",
SetCameraBoundsToRect = "r,",
SetCameraBoundsToRectForPlayerBJ = "whichPlayer, r,",
AdjustCameraBoundsBJ = "adjustMethod, dxWest, dxEast, dyNorth, dySouth,",
AdjustCameraBoundsForPlayerBJ = "adjustMethod, whichPlayer, dxWest, dxEast, dyNorth, dySouth,",
SetCameraQuickPositionForPlayer = "whichPlayer, x, y,",
SetCameraQuickPositionLocForPlayer = "whichPlayer, loc,",
SetCameraQuickPositionLoc = "loc,",
StopCameraForPlayerBJ = "whichPlayer,",
SetCameraOrientControllerForPlayerBJ = "whichPlayer, whichUnit, xoffset, yoffset,",
CameraSetSmoothingFactorBJ = "factor,",
CameraResetSmoothingFactorBJ = ",",
DisplayTextToForce = "toForce, message,",
DisplayTimedTextToForce = "toForce, duration, message,",
ClearTextMessagesBJ = "toForce,",
SubStringBJ = "source, start, finish,",
GetHandleIdBJ = "h,",
StringHashBJ = "s,",
TriggerRegisterTimerEventPeriodic = "trig, timeout,",
TriggerRegisterTimerEventSingle = "trig, timeout,",
TriggerRegisterTimerExpireEventBJ = "trig, t,",
TriggerRegisterPlayerUnitEventSimple = "trig, whichPlayer, whichEvent,",
TriggerRegisterAnyUnitEventBJ = "trig, whichEvent,",
TriggerRegisterPlayerSelectionEventBJ = "trig, whichPlayer, selected,",
TriggerRegisterPlayerKeyEventBJ = "trig, whichPlayer, keType, keKey,",
TriggerRegisterPlayerMouseEventBJ = "trig, whichPlayer, meType,",
TriggerRegisterPlayerEventVictory = "trig, whichPlayer,",
TriggerRegisterPlayerEventDefeat = "trig, whichPlayer,",
TriggerRegisterPlayerEventLeave = "trig, whichPlayer,",
TriggerRegisterPlayerEventAllianceChanged = "trig, whichPlayer,",
TriggerRegisterPlayerEventEndCinematic = "trig, whichPlayer,",
TriggerRegisterGameStateEventTimeOfDay = "trig, opcode, limitval,",
TriggerRegisterEnterRegionSimple = "trig, whichRegion,",
TriggerRegisterLeaveRegionSimple = "trig, whichRegion,",
TriggerRegisterEnterRectSimple = "trig, r,",
TriggerRegisterLeaveRectSimple = "trig, r,",
TriggerRegisterDistanceBetweenUnits = "trig, whichUnit, condition, range,",
TriggerRegisterUnitInRangeSimple = "trig, range, whichUnit,",
TriggerRegisterUnitLifeEvent = "trig, whichUnit, opcode, limitval,",
TriggerRegisterUnitManaEvent = "trig, whichUnit, opcode, limitval,",
TriggerRegisterDialogEventBJ = "trig, whichDialog,",
TriggerRegisterShowSkillEventBJ = "trig,",
TriggerRegisterBuildSubmenuEventBJ = "trig,",
TriggerRegisterBuildCommandEventBJ = "trig, unitId,",
TriggerRegisterTrainCommandEventBJ = "trig, unitId,",
TriggerRegisterUpgradeCommandEventBJ = "trig, techId,",
TriggerRegisterCommonCommandEventBJ = "trig, order,",
TriggerRegisterGameLoadedEventBJ = "trig,",
TriggerRegisterGameSavedEventBJ = "trig,",
RegisterDestDeathInRegionEnum = ",",
TriggerRegisterDestDeathInRegionEvent = "trig, r,",
AddWeatherEffectSaveLast = "where, effectID,",
GetLastCreatedWeatherEffect = ",",
RemoveWeatherEffectBJ = "weatherwhichWeatherEffect,",
TerrainDeformationCraterBJ = "duration, permanent, where, radius, depth,",
TerrainDeformationRippleBJ = "duration, limitNeg, where, startRadius, endRadius, depth, wavePeriod, waveWidth,",
TerrainDeformationWaveBJ = "duration, source, target, radius, depth, trailDelay,",
TerrainDeformationRandomBJ = "duration, where, radius, minDelta, maxDelta, updateInterval,",
TerrainDeformationStopBJ = "deformation, duration,",
GetLastCreatedTerrainDeformation = ",",
AddLightningLoc = "codeName, where1, where2,",
DestroyLightningBJ = "whichBolt,",
MoveLightningLoc = "whichBolt, where1, where2,",
GetLightningColorABJ = "whichBolt,",
GetLightningColorRBJ = "whichBolt,",
GetLightningColorGBJ = "whichBolt,",
GetLightningColorBBJ = "whichBolt,",
SetLightningColorBJ = "whichBolt, r, g, b, a,",
GetLastCreatedLightningBJ = ",",
GetAbilityEffectBJ = "abilcode, t, index,",
GetAbilitySoundBJ = "abilcode, t,",
GetTerrainCliffLevelBJ = "where,",
GetTerrainTypeBJ = "where,",
GetTerrainVarianceBJ = "where,",
SetTerrainTypeBJ = "where, terrainType, variation, area, shape,",
IsTerrainPathableBJ = "where, t,",
SetTerrainPathableBJ = "where, t, flag,",
SetWaterBaseColorBJ = "red, green, blue, transparency,",
CreateFogModifierRectSimple = "whichPlayer, whichFogState, r, afterUnits,",
CreateFogModifierRadiusLocSimple = "whichPlayer, whichFogState, center, radius, afterUnits,",
CreateFogModifierRectBJ = "enabled, whichPlayer, whichFogState, r,",
CreateFogModifierRadiusLocBJ = "enabled, whichPlayer, whichFogState, center, radius,",
GetLastCreatedFogModifier = ",",
FogEnableOn = ",",
FogEnableOff = ",",
FogMaskEnableOn = ",",
FogMaskEnableOff = ",",
UseTimeOfDayBJ = "flag,",
SetTerrainFogExBJ = "style, zstart, zend, density, red, green, blue,",
ResetTerrainFogBJ = ",",
SetDoodadAnimationBJ = "animName, doodadID, radius, center,",
SetDoodadAnimationRectBJ = "animName, doodadID, r,",
AddUnitAnimationPropertiesBJ = "add, animProperties, whichUnit,",
CreateImageBJ = "file, size, where, zOffset, imageType,",
ShowImageBJ = "flag, whichImage,",
SetImagePositionBJ = "whichImage, where, zOffset,",
SetImageColorBJ = "whichImage, red, green, blue, alpha,",
GetLastCreatedImage = ",",
CreateUbersplatBJ = "where, name, red, green, blue, alpha, forcePaused, noBirthTime,",
ShowUbersplatBJ = "flag, whichSplat,",
GetLastCreatedUbersplat = ",",
GetLastCreatedMinimapIcon = ",",
CreateMinimapIconOnUnitBJ = "whichUnit, red, green, blue, pingPath, fogVisibility,",
CreateMinimapIconAtLocBJ = "where, red, green, blue, pingPath, fogVisibility,",
CreateMinimapIconBJ = "x, y, red, green, blue, pingPath, fogVisibility,",
CampaignMinimapIconUnitBJ = "whichUnit, style,",
CampaignMinimapIconLocBJ = "where, style,",
PlaySoundBJ = "soundHandle,",
StopSoundBJ = "soundHandle, fadeOut,",
SetSoundVolumeBJ = "soundHandle, volumePercent,",
SetSoundOffsetBJ = "newOffset, soundHandle,",
SetSoundDistanceCutoffBJ = "soundHandle, cutoff,",
SetSoundPitchBJ = "soundHandle, pitch,",
SetSoundPositionLocBJ = "soundHandle, loc, z,",
AttachSoundToUnitBJ = "soundHandle, whichUnit,",
SetSoundConeAnglesBJ = "soundHandle, inside, outside, outsideVolumePercent,",
KillSoundWhenDoneBJ = "soundHandle,",
PlaySoundAtPointBJ = "soundHandle, volumePercent, loc, z,",
PlaySoundOnUnitBJ = "soundHandle, volumePercent, whichUnit,",
PlaySoundFromOffsetBJ = "soundHandle, volumePercent, startingOffset,",
PlayMusicBJ = "musicFileName,",
PlayMusicExBJ = "musicFileName, startingOffset, fadeInTime,",
SetMusicOffsetBJ = "newOffset,",
PlayThematicMusicBJ = "musicName,",
PlayThematicMusicExBJ = "musicName, startingOffset,",
SetThematicMusicOffsetBJ = "newOffset,",
EndThematicMusicBJ = ",",
StopMusicBJ = "fadeOut,",
ResumeMusicBJ = ",",
SetMusicVolumeBJ = "volumePercent,",
GetSoundDurationBJ = "soundHandle,",
GetSoundFileDurationBJ = "musicFileName,",
GetLastPlayedSound = ",",
GetLastPlayedMusic = ",",
VolumeGroupSetVolumeBJ = "vgroup, percent,",
SetCineModeVolumeGroupsImmediateBJ = ",",
SetCineModeVolumeGroupsBJ = ",",
SetSpeechVolumeGroupsImmediateBJ = ",",
SetSpeechVolumeGroupsBJ = ",",
VolumeGroupResetImmediateBJ = ",",
VolumeGroupResetBJ = ",",
GetSoundIsPlayingBJ = "soundHandle,",
WaitForSoundBJ = "soundHandle, offset,",
SetMapMusicIndexedBJ = "musicName, index,",
SetMapMusicRandomBJ = "musicName,",
ClearMapMusicBJ = ",",
SetStackedSoundBJ = "add, soundHandle, r,",
StartSoundForPlayerBJ = "whichPlayer, soundHandle,",
VolumeGroupSetVolumeForPlayerBJ = "whichPlayer, vgroup, scale,",
EnableDawnDusk = "flag,",
IsDawnDuskEnabled = ",",
SetAmbientDaySound = "inLabel,",
SetAmbientNightSound = "inLabel,",
AddSpecialEffectLocBJ = "where, modelName,",
AddSpecialEffectTargetUnitBJ = "attachPointName, targetWidget, modelName,",
DestroyEffectBJ = "whichEffect,",
GetLastCreatedEffectBJ = ",",
CreateCommandButtonEffectBJ = "abilityId, order,",
CreateTrainCommandButtonEffectBJ = "unitId,",
CreateUpgradeCommandButtonEffectBJ = "techId,",
CreateCommonCommandButtonEffectBJ = "order,",
CreateLearnCommandButtonEffectBJ = "abilityId,",
CreateBuildCommandButtonEffectBJ = "unitId,",
GetLastCreatedCommandButtonEffectBJ = ",",
GetItemLoc = "whichItem,",
GetItemLifeBJ = "whichWidget,",
SetItemLifeBJ = "whichWidget, life,",
AddHeroXPSwapped = "xpToAdd, whichHero, showEyeCandy,",
SetHeroLevelBJ = "whichHero, newLevel, showEyeCandy,",
DecUnitAbilityLevelSwapped = "abilcode, whichUnit,",
IncUnitAbilityLevelSwapped = "abilcode, whichUnit,",
SetUnitAbilityLevelSwapped = "abilcode, whichUnit, level,",
GetUnitAbilityLevelSwapped = "abilcode, whichUnit,",
UnitHasBuffBJ = "whichUnit, buffcode,",
UnitRemoveBuffBJ = "buffcode, whichUnit,",
UnitAddItemSwapped = "whichItem, whichHero,",
UnitAddItemByIdSwapped = "itemId, whichHero,",
UnitRemoveItemSwapped = "whichItem, whichHero,",
UnitRemoveItemFromSlotSwapped = "itemSlot, whichHero,",
CreateItemLoc = "itemId, loc,",
GetLastCreatedItem = ",",
GetLastRemovedItem = ",",
SetItemPositionLoc = "whichItem, loc,",
GetLearnedSkillBJ = ",",
SuspendHeroXPBJ = "flag, whichHero,",
SetPlayerHandicapDamageBJ = "whichPlayer, handicapPercent,",
GetPlayerHandicapDamageBJ = "whichPlayer,",
SetPlayerHandicapReviveTimeBJ = "whichPlayer, handicapPercent,",
GetPlayerHandicapReviveTimeBJ = "whichPlayer,",
SetPlayerHandicapXPBJ = "whichPlayer, handicapPercent,",
GetPlayerHandicapXPBJ = "whichPlayer,",
SetPlayerHandicapBJ = "whichPlayer, handicapPercent,",
GetPlayerHandicapBJ = "whichPlayer,",
GetHeroStatBJ = "whichStat, whichHero, includeBonuses,",
SetHeroStat = "whichHero, whichStat, value,",
ModifyHeroStat = "whichStat, whichHero, modifyMethod, value,",
ModifyHeroSkillPoints = "whichHero, modifyMethod, value,",
UnitDropItemPointBJ = "whichUnit, whichItem, x, y,",
UnitDropItemPointLoc = "whichUnit, whichItem, loc,",
UnitDropItemSlotBJ = "whichUnit, whichItem, slot,",
UnitDropItemTargetBJ = "whichUnit, whichItem, target,",
UnitUseItemDestructable = "whichUnit, whichItem, target,",
UnitUseItemPointLoc = "whichUnit, whichItem, loc,",
UnitItemInSlotBJ = "whichUnit, itemSlot,",
GetInventoryIndexOfItemTypeBJ = "whichUnit, itemId,",
GetItemOfTypeFromUnitBJ = "whichUnit, itemId,",
UnitHasItemOfTypeBJ = "whichUnit, itemId,",
UnitInventoryCount = "whichUnit,",
UnitInventorySizeBJ = "whichUnit,",
SetItemInvulnerableBJ = "whichItem, flag,",
SetItemDropOnDeathBJ = "whichItem, flag,",
SetItemDroppableBJ = "whichItem, flag,",
SetItemPlayerBJ = "whichItem, whichPlayer, changeColor,",
SetItemVisibleBJ = "show, whichItem,",
IsItemHiddenBJ = "whichItem,",
ChooseRandomItemBJ = "level,",
ChooseRandomItemExBJ = "level, whichType,",
ChooseRandomNPBuildingBJ = ",",
ChooseRandomCreepBJ = "level,",
EnumItemsInRectBJ = "r, actionFunc,",
RandomItemInRectBJEnum = ",",
RandomItemInRectBJ = "r, filter,",
RandomItemInRectSimpleBJ = "r,",
CheckItemStatus = "whichItem, status,",
CheckItemcodeStatus = "itemId, status,",
UnitId2OrderIdBJ = "unitId,",
String2UnitIdBJ = "unitIdString,",
UnitId2StringBJ = "unitId,",
String2OrderIdBJ = "orderIdString,",
OrderId2StringBJ = "orderId,",
GetIssuedOrderIdBJ = ",",
GetKillingUnitBJ = ",",
CreateUnitAtLocSaveLast = "id, unitid, loc, face,",
GetLastCreatedUnit = ",",
CreateNUnitsAtLoc = "count, unitId, whichPlayer, loc, face,",
CreateNUnitsAtLocFacingLocBJ = "count, unitId, whichPlayer, loc, lookAt,",
GetLastCreatedGroupEnum = ",",
GetLastCreatedGroup = ",",
CreateCorpseLocBJ = "unitid, whichPlayer, loc,",
UnitSuspendDecayBJ = "suspend, whichUnit,",
DelayedSuspendDecayStopAnimEnum = ",",
DelayedSuspendDecayBoneEnum = ",",
DelayedSuspendDecayFleshEnum = ",",
DelayedSuspendDecay = ",",
DelayedSuspendDecayCreate = ",",
CreatePermanentCorpseLocBJ = "style, unitid, whichPlayer, loc, facing,",
GetUnitStateSwap = "whichState, whichUnit,",
GetUnitStatePercent = "whichUnit, whichState, whichMaxState,",
GetUnitLifePercent = "whichUnit,",
GetUnitManaPercent = "whichUnit,",
SelectUnitSingle = "whichUnit,",
SelectGroupBJEnum = ",",
SelectGroupBJ = "g,",
SelectUnitAdd = "whichUnit,",
SelectUnitRemove = "whichUnit,",
ClearSelectionForPlayer = "whichPlayer,",
SelectUnitForPlayerSingle = "whichUnit, whichPlayer,",
SelectGroupForPlayerBJ = "g, whichPlayer,",
SelectUnitAddForPlayer = "whichUnit, whichPlayer,",
SelectUnitRemoveForPlayer = "whichUnit, whichPlayer,",
SetUnitLifeBJ = "whichUnit, newValue,",
SetUnitManaBJ = "whichUnit, newValue,",
SetUnitLifePercentBJ = "whichUnit, percent,",
SetUnitManaPercentBJ = "whichUnit, percent,",
IsUnitDeadBJ = "whichUnit,",
IsUnitAliveBJ = "whichUnit,",
IsUnitGroupDeadBJEnum = ",",
IsUnitGroupDeadBJ = "g,",
IsUnitGroupEmptyBJEnum = ",",
IsUnitGroupEmptyBJ = "g,",
IsUnitGroupInRectBJEnum = ",",
IsUnitGroupInRectBJ = "g, r,",
IsUnitHiddenBJ = "whichUnit,",
ShowUnitHide = "whichUnit,",
ShowUnitShow = "whichUnit,",
IssueHauntOrderAtLocBJFilter = ",",
IssueHauntOrderAtLocBJ = "whichPeon, loc,",
IssueBuildOrderByIdLocBJ = "whichPeon, unitId, loc,",
IssueTrainOrderByIdBJ = "whichUnit, unitId,",
GroupTrainOrderByIdBJ = "g, unitId,",
IssueUpgradeOrderByIdBJ = "whichUnit, techId,",
GetAttackedUnitBJ = ",",
SetUnitFlyHeightBJ = "whichUnit, newHeight, rate,",
SetUnitTurnSpeedBJ = "whichUnit, turnSpeed,",
SetUnitPropWindowBJ = "whichUnit, propWindow,",
GetUnitPropWindowBJ = "whichUnit,",
GetUnitDefaultPropWindowBJ = "whichUnit,",
SetUnitBlendTimeBJ = "whichUnit, blendTime,",
SetUnitAcquireRangeBJ = "whichUnit, acquireRange,",
UnitSetCanSleepBJ = "whichUnit, canSleep,",
UnitCanSleepBJ = "whichUnit,",
UnitWakeUpBJ = "whichUnit,",
UnitIsSleepingBJ = "whichUnit,",
WakePlayerUnitsEnum = ",",
WakePlayerUnits = "whichPlayer,",
EnableCreepSleepBJ = "enable,",
UnitGenerateAlarms = "whichUnit, generate,",
DoesUnitGenerateAlarms = "whichUnit,",
PauseAllUnitsBJEnum = ",",
PauseAllUnitsBJ = "pause,",
PauseUnitBJ = "pause, whichUnit,",
IsUnitPausedBJ = "whichUnit,",
UnitPauseTimedLifeBJ = "flag, whichUnit,",
UnitApplyTimedLifeBJ = "duration, buffId, whichUnit,",
UnitShareVisionBJ = "share, whichUnit, whichPlayer,",
UnitRemoveBuffsBJ = "buffType, whichUnit,",
UnitRemoveBuffsExBJ = "polarity, resist, whichUnit, bTLife, bAura,",
UnitCountBuffsExBJ = "polarity, resist, whichUnit, bTLife, bAura,",
UnitRemoveAbilityBJ = "abilityId, whichUnit,",
UnitAddAbilityBJ = "abilityId, whichUnit,",
UnitRemoveTypeBJ = "whichType, whichUnit,",
UnitAddTypeBJ = "whichType, whichUnit,",
UnitMakeAbilityPermanentBJ = "permanent, abilityId, whichUnit,",
SetUnitExplodedBJ = "whichUnit, exploded,",
ExplodeUnitBJ = "whichUnit,",
GetTransportUnitBJ = ",",
GetLoadedUnitBJ = ",",
IsUnitInTransportBJ = "whichUnit, whichTransport,",
IsUnitLoadedBJ = "whichUnit,",
IsUnitIllusionBJ = "whichUnit,",
ReplaceUnitBJ = "whichUnit, newUnitId, unitStateMethod,",
GetLastReplacedUnitBJ = ",",
SetUnitPositionLocFacingBJ = "whichUnit, loc, facing,",
SetUnitPositionLocFacingLocBJ = "whichUnit, loc, lookAt,",
AddItemToStockBJ = "itemId, whichUnit, currentStock, stockMax,",
AddUnitToStockBJ = "unitId, whichUnit, currentStock, stockMax,",
RemoveItemFromStockBJ = "itemId, whichUnit,",
RemoveUnitFromStockBJ = "unitId, whichUnit,",
SetUnitUseFoodBJ = "enable, whichUnit,",
UnitDamagePointLoc = "whichUnit, delay, radius, loc, amount, whichAttack, whichDamage,",
UnitDamageTargetBJ = "whichUnit, target, amount, whichAttack, whichDamage,",
CreateDestructableLoc = "objectid, loc, facing, scale, variation,",
CreateDeadDestructableLocBJ = "objectid, loc, facing, scale, variation,",
GetLastCreatedDestructable = ",",
ShowDestructableBJ = "flag, d,",
SetDestructableInvulnerableBJ = "d, flag,",
IsDestructableInvulnerableBJ = "d,",
GetDestructableLoc = "whichDestructable,",
EnumDestructablesInRectAll = "r, actionFunc,",
EnumDestructablesInCircleBJFilter = ",",
IsDestructableDeadBJ = "d,",
IsDestructableAliveBJ = "d,",
RandomDestructableInRectBJEnum = ",",
RandomDestructableInRectBJ = "r, filter,",
RandomDestructableInRectSimpleBJ = "r,",
EnumDestructablesInCircleBJ = "radius, loc, actionFunc,",
SetDestructableLifePercentBJ = "d, percent,",
SetDestructableMaxLifeBJ = "d, max,",
ModifyGateBJ = "gateOperation, d,",
GetElevatorHeight = "d,",
ChangeElevatorHeight = "d, newHeight,",
NudgeUnitsInRectEnum = ",",
NudgeItemsInRectEnum = ",",
NudgeObjectsInRect = "nudgeArea,",
NearbyElevatorExistsEnum = ",",
NearbyElevatorExists = "x, y,",
FindElevatorWallBlockerEnum = ",",
ChangeElevatorWallBlocker = "x, y, facing, open,",
ChangeElevatorWalls = "open, walls, d,",
WaygateActivateBJ = "activate, waygate,",
WaygateIsActiveBJ = "waygate,",
WaygateSetDestinationLocBJ = "waygate, loc,",
WaygateGetDestinationLocBJ = "waygate,",
UnitSetUsesAltIconBJ = "flag, whichUnit,",
ForceUIKeyBJ = "whichPlayer, key,",
ForceUICancelBJ = "whichPlayer,",
ForGroupBJ = "whichGroup, callback,",
GroupAddUnitSimple = "whichUnit, whichGroup,",
GroupRemoveUnitSimple = "whichUnit, whichGroup,",
GroupAddGroupEnum = ",",
GroupAddGroup = "sourceGroup, destGroup,",
GroupRemoveGroupEnum = ",",
GroupRemoveGroup = "sourceGroup, destGroup,",
ForceAddPlayerSimple = "whichPlayer, whichForce,",
ForceRemovePlayerSimple = "whichPlayer, whichForce,",
GroupPickRandomUnitEnum = ",",
GroupPickRandomUnit = "whichGroup,",
ForcePickRandomPlayerEnum = ",",
ForcePickRandomPlayer = "whichForce,",
EnumUnitsSelected = "whichPlayer, enumFilter, enumAction,",
GetUnitsInRectMatching = "r, filter,",
GetUnitsInRectAll = "r,",
GetUnitsInRectOfPlayerFilter = ",",
GetUnitsInRectOfPlayer = "r, whichPlayer,",
GetUnitsInRangeOfLocMatching = "radius, whichLocation, filter,",
GetUnitsInRangeOfLocAll = "radius, whichLocation,",
GetUnitsOfTypeIdAllFilter = ",",
GetUnitsOfTypeIdAll = "unitid,",
GetUnitsOfPlayerMatching = "whichPlayer, filter,",
GetUnitsOfPlayerAll = "whichPlayer,",
GetUnitsOfPlayerAndTypeIdFilter = ",",
GetUnitsOfPlayerAndTypeId = "whichPlayer, unitid,",
GetUnitsSelectedAll = "whichPlayer,",
GetForceOfPlayer = "whichPlayer,",
GetPlayersAll = ",",
GetPlayersByMapControl = "whichControl,",
GetPlayersAllies = "whichPlayer,",
GetPlayersEnemies = "whichPlayer,",
GetPlayersMatching = "filter,",
CountUnitsInGroupEnum = ",",
CountUnitsInGroup = "g,",
CountPlayersInForceEnum = ",",
CountPlayersInForceBJ = "f,",
GetRandomSubGroupEnum = ",",
GetRandomSubGroup = "count, sourceGroup,",
LivingPlayerUnitsOfTypeIdFilter = ",",
CountLivingPlayerUnitsOfTypeId = "unitId, whichPlayer,",
ResetUnitAnimation = "whichUnit,",
SetUnitTimeScalePercent = "whichUnit, percentScale,",
SetUnitScalePercent = "whichUnit, percentScaleX, percentScaleY, percentScaleZ,",
SetUnitVertexColorBJ = "whichUnit, red, green, blue, transparency,",
UnitAddIndicatorBJ = "whichUnit, red, green, blue, transparency,",
DestructableAddIndicatorBJ = "whichDestructable, red, green, blue, transparency,",
ItemAddIndicatorBJ = "whichItem, red, green, blue, transparency,",
SetUnitFacingToFaceLocTimed = "whichUnit, target, duration,",
SetUnitFacingToFaceUnitTimed = "whichUnit, target, duration,",
QueueUnitAnimationBJ = "whichUnit, whichAnimation,",
SetDestructableAnimationBJ = "d, whichAnimation,",
QueueDestructableAnimationBJ = "d, whichAnimation,",
SetDestAnimationSpeedPercent = "d, percentScale,",
DialogDisplayBJ = "flag, whichDialog, whichPlayer,",
DialogSetMessageBJ = "whichDialog, message,",
DialogAddButtonBJ = "whichDialog, buttonText,",
DialogAddButtonWithHotkeyBJ = "whichDialog, buttonText, hotkey,",
DialogClearBJ = "whichDialog,",
GetLastCreatedButtonBJ = ",",
GetClickedButtonBJ = ",",
GetClickedDialogBJ = ",",
SetPlayerAllianceBJ = "sourcePlayer, whichAllianceSetting, value, otherPlayer,",
SetPlayerAllianceStateAllyBJ = "sourcePlayer, otherPlayer, flag,",
SetPlayerAllianceStateVisionBJ = "sourcePlayer, otherPlayer, flag,",
SetPlayerAllianceStateControlBJ = "sourcePlayer, otherPlayer, flag,",
SetPlayerAllianceStateFullControlBJ = "sourcePlayer, otherPlayer, flag,",
SetPlayerAllianceStateBJ = "sourcePlayer, otherPlayer, allianceState,",
SetForceAllianceStateBJ = "sourceForce, targetForce, allianceState,",
PlayersAreCoAllied = "playerA, playerB,",
ShareEverythingWithTeamAI = "whichPlayer,",
ShareEverythingWithTeam = "whichPlayer,",
ConfigureNeutralVictim = ",",
MakeUnitsPassiveForPlayerEnum = ",",
MakeUnitsPassiveForPlayer = "whichPlayer,",
MakeUnitsPassiveForTeam = "whichPlayer,",
AllowVictoryDefeat = "gameResult,",
EndGameBJ = ",",
MeleeVictoryDialogBJ = "whichPlayer, leftGame,",
MeleeDefeatDialogBJ = "whichPlayer, leftGame,",
GameOverDialogBJ = "whichPlayer, leftGame,",
RemovePlayerPreserveUnitsBJ = "whichPlayer, gameResult, leftGame,",
CustomVictoryOkBJ = ",",
CustomVictoryQuitBJ = ",",
CustomVictoryDialogBJ = "whichPlayer,",
CustomVictorySkipBJ = "whichPlayer,",
CustomVictoryBJ = "whichPlayer, showDialog, showScores,",
CustomDefeatRestartBJ = ",",
CustomDefeatReduceDifficultyBJ = ",",
CustomDefeatLoadBJ = ",",
CustomDefeatQuitBJ = ",",
CustomDefeatDialogBJ = "whichPlayer, message,",
CustomDefeatBJ = "whichPlayer, message,",
SetNextLevelBJ = "nextLevel,",
SetPlayerOnScoreScreenBJ = "flag, whichPlayer,",
CreateQuestBJ = "questType, title, description, iconPath,",
DestroyQuestBJ = "whichQuest,",
QuestSetEnabledBJ = "enabled, whichQuest,",
QuestSetTitleBJ = "whichQuest, title,",
QuestSetDescriptionBJ = "whichQuest, description,",
QuestSetCompletedBJ = "whichQuest, completed,",
QuestSetFailedBJ = "whichQuest, failed,",
QuestSetDiscoveredBJ = "whichQuest, discovered,",
GetLastCreatedQuestBJ = ",",
CreateQuestItemBJ = "whichQuest, description,",
QuestItemSetDescriptionBJ = "questwhichQuestItem, description,",
QuestItemSetCompletedBJ = "questwhichQuestItem, completed,",
GetLastCreatedQuestItemBJ = ",",
CreateDefeatConditionBJ = "description,",
DestroyDefeatConditionBJ = "whichCondition,",
DefeatConditionSetDescriptionBJ = "whichCondition, description,",
GetLastCreatedDefeatConditionBJ = ",",
FlashQuestDialogButtonBJ = ",",
QuestMessageBJ = "f, messageType, message,",
StartTimerBJ = "t, periodic, timeout,",
CreateTimerBJ = "periodic, timeout,",
DestroyTimerBJ = "whichTimer,",
PauseTimerBJ = "pause, whichTimer,",
GetLastCreatedTimerBJ = ",",
CreateTimerDialogBJ = "t, title,",
DestroyTimerDialogBJ = "timertd,",
TimerDialogSetTitleBJ = "timertd, title,",
TimerDialogSetTitleColorBJ = "timertd, red, green, blue, transparency,",
TimerDialogSetTimeColorBJ = "timertd, red, green, blue, transparency,",
TimerDialogSetSpeedBJ = "timertd, speedMultFactor,",
TimerDialogDisplayForPlayerBJ = "show, timertd, whichPlayer,",
TimerDialogDisplayBJ = "show, timertd,",
GetLastCreatedTimerDialogBJ = ",",
LeaderboardResizeBJ = "lb,",
LeaderboardSetPlayerItemValueBJ = "whichPlayer, lb, val,",
LeaderboardSetPlayerItemLabelBJ = "whichPlayer, lb, val,",
LeaderboardSetPlayerItemStyleBJ = "whichPlayer, lb, showLabel, showValue, showIcon,",
LeaderboardSetPlayerItemLabelColorBJ = "whichPlayer, lb, red, green, blue, transparency,",
LeaderboardSetPlayerItemValueColorBJ = "whichPlayer, lb, red, green, blue, transparency,",
LeaderboardSetLabelColorBJ = "lb, red, green, blue, transparency,",
LeaderboardSetValueColorBJ = "lb, red, green, blue, transparency,",
LeaderboardSetLabelBJ = "lb, label,",
LeaderboardSetStyleBJ = "lb, showLabel, showNames, showValues, showIcon,",
LeaderboardGetItemCountBJ = "lb,",
LeaderboardHasPlayerItemBJ = "lb, whichPlayer,",
ForceSetLeaderboardBJ = "lb, toForce,",
CreateLeaderboardBJ = "toForce, label,",
DestroyLeaderboardBJ = "lb,",
LeaderboardDisplayBJ = "show, lb,",
LeaderboardAddItemBJ = "whichPlayer, lb, label, value,",
LeaderboardRemovePlayerItemBJ = "whichPlayer, lb,",
LeaderboardSortItemsBJ = "lb, sortType, ascending,",
LeaderboardSortItemsByPlayerBJ = "lb, ascending,",
LeaderboardSortItemsByLabelBJ = "lb, ascending,",
LeaderboardGetPlayerIndexBJ = "whichPlayer, lb,",
LeaderboardGetIndexedPlayerBJ = "position, lb,",
PlayerGetLeaderboardBJ = "whichPlayer,",
GetLastCreatedLeaderboard = ",",
CreateMultiboardBJ = "cols, rows, title,",
DestroyMultiboardBJ = "mb,",
GetLastCreatedMultiboard = ",",
MultiboardDisplayBJ = "show, mb,",
MultiboardMinimizeBJ = "minimize, mb,",
MultiboardSetTitleTextColorBJ = "mb, red, green, blue, transparency,",
MultiboardAllowDisplayBJ = "flag,",
MultiboardSetItemStyleBJ = "mb, col, row, showValue, showIcon,",
MultiboardSetItemValueBJ = "mb, col, row, val,",
MultiboardSetItemColorBJ = "mb, col, row, red, green, blue, transparency,",
MultiboardSetItemWidthBJ = "mb, col, row, width,",
MultiboardSetItemIconBJ = "mb, col, row, iconFileName,",
TextTagSize2Height = "size,",
TextTagSpeed2Velocity = "speed,",
SetTextTagColorBJ = "tt, red, green, blue, transparency,",
SetTextTagVelocityBJ = "tt, speed, angle,",
SetTextTagTextBJ = "tt, s, size,",
SetTextTagPosBJ = "tt, loc, zOffset,",
SetTextTagPosUnitBJ = "tt, whichUnit, zOffset,",
SetTextTagSuspendedBJ = "tt, flag,",
SetTextTagPermanentBJ = "tt, flag,",
SetTextTagAgeBJ = "tt, age,",
SetTextTagLifespanBJ = "tt, lifespan,",
SetTextTagFadepointBJ = "tt, fadepoint,",
CreateTextTagLocBJ = "s, loc, zOffset, size, red, green, blue, transparency,",
CreateTextTagUnitBJ = "s, whichUnit, zOffset, size, red, green, blue, transparency,",
DestroyTextTagBJ = "tt,",
ShowTextTagForceBJ = "show, tt, whichForce,",
GetLastCreatedTextTag = ",",
PauseGameOn = ",",
PauseGameOff = ",",
SetUserControlForceOn = "whichForce,",
SetUserControlForceOff = "whichForce,",
ShowInterfaceForceOn = "whichForce, fadeDuration,",
ShowInterfaceForceOff = "whichForce, fadeDuration,",
PingMinimapForForce = "whichForce, x, y, duration,",
PingMinimapLocForForce = "whichForce, loc, duration,",
PingMinimapForPlayer = "whichPlayer, x, y, duration,",
PingMinimapLocForPlayer = "whichPlayer, loc, duration,",
PingMinimapForForceEx = "whichForce, x, y, duration, style, red, green, blue,",
PingMinimapLocForForceEx = "whichForce, loc, duration, style, red, green, blue,",
EnableWorldFogBoundaryBJ = "enable, f,",
EnableOcclusionBJ = "enable, f,",
CancelCineSceneBJ = ",",
TryInitCinematicBehaviorBJ = ",",
SetCinematicSceneBJ = "soundHandle, portraitUnitId, color, speakerTitle, text, sceneDuration, voiceoverDuration,",
GetTransmissionDuration = "soundHandle, timeType, timeVal,",
WaitTransmissionDuration = "soundHandle, timeType, timeVal,",
DoTransmissionBasicsXYBJ = "unitId, color, x, y, soundHandle, unitName, message, duration,",
TransmissionFromUnitWithNameBJ = "toForce, whichUnit, unitName, soundHandle, message, timeType, timeVal, wait,",
PlayDialogueFromSpeakerEx = "toForce, speaker, speakerType, soundHandle, timeType, timeVal, wait,",
PlayDialogueFromSpeakerTypeEx = "toForce, fromPlayer, speakerType, loc, soundHandle, timeType, timeVal, wait,",
TransmissionFromUnitTypeWithNameBJ = "toForce, fromPlayer, unitId, unitName, loc, soundHandle, message, timeType, timeVal, wait,",
GetLastTransmissionDurationBJ = ",",
ForceCinematicSubtitlesBJ = "flag,",
CinematicModeExBJ = "cineMode, forForce, interfaceFadeTime,",
CinematicModeBJ = "cineMode, forForce,",
DisplayCineFilterBJ = "flag,",
CinematicFadeCommonBJ = "red, green, blue, duration, tex, startTrans, endTrans,",
FinishCinematicFadeBJ = ",",
FinishCinematicFadeAfterBJ = "duration,",
ContinueCinematicFadeBJ = ",",
ContinueCinematicFadeAfterBJ = "duration, red, green, blue, trans, tex,",
AbortCinematicFadeBJ = ",",
CinematicFadeBJ = "fadetype, duration, tex, red, green, blue, trans,",
CinematicFilterGenericBJ = "duration, bmode, tex, red0, green0, blue0, trans0, red1, green1, blue1, trans1,",
RescueUnitBJ = "whichUnit, rescuer, changeColor,",
TriggerActionUnitRescuedBJ = ",",
TryInitRescuableTriggersBJ = ",",
SetRescueUnitColorChangeBJ = "changeColor,",
SetRescueBuildingColorChangeBJ = "changeColor,",
MakeUnitRescuableToForceBJEnum = ",",
MakeUnitRescuableToForceBJ = "whichUnit, isRescuable, whichForce,",
InitRescuableBehaviorBJ = ",",
SetPlayerTechResearchedSwap = "techid, levels, whichPlayer,",
SetPlayerTechMaxAllowedSwap = "techid, maximum, whichPlayer,",
SetPlayerMaxHeroesAllowed = "maximum, whichPlayer,",
GetPlayerTechCountSimple = "techid, whichPlayer,",
GetPlayerTechMaxAllowedSwap = "techid, whichPlayer,",
SetPlayerAbilityAvailableBJ = "avail, abilid, whichPlayer,",
SetCampaignMenuRaceBJ = "campaignNumber,",
SetMissionAvailableBJ = "available, missionIndex,",
SetCampaignAvailableBJ = "available, campaignNumber,",
SetCinematicAvailableBJ = "available, cinematicIndex,",
InitGameCacheBJ = "campaignFile,",
SaveGameCacheBJ = "cache,",
GetLastCreatedGameCacheBJ = ",",
InitHashtableBJ = ",",
GetLastCreatedHashtableBJ = ",",
StoreRealBJ = "value, key, missionKey, cache,",
StoreIntegerBJ = "value, key, missionKey, cache,",
StoreBooleanBJ = "value, key, missionKey, cache,",
StoreStringBJ = "value, key, missionKey, cache,",
StoreUnitBJ = "whichUnit, key, missionKey, cache,",
SaveRealBJ = "value, key, missionKey, table,",
SaveIntegerBJ = "value, key, missionKey, table,",
SaveBooleanBJ = "value, key, missionKey, table,",
SaveStringBJ = "value, key, missionKey, table,",
SavePlayerHandleBJ = "whichPlayer, key, missionKey, table,",
SaveWidgetHandleBJ = "whichWidget, key, missionKey, table,",
SaveDestructableHandleBJ = "whichDestructable, key, missionKey, table,",
SaveItemHandleBJ = "whichItem, key, missionKey, table,",
SaveUnitHandleBJ = "whichUnit, key, missionKey, table,",
SaveAbilityHandleBJ = "whichAbility, key, missionKey, table,",
SaveTimerHandleBJ = "whichTimer, key, missionKey, table,",
SaveTriggerHandleBJ = "whichTrigger, key, missionKey, table,",
SaveTriggerConditionHandleBJ = "whichTriggercondition, key, missionKey, table,",
SaveTriggerActionHandleBJ = "whichTriggeraction, key, missionKey, table,",
SaveTriggerEventHandleBJ = "whichEvent, key, missionKey, table,",
SaveForceHandleBJ = "whichForce, key, missionKey, table,",
SaveGroupHandleBJ = "whichGroup, key, missionKey, table,",
SaveLocationHandleBJ = "whichLocation, key, missionKey, table,",
SaveRectHandleBJ = "whichRect, key, missionKey, table,",
SaveBooleanExprHandleBJ = "whichBoolexpr, key, missionKey, table,",
SaveSoundHandleBJ = "whichSound, key, missionKey, table,",
SaveEffectHandleBJ = "whichEffect, key, missionKey, table,",
SaveUnitPoolHandleBJ = "whichUnitpool, key, missionKey, table,",
SaveItemPoolHandleBJ = "whichItempool, key, missionKey, table,",
SaveQuestHandleBJ = "whichQuest, key, missionKey, table,",
SaveQuestItemHandleBJ = "questwhichQuestitem, key, missionKey, table,",
SaveDefeatConditionHandleBJ = "whichDefeatcondition, key, missionKey, table,",
SaveTimerDialogHandleBJ = "timerwhichTimerdialog, key, missionKey, table,",
SaveLeaderboardHandleBJ = "whichLeaderboard, key, missionKey, table,",
SaveMultiboardHandleBJ = "whichMultiboard, key, missionKey, table,",
SaveMultiboardItemHandleBJ = "multiboardwhichMultiboarditem, key, missionKey, table,",
SaveTrackableHandleBJ = "whichTrackable, key, missionKey, table,",
SaveDialogHandleBJ = "whichDialog, key, missionKey, table,",
SaveButtonHandleBJ = "whichButton, key, missionKey, table,",
SaveTextTagHandleBJ = "whichTexttag, key, missionKey, table,",
SaveLightningHandleBJ = "whichLightning, key, missionKey, table,",
SaveImageHandleBJ = "whichImage, key, missionKey, table,",
SaveUbersplatHandleBJ = "whichUbersplat, key, missionKey, table,",
SaveRegionHandleBJ = "whichRegion, key, missionKey, table,",
SaveFogStateHandleBJ = "whichFogState, key, missionKey, table,",
SaveFogModifierHandleBJ = "whichFogModifier, key, missionKey, table,",
SaveAgentHandleBJ = "whichAgent, key, missionKey, table,",
SaveHashtableHandleBJ = "whichHashtable, key, missionKey, table,",
GetStoredRealBJ = "key, missionKey, cache,",
GetStoredIntegerBJ = "key, missionKey, cache,",
GetStoredBooleanBJ = "key, missionKey, cache,",
GetStoredStringBJ = "key, missionKey, cache,",
LoadRealBJ = "key, missionKey, table,",
LoadIntegerBJ = "key, missionKey, table,",
LoadBooleanBJ = "key, missionKey, table,",
LoadStringBJ = "key, missionKey, table,",
LoadPlayerHandleBJ = "key, missionKey, table,",
LoadWidgetHandleBJ = "key, missionKey, table,",
LoadDestructableHandleBJ = "key, missionKey, table,",
LoadItemHandleBJ = "key, missionKey, table,",
LoadUnitHandleBJ = "key, missionKey, table,",
LoadAbilityHandleBJ = "key, missionKey, table,",
LoadTimerHandleBJ = "key, missionKey, table,",
LoadTriggerHandleBJ = "key, missionKey, table,",
LoadTriggerConditionHandleBJ = "key, missionKey, table,",
LoadTriggerActionHandleBJ = "key, missionKey, table,",
LoadTriggerEventHandleBJ = "key, missionKey, table,",
LoadForceHandleBJ = "key, missionKey, table,",
LoadGroupHandleBJ = "key, missionKey, table,",
LoadLocationHandleBJ = "key, missionKey, table,",
LoadRectHandleBJ = "key, missionKey, table,",
LoadBooleanExprHandleBJ = "key, missionKey, table,",
LoadSoundHandleBJ = "key, missionKey, table,",
LoadEffectHandleBJ = "key, missionKey, table,",
LoadUnitPoolHandleBJ = "key, missionKey, table,",
LoadItemPoolHandleBJ = "key, missionKey, table,",
LoadQuestHandleBJ = "key, missionKey, table,",
LoadQuestItemHandleBJ = "key, missionKey, table,",
LoadDefeatConditionHandleBJ = "key, missionKey, table,",
LoadTimerDialogHandleBJ = "key, missionKey, table,",
LoadLeaderboardHandleBJ = "key, missionKey, table,",
LoadMultiboardHandleBJ = "key, missionKey, table,",
LoadMultiboardItemHandleBJ = "key, missionKey, table,",
LoadTrackableHandleBJ = "key, missionKey, table,",
LoadDialogHandleBJ = "key, missionKey, table,",
LoadButtonHandleBJ = "key, missionKey, table,",
LoadTextTagHandleBJ = "key, missionKey, table,",
LoadLightningHandleBJ = "key, missionKey, table,",
LoadImageHandleBJ = "key, missionKey, table,",
LoadUbersplatHandleBJ = "key, missionKey, table,",
LoadRegionHandleBJ = "key, missionKey, table,",
LoadFogStateHandleBJ = "key, missionKey, table,",
LoadFogModifierHandleBJ = "key, missionKey, table,",
LoadHashtableHandleBJ = "key, missionKey, table,",
RestoreUnitLocFacingAngleBJ = "key, missionKey, cache, forWhichPlayer, loc, facing,",
RestoreUnitLocFacingPointBJ = "key, missionKey, cache, forWhichPlayer, loc, lookAt,",
GetLastRestoredUnitBJ = ",",
FlushGameCacheBJ = "cache,",
FlushStoredMissionBJ = "missionKey, cache,",
FlushParentHashtableBJ = "table,",
FlushChildHashtableBJ = "missionKey, table,",
HaveStoredValue = "key, valueType, missionKey, cache,",
HaveSavedValue = "key, valueType, missionKey, table,",
ShowCustomCampaignButton = "show, whichButton,",
IsCustomCampaignButtonVisibile = "whichButton,",
SaveGameCheckPointBJ = "mapSaveName, doCheckpointHint,",
LoadGameBJ = "loadFileName, doScoreScreen,",
SaveAndChangeLevelBJ = "saveFileName, newLevel, doScoreScreen,",
SaveAndLoadGameBJ = "saveFileName, loadFileName, doScoreScreen,",
RenameSaveDirectoryBJ = "sourceDirName, destDirName,",
RemoveSaveDirectoryBJ = "sourceDirName,",
CopySaveGameBJ = "sourceSaveName, destSaveName,",
GetPlayerStartLocationX = "whichPlayer,",
GetPlayerStartLocationY = "whichPlayer,",
GetPlayerStartLocationLoc = "whichPlayer,",
GetRectCenter = "whichRect,",
IsPlayerSlotState = "whichPlayer, whichState,",
GetFadeFromSeconds = "seconds,",
GetFadeFromSecondsAsReal = "seconds,",
AdjustPlayerStateSimpleBJ = "whichPlayer, whichPlayerState, delta,",
AdjustPlayerStateBJ = "delta, whichPlayer, whichPlayerState,",
SetPlayerStateBJ = "whichPlayer, whichPlayerState, value,",
SetPlayerFlagBJ = "whichPlayerFlag, flag, whichPlayer,",
SetPlayerTaxRateBJ = "rate, whichResource, sourcePlayer, otherPlayer,",
GetPlayerTaxRateBJ = "whichResource, sourcePlayer, otherPlayer,",
IsPlayerFlagSetBJ = "whichPlayerFlag, whichPlayer,",
AddResourceAmountBJ = "delta, whichUnit,",
GetConvertedPlayerId = "whichPlayer,",
ConvertedPlayer = "convertedPlayerId,",
GetRectWidthBJ = "r,",
GetRectHeightBJ = "r,",
BlightGoldMineForPlayerBJ = "goldMine, whichPlayer,",
BlightGoldMineForPlayer = "goldMine, whichPlayer,",
GetLastHauntedGoldMine = ",",
IsPointBlightedBJ = "where,",
SetPlayerColorBJEnum = ",",
SetPlayerColorBJ = "whichPlayer, color, changeExisting,",
SetPlayerUnitAvailableBJ = "unitId, allowed, whichPlayer,",
LockGameSpeedBJ = ",",
UnlockGameSpeedBJ = ",",
IssueTargetOrderBJ = "whichUnit, order, targetWidget,",
IssuePointOrderLocBJ = "whichUnit, order, whichLocation,",
IssueTargetDestructableOrder = "whichUnit, order, targetWidget,",
IssueTargetItemOrder = "whichUnit, order, targetWidget,",
IssueImmediateOrderBJ = "whichUnit, order,",
GroupTargetOrderBJ = "whichGroup, order, targetWidget,",
GroupPointOrderLocBJ = "whichGroup, order, whichLocation,",
GroupImmediateOrderBJ = "whichGroup, order,",
GroupTargetDestructableOrder = "whichGroup, order, targetWidget,",
GroupTargetItemOrder = "whichGroup, order, targetWidget,",
GetDyingDestructable = ",",
SetUnitRallyPoint = "whichUnit, targPos,",
SetUnitRallyUnit = "whichUnit, targUnit,",
SetUnitRallyDestructable = "whichUnit, targDest,",
SaveDyingWidget = ",",
SetBlightRectBJ = "addBlight, whichPlayer, r,",
SetBlightRadiusLocBJ = "addBlight, whichPlayer, loc, radius,",
GetAbilityName = "abilcode,",
MeleeStartingVisibility = ",",
MeleeStartingResources = ",",
ReducePlayerTechMaxAllowed = "whichPlayer, techId, limit,",
MeleeStartingHeroLimit = ",",
MeleeTrainedUnitIsHeroBJFilter = ",",
MeleeGrantItemsToHero = "whichUnit,",
MeleeGrantItemsToTrainedHero = ",",
MeleeGrantItemsToHiredHero = ",",
MeleeGrantHeroItems = ",",
MeleeClearExcessUnit = ",",
MeleeClearNearbyUnits = "x, y, range,",
MeleeClearExcessUnits = ",",
MeleeEnumFindNearestMine = ",",
MeleeFindNearestMine = "src, range,",
MeleeRandomHeroLoc = "p, id1, id2, id3, id4, loc,",
MeleeGetProjectedLoc = "src, targ, distance, deltaAngle,",
MeleeGetNearestValueWithin = "val, minVal, maxVal,",
MeleeGetLocWithinRect = "src, r,",
MeleeStartingUnitsHuman = "whichPlayer, startLoc, doHeroes, doCamera, doPreload,",
MeleeStartingUnitsOrc = "whichPlayer, startLoc, doHeroes, doCamera, doPreload,",
MeleeStartingUnitsUndead = "whichPlayer, startLoc, doHeroes, doCamera, doPreload,",
MeleeStartingUnitsNightElf = "whichPlayer, startLoc, doHeroes, doCamera, doPreload,",
MeleeStartingUnitsUnknownRace = "whichPlayer, startLoc, doHeroes, doCamera, doPreload,",
MeleeStartingUnits = ",",
MeleeStartingUnitsForPlayer = "whichRace, whichPlayer, loc, doHeroes,",
PickMeleeAI = "num, s1, s2, s3,",
MeleeStartingAI = ",",
LockGuardPosition = "targ,",
MeleePlayerIsOpponent = "playerIndex, opponentIndex,",
MeleeGetAllyStructureCount = "whichPlayer,",
MeleeGetAllyCount = "whichPlayer,",
MeleeGetAllyKeyStructureCount = "whichPlayer,",
MeleeDoDrawEnum = ",",
MeleeDoVictoryEnum = ",",
MeleeDoDefeat = "whichPlayer,",
MeleeDoDefeatEnum = ",",
MeleeDoLeave = "whichPlayer,",
MeleeRemoveObservers = ",",
MeleeCheckForVictors = ",",
MeleeCheckForLosersAndVictors = ",",
MeleeGetCrippledWarningMessage = "whichPlayer,",
MeleeGetCrippledTimerMessage = "whichPlayer,",
MeleeGetCrippledRevealedMessage = "whichPlayer,",
MeleeExposePlayer = "whichPlayer, expose,",
MeleeExposeAllPlayers = ",",
MeleeCrippledPlayerTimeout = ",",
MeleePlayerIsCrippled = "whichPlayer,",
MeleeCheckForCrippledPlayers = ",",
MeleeCheckLostUnit = "lostUnit,",
MeleeCheckAddedUnit = "addedUnit,",
MeleeTriggerActionConstructCancel = ",",
MeleeTriggerActionUnitDeath = ",",
MeleeTriggerActionUnitConstructionStart = ",",
MeleeTriggerActionPlayerDefeated = ",",
MeleeTriggerActionPlayerLeft = ",",
MeleeTriggerActionAllianceChange = ",",
MeleeTriggerTournamentFinishSoon = ",",
MeleeWasUserPlayer = "whichPlayer,",
MeleeTournamentFinishNowRuleA = "multiplier,",
MeleeTriggerTournamentFinishNow = ",",
MeleeInitVictoryDefeat = ",",
CheckInitPlayerSlotAvailability = ",",
SetPlayerSlotAvailable = "whichPlayer, control,",
TeamInitPlayerSlots = "teamCount,",
MeleeInitPlayerSlots = ",",
FFAInitPlayerSlots = ",",
OneOnOneInitPlayerSlots = ",",
InitGenericPlayerSlots = ",",
SetDNCSoundsDawn = ",",
SetDNCSoundsDusk = ",",
SetDNCSoundsDay = ",",
SetDNCSoundsNight = ",",
InitDNCSounds = ",",
InitBlizzardGlobals = ",",
InitQueuedTriggers = ",",
InitMapRects = ",",
InitSummonableCaps = ",",
UpdateStockAvailability = "whichItem,",
UpdateEachStockBuildingEnum = ",",
UpdateEachStockBuilding = "iType, iLevel,",
PerformStockUpdates = ",",
StartStockUpdates = ",",
RemovePurchasedItem = ",",
InitNeutralBuildings = ",",
MarkGameStarted = ",",
DetectGameStarted = ",",
InitBlizzard = ",",
RandomDistReset = ",",
RandomDistAddItem = "inID, inChance,",
RandomDistChoose = ",",
UnitDropItem = "inUnit, inItemID,",
WidgetDropItem = "inWidget, inItemID,",
BlzIsLastInstanceObjectFunctionSuccessful = ",",
BlzSetAbilityBooleanFieldBJ = "whichAbility, whichField, value,",
BlzSetAbilityIntegerFieldBJ = "whichAbility, whichField, value,",
BlzSetAbilityRealFieldBJ = "whichAbility, whichField, value,",
BlzSetAbilityStringFieldBJ = "whichAbility, whichField, value,",
BlzSetAbilityBooleanLevelFieldBJ = "whichAbility, whichField, level, value,",
BlzSetAbilityIntegerLevelFieldBJ = "whichAbility, whichField, level, value,",
BlzSetAbilityRealLevelFieldBJ = "whichAbility, whichField, level, value,",
BlzSetAbilityStringLevelFieldBJ = "whichAbility, whichField, level, value,",
BlzSetAbilityBooleanLevelArrayFieldBJ = "whichAbility, whichField, level, index, value,",
BlzSetAbilityIntegerLevelArrayFieldBJ = "whichAbility, whichField, level, index, value,",
BlzSetAbilityRealLevelArrayFieldBJ = "whichAbility, whichField, level, index, value,",
BlzSetAbilityStringLevelArrayFieldBJ = "whichAbility, whichField, level, index, value,",
BlzAddAbilityBooleanLevelArrayFieldBJ = "whichAbility, whichField, level, value,",
BlzAddAbilityIntegerLevelArrayFieldBJ = "whichAbility, whichField, level, value,",
BlzAddAbilityRealLevelArrayFieldBJ = "whichAbility, whichField, level, value,",
BlzAddAbilityStringLevelArrayFieldBJ = "whichAbility, whichField, level, value,",
BlzRemoveAbilityBooleanLevelArrayFieldBJ = "whichAbility, whichField, level, value,",
BlzRemoveAbilityIntegerLevelArrayFieldBJ = "whichAbility, whichField, level, value,",
BlzRemoveAbilityRealLevelArrayFieldBJ = "whichAbility, whichField, level, value,",
BlzRemoveAbilityStringLevelArrayFieldBJ = "whichAbility, whichField, level, value,",
BlzItemAddAbilityBJ = "whichItem, abilCode,",
BlzItemRemoveAbilityBJ = "whichItem, abilCode,",
BlzSetItemBooleanFieldBJ = "whichItem, whichField, value,",
BlzSetItemIntegerFieldBJ = "whichItem, whichField, value,",
BlzSetItemRealFieldBJ = "whichItem, whichField, value,",
BlzSetItemStringFieldBJ = "whichItem, whichField, value,",
BlzSetUnitBooleanFieldBJ = "whichUnit, whichField, value,",
BlzSetUnitIntegerFieldBJ = "whichUnit, whichField, value,",
BlzSetUnitRealFieldBJ = "whichUnit, whichField, value,",
BlzSetUnitStringFieldBJ = "whichUnit, whichField, value,",
BlzSetUnitWeaponBooleanFieldBJ = "whichUnit, whichField, index, value,",
BlzSetUnitWeaponIntegerFieldBJ = "whichUnit, whichField, index, value,",
BlzSetUnitWeaponRealFieldBJ = "whichUnit, whichField, index, value,",
BlzSetUnitWeaponStringFieldBJ = "whichUnit, whichField, index, value,",
ConvertRace = "i,",
ConvertAllianceType = "i,",
ConvertRacePref = "i,",
ConvertIGameState = "i,",
ConvertFGameState = "i,",
ConvertPlayerState = "i,",
ConvertPlayerScore = "i,",
ConvertPlayerGameResult = "i,",
ConvertUnitState = "i,",
ConvertAIDifficulty = "i,",
ConvertGameEvent = "i,",
ConvertPlayerEvent = "i,",
ConvertPlayerUnitEvent = "i,",
ConvertWidgetEvent = "i,",
ConvertDialogEvent = "i,",
ConvertUnitEvent = "i,",
ConvertLimitOp = "i,",
ConvertUnitType = "i,",
ConvertGameSpeed = "i,",
ConvertPlacement = "i,",
ConvertStartLocPrio = "i,",
ConvertGameDifficulty = "i,",
ConvertGameType = "i,",
ConvertMapFlag = "i,",
ConvertMapVisibility = "i,",
ConvertMapSetting = "i,",
ConvertMapDensity = "i,",
ConvertMapControl = "i,",
ConvertPlayerColor = "i,",
ConvertPlayerSlotState = "i,",
ConvertVolumeGroup = "i,",
ConvertCameraField = "i,",
ConvertBlendMode = "i,",
ConvertRarityControl = "i,",
ConvertTexMapFlags = "i,",
ConvertFogState = "i,",
ConvertEffectType = "i,",
ConvertVersion = "i,",
ConvertItemType = "i,",
ConvertAttackType = "i,",
ConvertDamageType = "i,",
ConvertWeaponType = "i,",
ConvertSoundType = "i,",
ConvertPathingType = "i,",
ConvertMouseButtonType = "i,",
ConvertAnimType = "i,",
ConvertSubAnimType = "i,",
ConvertOriginFrameType = "i,",
ConvertFramePointType = "i,",
ConvertTextAlignType = "i,",
ConvertFrameEventType = "i,",
ConvertOsKeyType = "i,",
ConvertAbilityIntegerField = "i,",
ConvertAbilityRealField = "i,",
ConvertAbilityBooleanField = "i,",
ConvertAbilityStringField = "i,",
ConvertAbilityIntegerLevelField = "i,",
ConvertAbilityRealLevelField = "i,",
ConvertAbilityBooleanLevelField = "i,",
ConvertAbilityStringLevelField = "i,",
ConvertAbilityIntegerLevelArrayField = "i,",
ConvertAbilityRealLevelArrayField = "i,",
ConvertAbilityBooleanLevelArrayField = "i,",
ConvertAbilityStringLevelArrayField = "i,",
ConvertUnitIntegerField = "i,",
ConvertUnitRealField = "i,",
ConvertUnitBooleanField = "i,",
ConvertUnitStringField = "i,",
ConvertUnitWeaponIntegerField = "i,",
ConvertUnitWeaponRealField = "i,",
ConvertUnitWeaponBooleanField = "i,",
ConvertUnitWeaponStringField = "i,",
ConvertItemIntegerField = "i,",
ConvertItemRealField = "i,",
ConvertItemBooleanField = "i,",
ConvertItemStringField = "i,",
ConvertMoveType = "i,",
ConvertTargetFlag = "i,",
ConvertArmorType = "i,",
ConvertHeroAttribute = "i,",
ConvertDefenseType = "i,",
ConvertRegenType = "i,",
ConvertUnitCategory = "i,",
ConvertPathingFlag = "i,",
OrderId = "orderIdString,",
OrderId2String = "orderId,",
UnitId = "unitIdString,",
UnitId2String = "unitId,",
AbilityId = "abilityIdString,",
AbilityId2String = "abilityId,",
GetObjectName = "objectId,",
GetBJMaxPlayers = ",",
GetBJPlayerNeutralVictim = ",",
GetBJPlayerNeutralExtra = ",",
GetBJMaxPlayerSlots = ",",
GetPlayerNeutralPassive = ",",
GetPlayerNeutralAggressive = ",",
Deg2Rad = "degrees,",
Rad2Deg = "radians,",
Sin = "radians,",
Cos = "radians,",
Tan = "radians,",
Asin = "y,",
Acos = "x,",
Atan = "x,",
Atan2 = "y, x,",
SquareRoot = "x,",
Pow = "x, power,",
MathRound = "r,",
I2R = "i,",
R2I = "r,",
I2S = "i,",
R2S = "r,",
R2SW = "r, width, precision,",
S2I = "s,",
S2R = "s,",
GetHandleId = "h,",
SubString = "source, start, end_,",
StringLength = "s,",
StringCase = "source, upper,",
StringHash = "s,",
GetLocalizedString = "source,",
GetLocalizedHotkey = "source,",
SetMapName = "name,",
SetMapDescription = "description,",
SetTeams = "teamcount,",
SetPlayers = "playercount,",
DefineStartLocation = "whichStartLoc, x, y,",
DefineStartLocationLoc = "whichStartLoc, whichLocation,",
SetStartLocPrioCount = "whichStartLoc, prioSlotCount,",
SetStartLocPrio = "whichStartLoc, prioSlotIndex, otherStartLocIndex, priority,",
GetStartLocPrioSlot = "whichStartLoc, prioSlotIndex,",
GetStartLocPrio = "whichStartLoc, prioSlotIndex,",
SetEnemyStartLocPrioCount = "whichStartLoc, prioSlotCount,",
SetEnemyStartLocPrio = "whichStartLoc, prioSlotIndex, otherStartLocIndex, priority,",
SetGameTypeSupported = "whichGameType, value,",
SetMapFlag = "whichMapFlag, value,",
SetGamePlacement = "whichPlacementType,",
SetGameSpeed = "whichspeed,",
SetGameDifficulty = "whichdifficulty,",
SetResourceDensity = "whichdensity,",
SetCreatureDensity = "whichdensity,",
GetTeams = ",",
GetPlayers = ",",
IsGameTypeSupported = "whichGameType,",
GetGameTypeSelected = ",",
IsMapFlagSet = "whichMapFlag,",
GetGamePlacement = ",",
GetGameSpeed = ",",
GetGameDifficulty = ",",
GetResourceDensity = ",",
GetCreatureDensity = ",",
GetStartLocationX = "whichStartLocation,",
GetStartLocationY = "whichStartLocation,",
GetStartLocationLoc = "whichStartLocation,",
SetPlayerTeam = "whichPlayer, whichTeam,",
SetPlayerStartLocation = "whichPlayer, startLocIndex,",
ForcePlayerStartLocation = "whichPlayer, startLocIndex,",
SetPlayerColor = "whichPlayer, color,",
SetPlayerAlliance = "sourcePlayer, otherPlayer, whichAllianceSetting, value,",
SetPlayerTaxRate = "sourcePlayer, otherPlayer, whichResource, rate,",
SetPlayerRacePreference = "whichPlayer, whichRacePreference,",
SetPlayerRaceSelectable = "whichPlayer, value,",
SetPlayerController = "whichPlayer, controlType,",
SetPlayerName = "whichPlayer, name,",
SetPlayerOnScoreScreen = "whichPlayer, flag,",
GetPlayerTeam = "whichPlayer,",
GetPlayerStartLocation = "whichPlayer,",
GetPlayerColor = "whichPlayer,",
GetPlayerSelectable = "whichPlayer,",
GetPlayerController = "whichPlayer,",
GetPlayerSlotState = "whichPlayer,",
GetPlayerTaxRate = "sourcePlayer, otherPlayer, whichResource,",
IsPlayerRacePrefSet = "whichPlayer, pref,",
GetPlayerName = "whichPlayer,",
CreateTimer = ",",
DestroyTimer = "whichTimer,",
TimerStart = "whichTimer, timeout, periodic, handlerFunc,",
TimerGetElapsed = "whichTimer,",
TimerGetRemaining = "whichTimer,",
TimerGetTimeout = "whichTimer,",
PauseTimer = "whichTimer,",
ResumeTimer = "whichTimer,",
GetExpiredTimer = ",",
CreateGroup = ",",
DestroyGroup = "whichGroup,",
GroupAddUnit = "whichGroup, whichUnit,",
GroupRemoveUnit = "whichGroup, whichUnit,",
BlzGroupAddGroupFast = "whichGroup, addGroup,",
BlzGroupRemoveGroupFast = "whichGroup, removeGroup,",
GroupClear = "whichGroup,",
BlzGroupGetSize = "whichGroup,",
BlzGroupUnitAt = "whichGroup, index,",
GroupEnumUnitsOfType = "whichGroup, unitname, filter,",
GroupEnumUnitsOfPlayer = "whichGroup, whichPlayer, filter,",
GroupEnumUnitsOfTypeCounted = "whichGroup, unitname, filter, countLimit,",
GroupEnumUnitsInRect = "whichGroup, r, filter,",
GroupEnumUnitsInRectCounted = "whichGroup, r, filter, countLimit,",
GroupEnumUnitsInRange = "whichGroup, x, y, radius, filter,",
GroupEnumUnitsInRangeOfLoc = "whichGroup, whichLocation, radius, filter,",
GroupEnumUnitsInRangeCounted = "whichGroup, x, y, radius, filter, countLimit,",
GroupEnumUnitsInRangeOfLocCounted = "whichGroup, whichLocation, radius, filter, countLimit,",
GroupEnumUnitsSelected = "whichGroup, whichPlayer, filter,",
GroupImmediateOrder = "whichGroup, order,",
GroupImmediateOrderById = "whichGroup, order,",
GroupPointOrder = "whichGroup, order, x, y,",
GroupPointOrderLoc = "whichGroup, order, whichLocation,",
GroupPointOrderById = "whichGroup, order, x, y,",
GroupPointOrderByIdLoc = "whichGroup, order, whichLocation,",
GroupTargetOrder = "whichGroup, order, targetWidget,",
GroupTargetOrderById = "whichGroup, order, targetWidget,",
ForGroup = "whichGroup, callback,",
FirstOfGroup = "whichGroup,",
CreateForce = ",",
DestroyForce = "whichForce,",
ForceAddPlayer = "whichForce, whichPlayer,",
ForceRemovePlayer = "whichForce, whichPlayer,",
BlzForceHasPlayer = "whichForce, whichPlayer,",
ForceClear = "whichForce,",
ForceEnumPlayers = "whichForce, filter,",
ForceEnumPlayersCounted = "whichForce, filter, countLimit,",
ForceEnumAllies = "whichForce, whichPlayer, filter,",
ForceEnumEnemies = "whichForce, whichPlayer, filter,",
ForForce = "whichForce, callback,",
Rect = "minx, miny, maxx, maxy,",
RectFromLoc = "min, max,",
RemoveRect = "whichRect,",
SetRect = "whichRect, minx, miny, maxx, maxy,",
SetRectFromLoc = "whichRect, min, max,",
MoveRectTo = "whichRect, newCenterX, newCenterY,",
MoveRectToLoc = "whichRect, newCenterLoc,",
GetRectCenterX = "whichRect,",
GetRectCenterY = "whichRect,",
GetRectMinX = "whichRect,",
GetRectMinY = "whichRect,",
GetRectMaxX = "whichRect,",
GetRectMaxY = "whichRect,",
CreateRegion = ",",
RemoveRegion = "whichRegion,",
RegionAddRect = "whichRegion, r,",
RegionClearRect = "whichRegion, r,",
RegionAddCell = "whichRegion, x, y,",
RegionAddCellAtLoc = "whichRegion, whichLocation,",
RegionClearCell = "whichRegion, x, y,",
RegionClearCellAtLoc = "whichRegion, whichLocation,",
Location = "x, y,",
RemoveLocation = "whichLocation,",
MoveLocation = "whichLocation, newX, newY,",
GetLocationX = "whichLocation,",
GetLocationY = "whichLocation,",
GetLocationZ = "whichLocation,",
IsUnitInRegion = "whichRegion, whichUnit,",
IsPointInRegion = "whichRegion, x, y,",
IsLocationInRegion = "whichRegion, whichLocation,",
GetWorldBounds = ",",
CreateTrigger = ",",
DestroyTrigger = "whichTrigger,",
ResetTrigger = "whichTrigger,",
EnableTrigger = "whichTrigger,",
DisableTrigger = "whichTrigger,",
IsTriggerEnabled = "whichTrigger,",
TriggerWaitOnSleeps = "whichTrigger, flag,",
IsTriggerWaitOnSleeps = "whichTrigger,",
GetFilterUnit = ",",
GetEnumUnit = ",",
GetFilterDestructable = ",",
GetEnumDestructable = ",",
GetFilterItem = ",",
GetEnumItem = ",",
ParseTags = "taggedString,",
GetFilterPlayer = ",",
GetEnumPlayer = ",",
GetTriggeringTrigger = ",",
GetTriggerEventId = ",",
GetTriggerEvalCount = "whichTrigger,",
GetTriggerExecCount = "whichTrigger,",
ExecuteFunc = "funcName,",
And = "operandA, operandB,",
Or = "operandA, operandB,",
Not = "operand,",
Condition = "func,",
DestroyCondition = "c,",
Filter = "func,",
DestroyFilter = "f,",
DestroyBoolExpr = "e,",
TriggerRegisterVariableEvent = "whichTrigger, varName, opcode, limitval,",
TriggerRegisterTimerEvent = "whichTrigger, timeout, periodic,",
TriggerRegisterTimerExpireEvent = "whichTrigger, t,",
TriggerRegisterGameStateEvent = "whichTrigger, whichState, opcode, limitval,",
TriggerRegisterDialogEvent = "whichTrigger, whichDialog,",
TriggerRegisterDialogButtonEvent = "whichTrigger, whichButton,",
GetEventGameState = ",",
TriggerRegisterGameEvent = "whichTrigger, whichGameEvent,",
GetWinningPlayer = ",",
TriggerRegisterEnterRegion = "whichTrigger, whichRegion, filter,",
GetTriggeringRegion = ",",
GetEnteringUnit = ",",
TriggerRegisterLeaveRegion = "whichTrigger, whichRegion, filter,",
GetLeavingUnit = ",",
TriggerRegisterTrackableHitEvent = "whichTrigger, t,",
TriggerRegisterTrackableTrackEvent = "whichTrigger, t,",
TriggerRegisterCommandEvent = "whichTrigger, whichAbility, order,",
TriggerRegisterUpgradeCommandEvent = "whichTrigger, whichUpgrade,",
GetTriggeringTrackable = ",",
GetClickedButton = ",",
GetClickedDialog = ",",
GetTournamentFinishSoonTimeRemaining = ",",
GetTournamentFinishNowRule = ",",
GetTournamentFinishNowPlayer = ",",
GetTournamentScore = "whichPlayer,",
GetSaveBasicFilename = ",",
TriggerRegisterPlayerEvent = "whichTrigger, whichPlayer, whichPlayerEvent,",
GetTriggerPlayer = ",",
TriggerRegisterPlayerUnitEvent = "whichTrigger, whichPlayer, whichPlayerUnitEvent, filter,",
GetLevelingUnit = ",",
GetLearningUnit = ",",
GetLearnedSkill = ",",
GetLearnedSkillLevel = ",",
GetRevivableUnit = ",",
GetRevivingUnit = ",",
GetAttacker = ",",
GetRescuer = ",",
GetDyingUnit = ",",
GetKillingUnit = ",",
GetDecayingUnit = ",",
GetConstructingStructure = ",",
GetCancelledStructure = ",",
GetConstructedStructure = ",",
GetResearchingUnit = ",",
GetResearched = ",",
GetTrainedUnitType = ",",
GetTrainedUnit = ",",
GetDetectedUnit = ",",
GetSummoningUnit = ",",
GetSummonedUnit = ",",
GetTransportUnit = ",",
GetLoadedUnit = ",",
GetSellingUnit = ",",
GetSoldUnit = ",",
GetBuyingUnit = ",",
GetSoldItem = ",",
GetChangingUnit = ",",
GetChangingUnitPrevOwner = ",",
GetManipulatingUnit = ",",
GetManipulatedItem = ",",
BlzGetAbsorbingItem = ",",
BlzGetManipulatedItemWasAbsorbed = ",",
BlzGetStackingItemSource = ",",
BlzGetStackingItemTarget = ",",
BlzGetStackingItemTargetPreviousCharges = ",",
GetOrderedUnit = ",",
GetIssuedOrderId = ",",
GetOrderPointX = ",",
GetOrderPointY = ",",
GetOrderPointLoc = ",",
GetOrderTarget = ",",
GetOrderTargetDestructable = ",",
GetOrderTargetItem = ",",
GetOrderTargetUnit = ",",
GetSpellAbilityUnit = ",",
GetSpellAbilityId = ",",
GetSpellAbility = ",",
GetSpellTargetLoc = ",",
GetSpellTargetX = ",",
GetSpellTargetY = ",",
GetSpellTargetDestructable = ",",
GetSpellTargetItem = ",",
GetSpellTargetUnit = ",",
TriggerRegisterPlayerAllianceChange = "whichTrigger, whichPlayer, whichAlliance,",
TriggerRegisterPlayerStateEvent = "whichTrigger, whichPlayer, whichState, opcode, limitval,",
GetEventPlayerState = ",",
TriggerRegisterPlayerChatEvent = "whichTrigger, whichPlayer, chatMessageToDetect, exactMatchOnly,",
GetEventPlayerChatString = ",",
GetEventPlayerChatStringMatched = ",",
TriggerRegisterDeathEvent = "whichTrigger, whichWidget,",
GetTriggerUnit = ",",
TriggerRegisterUnitStateEvent = "whichTrigger, whichUnit, whichState, opcode, limitval,",
GetEventUnitState = ",",
TriggerRegisterUnitEvent = "whichTrigger, whichUnit, whichEvent,",
GetEventDamage = ",",
GetEventDamageSource = ",",
GetEventDetectingPlayer = ",",
TriggerRegisterFilterUnitEvent = "whichTrigger, whichUnit, whichEvent, filter,",
GetEventTargetUnit = ",",
TriggerRegisterUnitInRange = "whichTrigger, whichUnit, range, filter,",
TriggerAddCondition = "whichTrigger, condition,",
TriggerRemoveCondition = "whichTrigger, whichCondition,",
TriggerClearConditions = "whichTrigger,",
TriggerAddAction = "whichTrigger, actionFunc,",
TriggerRemoveAction = "whichTrigger, whichAction,",
TriggerClearActions = "whichTrigger,",
TriggerSleepAction = "timeout,",
TriggerWaitForSound = "s, offset,",
TriggerEvaluate = "whichTrigger,",
TriggerExecute = "whichTrigger,",
TriggerExecuteWait = "whichTrigger,",
TriggerSyncStart = ",",
TriggerSyncReady = ",",
GetWidgetLife = "whichWidget,",
SetWidgetLife = "whichWidget, newLife,",
GetWidgetX = "whichWidget,",
GetWidgetY = "whichWidget,",
GetTriggerWidget = ",",
CreateDestructable = "objectid, x, y, face, scale, variation,",
CreateDestructableZ = "objectid, x, y, z, face, scale, variation,",
CreateDeadDestructable = "objectid, x, y, face, scale, variation,",
CreateDeadDestructableZ = "objectid, x, y, z, face, scale, variation,",
RemoveDestructable = "d,",
KillDestructable = "d,",
SetDestructableInvulnerable = "d, flag,",
IsDestructableInvulnerable = "d,",
EnumDestructablesInRect = "r, filter, actionFunc,",
GetDestructableTypeId = "d,",
GetDestructableX = "d,",
GetDestructableY = "d,",
SetDestructableLife = "d, life,",
GetDestructableLife = "d,",
SetDestructableMaxLife = "d, max,",
GetDestructableMaxLife = "d,",
DestructableRestoreLife = "d, life, birth,",
QueueDestructableAnimation = "d, whichAnimation,",
SetDestructableAnimation = "d, whichAnimation,",
SetDestructableAnimationSpeed = "d, speedFactor,",
ShowDestructable = "d, flag,",
GetDestructableOccluderHeight = "d,",
SetDestructableOccluderHeight = "d, height,",
GetDestructableName = "d,",
GetTriggerDestructable = ",",
CreateItem = "itemid, x, y,",
RemoveItem = "whichItem,",
GetItemPlayer = "whichItem,",
GetItemTypeId = "i,",
GetItemX = "i,",
GetItemY = "i,",
SetItemPosition = "i, x, y,",
SetItemDropOnDeath = "whichItem, flag,",
SetItemDroppable = "i, flag,",
SetItemPawnable = "i, flag,",
SetItemPlayer = "whichItem, whichPlayer, changeColor,",
SetItemInvulnerable = "whichItem, flag,",
IsItemInvulnerable = "whichItem,",
SetItemVisible = "whichItem, show,",
IsItemVisible = "whichItem,",
IsItemOwned = "whichItem,",
IsItemPowerup = "whichItem,",
IsItemSellable = "whichItem,",
IsItemPawnable = "whichItem,",
IsItemIdPowerup = "itemId,",
IsItemIdSellable = "itemId,",
IsItemIdPawnable = "itemId,",
EnumItemsInRect = "r, filter, actionFunc,",
GetItemLevel = "whichItem,",
GetItemType = "whichItem,",
SetItemDropID = "whichItem, unitId,",
GetItemName = "whichItem,",
GetItemCharges = "whichItem,",
SetItemCharges = "whichItem, charges,",
GetItemUserData = "whichItem,",
SetItemUserData = "whichItem, data,",
CreateUnit = "id, unitid, x, y, face,",
CreateUnitByName = "whichPlayer, unitname, x, y, face,",
CreateUnitAtLoc = "id, unitid, whichLocation, face,",
CreateUnitAtLocByName = "id, unitname, whichLocation, face,",
CreateCorpse = "whichPlayer, unitid, x, y, face,",
KillUnit = "whichUnit,",
RemoveUnit = "whichUnit,",
ShowUnit = "whichUnit, show,",
SetUnitState = "whichUnit, whichUnitState, newVal,",
SetUnitX = "whichUnit, newX,",
SetUnitY = "whichUnit, newY,",
SetUnitPosition = "whichUnit, newX, newY,",
SetUnitPositionLoc = "whichUnit, whichLocation,",
SetUnitFacing = "whichUnit, facingAngle,",
SetUnitFacingTimed = "whichUnit, facingAngle, duration,",
SetUnitMoveSpeed = "whichUnit, newSpeed,",
SetUnitFlyHeight = "whichUnit, newHeight, rate,",
SetUnitTurnSpeed = "whichUnit, newTurnSpeed,",
SetUnitPropWindow = "whichUnit, newPropWindowAngle,",
SetUnitAcquireRange = "whichUnit, newAcquireRange,",
SetUnitCreepGuard = "whichUnit, creepGuard,",
GetUnitAcquireRange = "whichUnit,",
GetUnitTurnSpeed = "whichUnit,",
GetUnitPropWindow = "whichUnit,",
GetUnitFlyHeight = "whichUnit,",
GetUnitDefaultAcquireRange = "whichUnit,",
GetUnitDefaultTurnSpeed = "whichUnit,",
GetUnitDefaultPropWindow = "whichUnit,",
GetUnitDefaultFlyHeight = "whichUnit,",
SetUnitOwner = "whichUnit, whichPlayer, changeColor,",
SetUnitColor = "whichUnit, whichColor,",
SetUnitScale = "whichUnit, scaleX, scaleY, scaleZ,",
SetUnitTimeScale = "whichUnit, timeScale,",
SetUnitBlendTime = "whichUnit, blendTime,",
SetUnitVertexColor = "whichUnit, red, green, blue, alpha,",
QueueUnitAnimation = "whichUnit, whichAnimation,",
SetUnitAnimation = "whichUnit, whichAnimation,",
SetUnitAnimationByIndex = "whichUnit, whichAnimation,",
SetUnitAnimationWithRarity = "whichUnit, whichAnimation, rarity,",
AddUnitAnimationProperties = "whichUnit, animProperties, add,",
SetUnitLookAt = "whichUnit, whichBone, lookAtTarget, offsetX, offsetY, offsetZ,",
ResetUnitLookAt = "whichUnit,",
SetUnitRescuable = "whichUnit, byWhichPlayer, flag,",
SetUnitRescueRange = "whichUnit, range,",
SetHeroStr = "whichHero, newStr, permanent,",
SetHeroAgi = "whichHero, newAgi, permanent,",
SetHeroInt = "whichHero, newInt, permanent,",
GetHeroStr = "whichHero, includeBonuses,",
GetHeroAgi = "whichHero, includeBonuses,",
GetHeroInt = "whichHero, includeBonuses,",
UnitStripHeroLevel = "whichHero, howManyLevels,",
GetHeroXP = "whichHero,",
SetHeroXP = "whichHero, newXpVal, showEyeCandy,",
GetHeroSkillPoints = "whichHero,",
UnitModifySkillPoints = "whichHero, skillPointDelta,",
AddHeroXP = "whichHero, xpToAdd, showEyeCandy,",
SetHeroLevel = "whichHero, level, showEyeCandy,",
GetHeroLevel = "whichHero,",
GetUnitLevel = "whichUnit,",
GetHeroProperName = "whichHero,",
SuspendHeroXP = "whichHero, flag,",
IsSuspendedXP = "whichHero,",
SelectHeroSkill = "whichHero, abilcode,",
GetUnitAbilityLevel = "whichUnit, abilcode,",
DecUnitAbilityLevel = "whichUnit, abilcode,",
IncUnitAbilityLevel = "whichUnit, abilcode,",
SetUnitAbilityLevel = "whichUnit, abilcode, level,",
ReviveHero = "whichHero, x, y, doEyecandy,",
ReviveHeroLoc = "whichHero, loc, doEyecandy,",
SetUnitExploded = "whichUnit, exploded,",
SetUnitInvulnerable = "whichUnit, flag,",
PauseUnit = "whichUnit, flag,",
IsUnitPaused = "whichHero,",
SetUnitPathing = "whichUnit, flag,",
ClearSelection = ",",
SelectUnit = "whichUnit, flag,",
GetUnitPointValue = "whichUnit,",
GetUnitPointValueByType = "unitType,",
UnitAddItem = "whichUnit, whichItem,",
UnitAddItemById = "whichUnit, itemId,",
UnitAddItemToSlotById = "whichUnit, itemId, itemSlot,",
UnitRemoveItem = "whichUnit, whichItem,",
UnitRemoveItemFromSlot = "whichUnit, itemSlot,",
UnitHasItem = "whichUnit, whichItem,",
UnitItemInSlot = "whichUnit, itemSlot,",
UnitInventorySize = "whichUnit,",
UnitDropItemPoint = "whichUnit, whichItem, x, y,",
UnitDropItemSlot = "whichUnit, whichItem, slot,",
UnitDropItemTarget = "whichUnit, whichItem, target,",
UnitUseItem = "whichUnit, whichItem,",
UnitUseItemPoint = "whichUnit, whichItem, x, y,",
UnitUseItemTarget = "whichUnit, whichItem, target,",
GetUnitX = "whichUnit,",
GetUnitY = "whichUnit,",
GetUnitLoc = "whichUnit,",
GetUnitFacing = "whichUnit,",
GetUnitMoveSpeed = "whichUnit,",
GetUnitDefaultMoveSpeed = "whichUnit,",
GetUnitState = "whichUnit, whichUnitState,",
GetOwningPlayer = "whichUnit,",
GetUnitTypeId = "whichUnit,",
GetUnitRace = "whichUnit,",
GetUnitName = "whichUnit,",
GetUnitFoodUsed = "whichUnit,",
GetUnitFoodMade = "whichUnit,",
GetFoodMade = "unitId,",
GetFoodUsed = "unitId,",
SetUnitUseFood = "whichUnit, useFood,",
GetUnitRallyPoint = "whichUnit,",
GetUnitRallyUnit = "whichUnit,",
GetUnitRallyDestructable = "whichUnit,",
IsUnitInGroup = "whichUnit, whichGroup,",
IsUnitInForce = "whichUnit, whichForce,",
IsUnitOwnedByPlayer = "whichUnit, whichPlayer,",
IsUnitAlly = "whichUnit, whichPlayer,",
IsUnitEnemy = "whichUnit, whichPlayer,",
IsUnitVisible = "whichUnit, whichPlayer,",
IsUnitDetected = "whichUnit, whichPlayer,",
IsUnitInvisible = "whichUnit, whichPlayer,",
IsUnitFogged = "whichUnit, whichPlayer,",
IsUnitMasked = "whichUnit, whichPlayer,",
IsUnitSelected = "whichUnit, whichPlayer,",
IsUnitRace = "whichUnit, whichRace,",
IsUnitType = "whichUnit, whichUnitType,",
IsUnit = "whichUnit, whichSpecifiedUnit,",
IsUnitInRange = "whichUnit, otherUnit, distance,",
IsUnitInRangeXY = "whichUnit, x, y, distance,",
IsUnitInRangeLoc = "whichUnit, whichLocation, distance,",
IsUnitHidden = "whichUnit,",
IsUnitIllusion = "whichUnit,",
IsUnitInTransport = "whichUnit, whichTransport,",
IsUnitLoaded = "whichUnit,",
IsHeroUnitId = "unitId,",
IsUnitIdType = "unitId, whichUnitType,",
UnitShareVision = "whichUnit, whichPlayer, share,",
UnitSuspendDecay = "whichUnit, suspend,",
UnitAddType = "whichUnit, whichUnitType,",
UnitRemoveType = "whichUnit, whichUnitType,",
UnitAddAbility = "whichUnit, abilityId,",
UnitRemoveAbility = "whichUnit, abilityId,",
UnitMakeAbilityPermanent = "whichUnit, permanent, abilityId,",
UnitRemoveBuffs = "whichUnit, removePositive, removeNegative,",
UnitRemoveBuffsEx = "whichUnit, removePositive, removeNegative, magic, physical, timedLife, aura, autoDispel,",
UnitHasBuffsEx = "whichUnit, removePositive, removeNegative, magic, physical, timedLife, aura, autoDispel,",
UnitCountBuffsEx = "whichUnit, removePositive, removeNegative, magic, physical, timedLife, aura, autoDispel,",
UnitAddSleep = "whichUnit, add,",
UnitCanSleep = "whichUnit,",
UnitAddSleepPerm = "whichUnit, add,",
UnitCanSleepPerm = "whichUnit,",
UnitIsSleeping = "whichUnit,",
UnitWakeUp = "whichUnit,",
UnitApplyTimedLife = "whichUnit, buffId, duration,",
UnitIgnoreAlarm = "whichUnit, flag,",
UnitIgnoreAlarmToggled = "whichUnit,",
UnitResetCooldown = "whichUnit,",
UnitSetConstructionProgress = "whichUnit, constructionPercentage,",
UnitSetUpgradeProgress = "whichUnit, upgradePercentage,",
UnitPauseTimedLife = "whichUnit, flag,",
UnitSetUsesAltIcon = "whichUnit, flag,",
UnitDamagePoint = "whichUnit, delay, radius, x, y, amount, attack, ranged, attackType, damageType, weaponType,",
UnitDamageTarget = "whichUnit, target, amount, attack, ranged, attackType, damageType, weaponType,",
IssueImmediateOrder = "whichUnit, order,",
IssueImmediateOrderById = "whichUnit, order,",
IssuePointOrder = "whichUnit, order, x, y,",
IssuePointOrderLoc = "whichUnit, order, whichLocation,",
IssuePointOrderById = "whichUnit, order, x, y,",
IssuePointOrderByIdLoc = "whichUnit, order, whichLocation,",
IssueTargetOrder = "whichUnit, order, targetWidget,",
IssueTargetOrderById = "whichUnit, order, targetWidget,",
IssueInstantPointOrder = "whichUnit, order, x, y, instantTargetWidget,",
IssueInstantPointOrderById = "whichUnit, order, x, y, instantTargetWidget,",
IssueInstantTargetOrder = "whichUnit, order, targetWidget, instantTargetWidget,",
IssueInstantTargetOrderById = "whichUnit, order, targetWidget, instantTargetWidget,",
IssueBuildOrder = "whichPeon, unitToBuild, x, y,",
IssueBuildOrderById = "whichPeon, unitId, x, y,",
IssueNeutralImmediateOrder = "forWhichPlayer, neutralStructure, unitToBuild,",
IssueNeutralImmediateOrderById = "forWhichPlayer, neutralStructure, unitId,",
IssueNeutralPointOrder = "forWhichPlayer, neutralStructure, unitToBuild, x, y,",
IssueNeutralPointOrderById = "forWhichPlayer, neutralStructure, unitId, x, y,",
IssueNeutralTargetOrder = "forWhichPlayer, neutralStructure, unitToBuild, target,",
IssueNeutralTargetOrderById = "forWhichPlayer, neutralStructure, unitId, target,",
GetUnitCurrentOrder = "whichUnit,",
SetResourceAmount = "whichUnit, amount,",
AddResourceAmount = "whichUnit, amount,",
GetResourceAmount = "whichUnit,",
WaygateGetDestinationX = "waygate,",
WaygateGetDestinationY = "waygate,",
WaygateSetDestination = "waygate, x, y,",
WaygateActivate = "waygate, activate,",
WaygateIsActive = "waygate,",
AddItemToAllStock = "itemId, currentStock, stockMax,",
AddItemToStock = "whichUnit, itemId, currentStock, stockMax,",
AddUnitToAllStock = "unitId, currentStock, stockMax,",
AddUnitToStock = "whichUnit, unitId, currentStock, stockMax,",
RemoveItemFromAllStock = "itemId,",
RemoveItemFromStock = "whichUnit, itemId,",
RemoveUnitFromAllStock = "unitId,",
RemoveUnitFromStock = "whichUnit, unitId,",
SetAllItemTypeSlots = "slots,",
SetAllUnitTypeSlots = "slots,",
SetItemTypeSlots = "whichUnit, slots,",
SetUnitTypeSlots = "whichUnit, slots,",
GetUnitUserData = "whichUnit,",
SetUnitUserData = "whichUnit, data,",
Player = "number,",
GetLocalPlayer = ",",
IsPlayerAlly = "whichPlayer, otherPlayer,",
IsPlayerEnemy = "whichPlayer, otherPlayer,",
IsPlayerInForce = "whichPlayer, whichForce,",
IsPlayerObserver = "whichPlayer,",
IsVisibleToPlayer = "x, y, whichPlayer,",
IsLocationVisibleToPlayer = "whichLocation, whichPlayer,",
IsFoggedToPlayer = "x, y, whichPlayer,",
IsLocationFoggedToPlayer = "whichLocation, whichPlayer,",
IsMaskedToPlayer = "x, y, whichPlayer,",
IsLocationMaskedToPlayer = "whichLocation, whichPlayer,",
GetPlayerRace = "whichPlayer,",
GetPlayerId = "whichPlayer,",
GetPlayerUnitCount = "whichPlayer, includeIncomplete,",
GetPlayerTypedUnitCount = "whichPlayer, unitName, includeIncomplete, includeUpgrades,",
GetPlayerStructureCount = "whichPlayer, includeIncomplete,",
GetPlayerState = "whichPlayer, whichPlayerState,",
GetPlayerScore = "whichPlayer, whichPlayerScore,",
GetPlayerAlliance = "sourcePlayer, otherPlayer, whichAllianceSetting,",
GetPlayerHandicap = "whichPlayer,",
GetPlayerHandicapXP = "whichPlayer,",
GetPlayerHandicapReviveTime = "whichPlayer,",
GetPlayerHandicapDamage = "whichPlayer,",
SetPlayerHandicap = "whichPlayer, handicap,",
SetPlayerHandicapXP = "whichPlayer, handicap,",
SetPlayerHandicapReviveTime = "whichPlayer, handicap,",
SetPlayerHandicapDamage = "whichPlayer, handicap,",
SetPlayerTechMaxAllowed = "whichPlayer, techid, maximum,",
GetPlayerTechMaxAllowed = "whichPlayer, techid,",
AddPlayerTechResearched = "whichPlayer, techid, levels,",
SetPlayerTechResearched = "whichPlayer, techid, setToLevel,",
GetPlayerTechResearched = "whichPlayer, techid, specificonly,",
GetPlayerTechCount = "whichPlayer, techid, specificonly,",
SetPlayerUnitsOwner = "whichPlayer, newOwner,",
CripplePlayer = "whichPlayer, toWhichPlayers, flag,",
SetPlayerAbilityAvailable = "whichPlayer, abilid, avail,",
SetPlayerState = "whichPlayer, whichPlayerState, value,",
RemovePlayer = "whichPlayer, gameResult,",
CachePlayerHeroData = "whichPlayer,",
SetFogStateRect = "forWhichPlayer, whichState, where, useSharedVision,",
SetFogStateRadius = "forWhichPlayer, whichState, centerx, centerY, radius, useSharedVision,",
SetFogStateRadiusLoc = "forWhichPlayer, whichState, center, radius, useSharedVision,",
FogMaskEnable = "enable,",
IsFogMaskEnabled = ",",
FogEnable = "enable,",
IsFogEnabled = ",",
CreateFogModifierRect = "forWhichPlayer, whichState, where, useSharedVision, afterUnits,",
CreateFogModifierRadius = "forWhichPlayer, whichState, centerx, centerY, radius, useSharedVision, afterUnits,",
CreateFogModifierRadiusLoc = "forWhichPlayer, whichState, center, radius, useSharedVision, afterUnits,",
DestroyFogModifier = "whichFogModifier,",
FogModifierStart = "whichFogModifier,",
FogModifierStop = "whichFogModifier,",
VersionGet = ",",
VersionCompatible = "whichVersion,",
VersionSupported = "whichVersion,",
EndGame = "doScoreScreen,",
ChangeLevel = "newLevel, doScoreScreen,",
RestartGame = "doScoreScreen,",
ReloadGame = ",",
SetCampaignMenuRace = "r,",
SetCampaignMenuRaceEx = "campaignIndex,",
ForceCampaignSelectScreen = ",",
LoadGame = "saveFileName, doScoreScreen,",
SaveGame = "saveFileName,",
RenameSaveDirectory = "sourceDirName, destDirName,",
RemoveSaveDirectory = "sourceDirName,",
CopySaveGame = "sourceSaveName, destSaveName,",
SaveGameExists = "saveName,",
SetMaxCheckpointSaves = "maxCheckpointSaves,",
SaveGameCheckpoint = "saveFileName, showWindow,",
SyncSelections = ",",
SetFloatGameState = "whichFloatGameState, value,",
GetFloatGameState = "whichFloatGameState,",
SetIntegerGameState = "whichIntegerGameState, value,",
GetIntegerGameState = "whichIntegerGameState,",
SetTutorialCleared = "cleared,",
SetMissionAvailable = "campaignNumber, missionNumber, available,",
SetCampaignAvailable = "campaignNumber, available,",
SetOpCinematicAvailable = "campaignNumber, available,",
SetEdCinematicAvailable = "campaignNumber, available,",
GetDefaultDifficulty = ",",
SetDefaultDifficulty = "g,",
SetCustomCampaignButtonVisible = "whichButton, visible,",
GetCustomCampaignButtonVisible = "whichButton,",
DoNotSaveReplay = ",",
DialogCreate = ",",
DialogDestroy = "whichDialog,",
DialogClear = "whichDialog,",
DialogSetMessage = "whichDialog, messageText,",
DialogAddButton = "whichDialog, buttonText, hotkey,",
DialogAddQuitButton = "whichDialog, doScoreScreen, buttonText, hotkey,",
DialogDisplay = "whichPlayer, whichDialog, flag,",
ReloadGameCachesFromDisk = ",",
InitGameCache = "campaignFile,",
SaveGameCache = "whichCache,",
StoreInteger = "cache, missionKey, key, value,",
StoreReal = "cache, missionKey, key, value,",
StoreBoolean = "cache, missionKey, key, value,",
StoreUnit = "cache, missionKey, key, whichUnit,",
StoreString = "cache, missionKey, key, value,",
SyncStoredInteger = "cache, missionKey, key,",
SyncStoredReal = "cache, missionKey, key,",
SyncStoredBoolean = "cache, missionKey, key,",
SyncStoredUnit = "cache, missionKey, key,",
SyncStoredString = "cache, missionKey, key,",
HaveStoredInteger = "cache, missionKey, key,",
HaveStoredReal = "cache, missionKey, key,",
HaveStoredBoolean = "cache, missionKey, key,",
HaveStoredUnit = "cache, missionKey, key,",
HaveStoredString = "cache, missionKey, key,",
FlushGameCache = "cache,",
FlushStoredMission = "cache, missionKey,",
FlushStoredInteger = "cache, missionKey, key,",
FlushStoredReal = "cache, missionKey, key,",
FlushStoredBoolean = "cache, missionKey, key,",
FlushStoredUnit = "cache, missionKey, key,",
FlushStoredString = "cache, missionKey, key,",
GetStoredInteger = "cache, missionKey, key,",
GetStoredReal = "cache, missionKey, key,",
GetStoredBoolean = "cache, missionKey, key,",
GetStoredString = "cache, missionKey, key,",
RestoreUnit = "cache, missionKey, key, forWhichPlayer, x, y, facing,",
InitHashtable = ",",
SaveInteger = "table, parentKey, childKey, value,",
SaveReal = "table, parentKey, childKey, value,",
SaveBoolean = "table, parentKey, childKey, value,",
SaveStr = "table, parentKey, childKey, value,",
SavePlayerHandle = "table, parentKey, childKey, whichPlayer,",
SaveWidgetHandle = "table, parentKey, childKey, whichWidget,",
SaveDestructableHandle = "table, parentKey, childKey, whichDestructable,",
SaveItemHandle = "table, parentKey, childKey, whichItem,",
SaveUnitHandle = "table, parentKey, childKey, whichUnit,",
SaveAbilityHandle = "table, parentKey, childKey, whichAbility,",
SaveTimerHandle = "table, parentKey, childKey, whichTimer,",
SaveTriggerHandle = "table, parentKey, childKey, whichTrigger,",
SaveTriggerConditionHandle = "table, parentKey, childKey, whichTriggercondition,",
SaveTriggerActionHandle = "table, parentKey, childKey, whichTriggeraction,",
SaveTriggerEventHandle = "table, parentKey, childKey, whichEvent,",
SaveForceHandle = "table, parentKey, childKey, whichForce,",
SaveGroupHandle = "table, parentKey, childKey, whichGroup,",
SaveLocationHandle = "table, parentKey, childKey, whichLocation,",
SaveRectHandle = "table, parentKey, childKey, whichRect,",
SaveBooleanExprHandle = "table, parentKey, childKey, whichBoolexpr,",
SaveSoundHandle = "table, parentKey, childKey, whichSound,",
SaveEffectHandle = "table, parentKey, childKey, whichEffect,",
SaveUnitPoolHandle = "table, parentKey, childKey, whichUnitpool,",
SaveItemPoolHandle = "table, parentKey, childKey, whichItempool,",
SaveQuestHandle = "table, parentKey, childKey, whichQuest,",
SaveQuestItemHandle = "table, parentKey, childKey, whichQuestitem,",
SaveDefeatConditionHandle = "table, parentKey, childKey, whichDefeatcondition,",
SaveTimerDialogHandle = "table, parentKey, childKey, whichTimerdialog,",
SaveLeaderboardHandle = "table, parentKey, childKey, whichLeaderboard,",
SaveMultiboardHandle = "table, parentKey, childKey, whichMultiboard,",
SaveMultiboardItemHandle = "table, parentKey, childKey, whichMultiboarditem,",
SaveTrackableHandle = "table, parentKey, childKey, whichTrackable,",
SaveDialogHandle = "table, parentKey, childKey, whichDialog,",
SaveButtonHandle = "table, parentKey, childKey, whichButton,",
SaveTextTagHandle = "table, parentKey, childKey, whichTexttag,",
SaveLightningHandle = "table, parentKey, childKey, whichLightning,",
SaveImageHandle = "table, parentKey, childKey, whichImage,",
SaveUbersplatHandle = "table, parentKey, childKey, whichUbersplat,",
SaveRegionHandle = "table, parentKey, childKey, whichRegion,",
SaveFogStateHandle = "table, parentKey, childKey, whichFogState,",
SaveFogModifierHandle = "table, parentKey, childKey, whichFogModifier,",
SaveAgentHandle = "table, parentKey, childKey, whichAgent,",
SaveHashtableHandle = "table, parentKey, childKey, whichHashtable,",
SaveFrameHandle = "table, parentKey, childKey, whichFrameHandle,",
LoadInteger = "table, parentKey, childKey,",
LoadReal = "table, parentKey, childKey,",
LoadBoolean = "table, parentKey, childKey,",
LoadStr = "table, parentKey, childKey,",
LoadPlayerHandle = "table, parentKey, childKey,",
LoadWidgetHandle = "table, parentKey, childKey,",
LoadDestructableHandle = "table, parentKey, childKey,",
LoadItemHandle = "table, parentKey, childKey,",
LoadUnitHandle = "table, parentKey, childKey,",
LoadAbilityHandle = "table, parentKey, childKey,",
LoadTimerHandle = "table, parentKey, childKey,",
LoadTriggerHandle = "table, parentKey, childKey,",
LoadTriggerConditionHandle = "table, parentKey, childKey,",
LoadTriggerActionHandle = "table, parentKey, childKey,",
LoadTriggerEventHandle = "table, parentKey, childKey,",
LoadForceHandle = "table, parentKey, childKey,",
LoadGroupHandle = "table, parentKey, childKey,",
LoadLocationHandle = "table, parentKey, childKey,",
LoadRectHandle = "table, parentKey, childKey,",
LoadBooleanExprHandle = "table, parentKey, childKey,",
LoadSoundHandle = "table, parentKey, childKey,",
LoadEffectHandle = "table, parentKey, childKey,",
LoadUnitPoolHandle = "table, parentKey, childKey,",
LoadItemPoolHandle = "table, parentKey, childKey,",
LoadQuestHandle = "table, parentKey, childKey,",
LoadQuestItemHandle = "table, parentKey, childKey,",
LoadDefeatConditionHandle = "table, parentKey, childKey,",
LoadTimerDialogHandle = "table, parentKey, childKey,",
LoadLeaderboardHandle = "table, parentKey, childKey,",
LoadMultiboardHandle = "table, parentKey, childKey,",
LoadMultiboardItemHandle = "table, parentKey, childKey,",
LoadTrackableHandle = "table, parentKey, childKey,",
LoadDialogHandle = "table, parentKey, childKey,",
LoadButtonHandle = "table, parentKey, childKey,",
LoadTextTagHandle = "table, parentKey, childKey,",
LoadLightningHandle = "table, parentKey, childKey,",
LoadImageHandle = "table, parentKey, childKey,",
LoadUbersplatHandle = "table, parentKey, childKey,",
LoadRegionHandle = "table, parentKey, childKey,",
LoadFogStateHandle = "table, parentKey, childKey,",
LoadFogModifierHandle = "table, parentKey, childKey,",
LoadHashtableHandle = "table, parentKey, childKey,",
LoadFrameHandle = "table, parentKey, childKey,",
HaveSavedInteger = "table, parentKey, childKey,",
HaveSavedReal = "table, parentKey, childKey,",
HaveSavedBoolean = "table, parentKey, childKey,",
HaveSavedString = "table, parentKey, childKey,",
HaveSavedHandle = "table, parentKey, childKey,",
RemoveSavedInteger = "table, parentKey, childKey,",
RemoveSavedReal = "table, parentKey, childKey,",
RemoveSavedBoolean = "table, parentKey, childKey,",
RemoveSavedString = "table, parentKey, childKey,",
RemoveSavedHandle = "table, parentKey, childKey,",
FlushParentHashtable = "table,",
FlushChildHashtable = "table, parentKey,",
GetRandomInt = "lowBound, highBound,",
GetRandomReal = "lowBound, highBound,",
CreateUnitPool = ",",
DestroyUnitPool = "whichPool,",
UnitPoolAddUnitType = "whichPool, unitId, weight,",
UnitPoolRemoveUnitType = "whichPool, unitId,",
PlaceRandomUnit = "whichPool, forWhichPlayer, x, y, facing,",
CreateItemPool = ",",
DestroyItemPool = "whichItemPool,",
ItemPoolAddItemType = "whichItemPool, itemId, weight,",
ItemPoolRemoveItemType = "whichItemPool, itemId,",
PlaceRandomItem = "whichItemPool, x, y,",
ChooseRandomCreep = "level,",
ChooseRandomNPBuilding = ",",
ChooseRandomItem = "level,",
ChooseRandomItemEx = "whichType, level,",
SetRandomSeed = "seed,",
SetTerrainFog = "a, b, c, d, e,",
ResetTerrainFog = ",",
SetUnitFog = "a, b, c, d, e,",
SetTerrainFogEx = "style, zstart, zend, density, red, green, blue,",
DisplayTextToPlayer = "toPlayer, x, y, message,",
DisplayTimedTextToPlayer = "toPlayer, x, y, duration, message,",
DisplayTimedTextFromPlayer = "toPlayer, x, y, duration, message,",
ClearTextMessages = ",",
SetDayNightModels = "terrainDNCFile, unitDNCFile,",
SetPortraitLight = "portraitDNCFile,",
SetSkyModel = "skyModelFile,",
EnableUserControl = "b,",
EnableUserUI = "b,",
SuspendTimeOfDay = "b,",
SetTimeOfDayScale = "r,",
GetTimeOfDayScale = ",",
ShowInterface = "flag, fadeDuration,",
PauseGame = "flag,",
UnitAddIndicator = "whichUnit, red, green, blue, alpha,",
AddIndicator = "whichWidget, red, green, blue, alpha,",
PingMinimap = "x, y, duration,",
PingMinimapEx = "x, y, duration, red, green, blue, extraEffects,",
CreateMinimapIconOnUnit = "whichUnit, red, green, blue, pingPath, fogVisibility,",
CreateMinimapIconAtLoc = "where, red, green, blue, pingPath, fogVisibility,",
CreateMinimapIcon = "x, y, red, green, blue, pingPath, fogVisibility,",
SkinManagerGetLocalPath = "key,",
DestroyMinimapIcon = "pingId,",
SetMinimapIconVisible = "whichMinimapIcon, visible,",
SetMinimapIconOrphanDestroy = "whichMinimapIcon, doDestroy,",
EnableOcclusion = "flag,",
SetIntroShotText = "introText,",
SetIntroShotModel = "introModelPath,",
EnableWorldFogBoundary = "b,",
PlayModelCinematic = "modelName,",
PlayCinematic = "movieName,",
ForceUIKey = "key,",
ForceUICancel = ",",
DisplayLoadDialog = ",",
SetAltMinimapIcon = "iconPath,",
DisableRestartMission = "flag,",
CreateTextTag = ",",
DestroyTextTag = "t,",
SetTextTagText = "t, s, height,",
SetTextTagPos = "t, x, y, heightOffset,",
SetTextTagPosUnit = "t, whichUnit, heightOffset,",
SetTextTagColor = "t, red, green, blue, alpha,",
SetTextTagVelocity = "t, xvel, yvel,",
SetTextTagVisibility = "t, flag,",
SetTextTagSuspended = "t, flag,",
SetTextTagPermanent = "t, flag,",
SetTextTagAge = "t, age,",
SetTextTagLifespan = "t, lifespan,",
SetTextTagFadepoint = "t, fadepoint,",
SetReservedLocalHeroButtons = "reserved,",
GetAllyColorFilterState = ",",
SetAllyColorFilterState = "state,",
GetCreepCampFilterState = ",",
SetCreepCampFilterState = "state,",
EnableMinimapFilterButtons = "enableAlly, enableCreep,",
EnableDragSelect = "state, ui,",
EnablePreSelect = "state, ui,",
EnableSelect = "state, ui,",
CreateTrackable = "trackableModelPath, x, y, facing,",
CreateQuest = ",",
DestroyQuest = "whichQuest,",
QuestSetTitle = "whichQuest, title,",
QuestSetDescription = "whichQuest, description,",
QuestSetIconPath = "whichQuest, iconPath,",
QuestSetRequired = "whichQuest, required,",
QuestSetCompleted = "whichQuest, completed,",
QuestSetDiscovered = "whichQuest, discovered,",
QuestSetFailed = "whichQuest, failed,",
QuestSetEnabled = "whichQuest, enabled,",
IsQuestRequired = "whichQuest,",
IsQuestCompleted = "whichQuest,",
IsQuestDiscovered = "whichQuest,",
IsQuestFailed = "whichQuest,",
IsQuestEnabled = "whichQuest,",
QuestCreateItem = "whichQuest,",
QuestItemSetDescription = "whichQuestItem, description,",
QuestItemSetCompleted = "whichQuestItem, completed,",
IsQuestItemCompleted = "whichQuestItem,",
CreateDefeatCondition = ",",
DestroyDefeatCondition = "whichCondition,",
DefeatConditionSetDescription = "whichCondition, description,",
FlashQuestDialogButton = ",",
ForceQuestDialogUpdate = ",",
CreateTimerDialog = "t,",
DestroyTimerDialog = "whichDialog,",
TimerDialogSetTitle = "whichDialog, title,",
TimerDialogSetTitleColor = "whichDialog, red, green, blue, alpha,",
TimerDialogSetTimeColor = "whichDialog, red, green, blue, alpha,",
TimerDialogSetSpeed = "whichDialog, speedMultFactor,",
TimerDialogDisplay = "whichDialog, display,",
IsTimerDialogDisplayed = "whichDialog,",
TimerDialogSetRealTimeRemaining = "whichDialog, timeRemaining,",
CreateLeaderboard = ",",
DestroyLeaderboard = "lb,",
LeaderboardDisplay = "lb, show,",
IsLeaderboardDisplayed = "lb,",
LeaderboardGetItemCount = "lb,",
LeaderboardSetSizeByItemCount = "lb, count,",
LeaderboardAddItem = "lb, label, value, p,",
LeaderboardRemoveItem = "lb, index,",
LeaderboardRemovePlayerItem = "lb, p,",
LeaderboardClear = "lb,",
LeaderboardSortItemsByValue = "lb, ascending,",
LeaderboardSortItemsByPlayer = "lb, ascending,",
LeaderboardSortItemsByLabel = "lb, ascending,",
LeaderboardHasPlayerItem = "lb, p,",
LeaderboardGetPlayerIndex = "lb, p,",
LeaderboardSetLabel = "lb, label,",
LeaderboardGetLabelText = "lb,",
PlayerSetLeaderboard = "toPlayer, lb,",
PlayerGetLeaderboard = "toPlayer,",
LeaderboardSetLabelColor = "lb, red, green, blue, alpha,",
LeaderboardSetValueColor = "lb, red, green, blue, alpha,",
LeaderboardSetStyle = "lb, showLabel, showNames, showValues, showIcon,",
LeaderboardSetItemValue = "lb, whichItem, val,",
LeaderboardSetItemLabel = "lb, whichItem, val,",
LeaderboardSetItemStyle = "lb, whichItem, showLabel, showValue, showIcon,",
LeaderboardSetItemLabelColor = "lb, whichItem, red, green, blue, alpha,",
LeaderboardSetItemValueColor = "lb, whichItem, red, green, blue, alpha,",
CreateMultiboard = ",",
DestroyMultiboard = "lb,",
MultiboardDisplay = "lb, show,",
IsMultiboardDisplayed = "lb,",
MultiboardMinimize = "lb, minimize,",
IsMultiboardMinimized = "lb,",
MultiboardClear = "lb,",
MultiboardSetTitleText = "lb, label,",
MultiboardGetTitleText = "lb,",
MultiboardSetTitleTextColor = "lb, red, green, blue, alpha,",
MultiboardGetRowCount = "lb,",
MultiboardGetColumnCount = "lb,",
MultiboardSetColumnCount = "lb, count,",
MultiboardSetRowCount = "lb, count,",
MultiboardSetItemsStyle = "lb, showValues, showIcon,",
MultiboardSetItemsValue = "lb, value,",
MultiboardSetItemsValueColor = "lb, red, green, blue, alpha,",
MultiboardSetItemsWidth = "lb, width,",
MultiboardSetItemsIcon = "lb, iconPath,",
MultiboardGetItem = "lb, row, column,",
MultiboardReleaseItem = "mbi,",
MultiboardSetItemStyle = "mbi, showValue, showIcon,",
MultiboardSetItemValue = "mbi, val,",
MultiboardSetItemValueColor = "mbi, red, green, blue, alpha,",
MultiboardSetItemWidth = "mbi, width,",
MultiboardSetItemIcon = "mbi, iconFileName,",
MultiboardSuppressDisplay = "flag,",
SetCameraPosition = "x, y,",
SetCameraQuickPosition = "x, y,",
SetCameraBounds = "x1, y1, x2, y2, x3, y3, x4, y4,",
StopCamera = ",",
ResetToGameCamera = "duration,",
PanCameraTo = "x, y,",
PanCameraToTimed = "x, y, duration,",
PanCameraToWithZ = "x, y, zOffsetDest,",
PanCameraToTimedWithZ = "x, y, zOffsetDest, duration,",
SetCinematicCamera = "cameraModelFile,",
SetCameraRotateMode = "x, y, radiansToSweep, duration,",
SetCameraField = "whichField, value, duration,",
AdjustCameraField = "whichField, offset, duration,",
SetCameraTargetController = "whichUnit, xoffset, yoffset, inheritOrientation,",
SetCameraOrientController = "whichUnit, xoffset, yoffset,",
CreateCameraSetup = ",",
CameraSetupSetField = "whichSetup, whichField, value, duration,",
CameraSetupGetField = "whichSetup, whichField,",
CameraSetupSetDestPosition = "whichSetup, x, y, duration,",
CameraSetupGetDestPositionLoc = "whichSetup,",
CameraSetupGetDestPositionX = "whichSetup,",
CameraSetupGetDestPositionY = "whichSetup,",
CameraSetupApply = "whichSetup, doPan, panTimed,",
CameraSetupApplyWithZ = "whichSetup, zDestOffset,",
CameraSetupApplyForceDuration = "whichSetup, doPan, forceDuration,",
CameraSetupApplyForceDurationWithZ = "whichSetup, zDestOffset, forceDuration,",
BlzCameraSetupSetLabel = "whichSetup, label,",
BlzCameraSetupGetLabel = "whichSetup,",
CameraSetTargetNoise = "mag, velocity,",
CameraSetSourceNoise = "mag, velocity,",
CameraSetTargetNoiseEx = "mag, velocity, vertOnly,",
CameraSetSourceNoiseEx = "mag, velocity, vertOnly,",
CameraSetSmoothingFactor = "factor,",
CameraSetFocalDistance = "distance,",
CameraSetDepthOfFieldScale = "scale,",
SetCineFilterTexture = "filename,",
SetCineFilterBlendMode = "whichMode,",
SetCineFilterTexMapFlags = "whichFlags,",
SetCineFilterStartUV = "minu, minv, maxu, maxv,",
SetCineFilterEndUV = "minu, minv, maxu, maxv,",
SetCineFilterStartColor = "red, green, blue, alpha,",
SetCineFilterEndColor = "red, green, blue, alpha,",
SetCineFilterDuration = "duration,",
DisplayCineFilter = "flag,",
IsCineFilterDisplayed = ",",
SetCinematicScene = "portraitUnitId, color, speakerTitle, text, sceneDuration, voiceoverDuration,",
EndCinematicScene = ",",
ForceCinematicSubtitles = "flag,",
SetCinematicAudio = "cinematicAudio,",
GetCameraMargin = "whichMargin,",
GetCameraBoundMinX = ",",
GetCameraBoundMinY = ",",
GetCameraBoundMaxX = ",",
GetCameraBoundMaxY = ",",
GetCameraField = "whichField,",
GetCameraTargetPositionX = ",",
GetCameraTargetPositionY = ",",
GetCameraTargetPositionZ = ",",
GetCameraTargetPositionLoc = ",",
GetCameraEyePositionX = ",",
GetCameraEyePositionY = ",",
GetCameraEyePositionZ = ",",
GetCameraEyePositionLoc = ",",
NewSoundEnvironment = "environmentName,",
CreateSound = "fileName, looping, is3D, stopwhenoutofrange, fadeInRate, fadeOutRate, eaxSetting,",
CreateSoundFilenameWithLabel = "fileName, looping, is3D, stopwhenoutofrange, fadeInRate, fadeOutRate, SLKEntryName,",
CreateSoundFromLabel = "soundLabel, looping, is3D, stopwhenoutofrange, fadeInRate, fadeOutRate,",
CreateMIDISound = "soundLabel, fadeInRate, fadeOutRate,",
SetSoundParamsFromLabel = "soundHandle, soundLabel,",
SetSoundDistanceCutoff = "soundHandle, cutoff,",
SetSoundChannel = "soundHandle, channel,",
SetSoundVolume = "soundHandle, volume,",
SetSoundPitch = "soundHandle, pitch,",
SetSoundPlayPosition = "soundHandle, millisecs,",
SetSoundDistances = "soundHandle, minDist, maxDist,",
SetSoundConeAngles = "soundHandle, inside, outside, outsideVolume,",
SetSoundConeOrientation = "soundHandle, x, y, z,",
SetSoundPosition = "soundHandle, x, y, z,",
SetSoundVelocity = "soundHandle, x, y, z,",
AttachSoundToUnit = "soundHandle, whichUnit,",
StartSound = "soundHandle,",
StartSoundEx = "soundHandle, fadeIn,",
StopSound = "soundHandle, killWhenDone, fadeOut,",
KillSoundWhenDone = "soundHandle,",
SetMapMusic = "musicName, random, index,",
ClearMapMusic = ",",
PlayMusic = "musicName,",
PlayMusicEx = "musicName, frommsecs, fadeinmsecs,",
StopMusic = "fadeOut,",
ResumeMusic = ",",
PlayThematicMusic = "musicFileName,",
PlayThematicMusicEx = "musicFileName, frommsecs,",
EndThematicMusic = ",",
SetMusicVolume = "volume,",
SetMusicPlayPosition = "millisecs,",
SetThematicMusicVolume = "volume,",
SetThematicMusicPlayPosition = "millisecs,",
SetSoundDuration = "soundHandle, duration,",
GetSoundDuration = "soundHandle,",
GetSoundFileDuration = "musicFileName,",
VolumeGroupSetVolume = "vgroup, scale,",
VolumeGroupReset = ",",
GetSoundIsPlaying = "soundHandle,",
GetSoundIsLoading = "soundHandle,",
RegisterStackedSound = "soundHandle, byPosition, rectwidth, rectheight,",
UnregisterStackedSound = "soundHandle, byPosition, rectwidth, rectheight,",
SetSoundFacialAnimationLabel = "soundHandle, animationLabel,",
SetSoundFacialAnimationGroupLabel = "soundHandle, groupLabel,",
SetSoundFacialAnimationSetFilepath = "soundHandle, animationSetFilepath,",
SetDialogueSpeakerNameKey = "soundHandle, speakerName,",
GetDialogueSpeakerNameKey = "soundHandle,",
SetDialogueTextKey = "soundHandle, dialogueText,",
GetDialogueTextKey = "soundHandle,",
AddWeatherEffect = "where, effectID,",
RemoveWeatherEffect = "whichEffect,",
EnableWeatherEffect = "whichEffect, enable,",
TerrainDeformCrater = "x, y, radius, depth, duration, permanent,",
TerrainDeformRipple = "x, y, radius, depth, duration, count, spaceWaves, timeWaves, radiusStartPct, limitNeg,",
TerrainDeformWave = "x, y, dirX, dirY, distance, speed, radius, depth, trailTime, count,",
TerrainDeformRandom = "x, y, radius, minDelta, maxDelta, duration, updateInterval,",
TerrainDeformStop = "deformation, duration,",
TerrainDeformStopAll = ",",
AddSpecialEffect = "modelName, x, y,",
AddSpecialEffectLoc = "modelName, where,",
AddSpecialEffectTarget = "modelName, targetWidget, attachPointName,",
DestroyEffect = "whichEffect,",
AddSpellEffect = "abilityString, t, x, y,",
AddSpellEffectLoc = "abilityString, t, where,",
AddSpellEffectById = "abilityId, t, x, y,",
AddSpellEffectByIdLoc = "abilityId, t, where,",
AddSpellEffectTarget = "modelName, t, targetWidget, attachPoint,",
AddSpellEffectTargetById = "abilityId, t, targetWidget, attachPoint,",
AddLightning = "codeName, checkVisibility, x1, y1, x2, y2,",
AddLightningEx = "codeName, checkVisibility, x1, y1, z1, x2, y2, z2,",
DestroyLightning = "whichBolt,",
MoveLightning = "whichBolt, checkVisibility, x1, y1, x2, y2,",
MoveLightningEx = "whichBolt, checkVisibility, x1, y1, z1, x2, y2, z2,",
BlzGetTriggerFrameText = ",",
GetLightningColorA = "whichBolt,",
GetLightningColorR = "whichBolt,",
GetLightningColorG = "whichBolt,",
GetLightningColorB = "whichBolt,",
SetLightningColor = "whichBolt, r, g, b, a,",
GetAbilityEffect = "abilityString, t, index,",
GetAbilityEffectById = "abilityId, t, index,",
GetAbilitySound = "abilityString, t,",
GetAbilitySoundById = "abilityId, t,",
GetTerrainCliffLevel = "x, y,",
SetWaterBaseColor = "red, green, blue, alpha,",
SetWaterDeforms = "val,",
GetTerrainType = "x, y,",
GetTerrainVariance = "x, y,",
SetTerrainType = "x, y, terrainType, variation, area, shape,",
IsTerrainPathable = "x, y, t,",
SetTerrainPathable = "x, y, t, flag,",
CreateImage = "file, sizeX, sizeY, sizeZ, posX, posY, posZ, originX, originY, originZ, imageType,",
DestroyImage = "whichImage,",
ShowImage = "whichImage, flag,",
SetImageConstantHeight = "whichImage, flag, height,",
SetImagePosition = "whichImage, x, y, z,",
SetImageColor = "whichImage, red, green, blue, alpha,",
SetImageRender = "whichImage, flag,",
SetImageRenderAlways = "whichImage, flag,",
SetImageAboveWater = "whichImage, flag, useWaterAlpha,",
SetImageType = "whichImage, imageType,",
CreateUbersplat = "x, y, name, red, green, blue, alpha, forcePaused, noBirthTime,",
DestroyUbersplat = "whichSplat,",
ResetUbersplat = "whichSplat,",
FinishUbersplat = "whichSplat,",
ShowUbersplat = "whichSplat, flag,",
SetUbersplatRender = "whichSplat, flag,",
SetUbersplatRenderAlways = "whichSplat, flag,",
SetBlight = "whichPlayer, x, y, radius, addBlight,",
SetBlightRect = "whichPlayer, r, addBlight,",
SetBlightPoint = "whichPlayer, x, y, addBlight,",
SetBlightLoc = "whichPlayer, whichLocation, radius, addBlight,",
CreateBlightedGoldmine = "id, x, y, face,",
IsPointBlighted = "x, y,",
SetDoodadAnimation = "x, y, radius, doodadID, nearestOnly, animName, animRandom,",
SetDoodadAnimationRect = "r, doodadID, animName, animRandom,",
StartMeleeAI = "num, script,",
StartCampaignAI = "num, script,",
CommandAI = "num, command, data,",
PauseCompAI = "p, pause,",
GetAIDifficulty = "num,",
RemoveGuardPosition = "hUnit,",
RecycleGuardPosition = "hUnit,",
RemoveAllGuardPositions = "num,",
Cheat = "cheatStr,",
IsNoVictoryCheat = ",",
IsNoDefeatCheat = ",",
Preload = "filename,",
PreloadEnd = "timeout,",
PreloadStart = ",",
PreloadRefresh = ",",
PreloadEndEx = ",",
PreloadGenClear = ",",
PreloadGenStart = ",",
PreloadGenEnd = "filename,",
Preloader = "filename,",
BlzHideCinematicPanels = "enable,",
AutomationSetTestType = "testType,",
AutomationTestStart = "testName,",
AutomationTestEnd = ",",
AutomationTestingFinished = ",",
BlzGetTriggerPlayerMouseX = ",",
BlzGetTriggerPlayerMouseY = ",",
BlzGetTriggerPlayerMousePosition = ",",
BlzGetTriggerPlayerMouseButton = ",",
BlzSetAbilityTooltip = "abilCode, tooltip, level,",
BlzSetAbilityActivatedTooltip = "abilCode, tooltip, level,",
BlzSetAbilityExtendedTooltip = "abilCode, extendedTooltip, level,",
BlzSetAbilityActivatedExtendedTooltip = "abilCode, extendedTooltip, level,",
BlzSetAbilityResearchTooltip = "abilCode, researchTooltip, level,",
BlzSetAbilityResearchExtendedTooltip = "abilCode, researchExtendedTooltip, level,",
BlzGetAbilityTooltip = "abilCode, level,",
BlzGetAbilityActivatedTooltip = "abilCode, level,",
BlzGetAbilityExtendedTooltip = "abilCode, level,",
BlzGetAbilityActivatedExtendedTooltip = "abilCode, level,",
BlzGetAbilityResearchTooltip = "abilCode, level,",
BlzGetAbilityResearchExtendedTooltip = "abilCode, level,",
BlzSetAbilityIcon = "abilCode, iconPath,",
BlzGetAbilityIcon = "abilCode,",
BlzSetAbilityActivatedIcon = "abilCode, iconPath,",
BlzGetAbilityActivatedIcon = "abilCode,",
BlzGetAbilityPosX = "abilCode,",
BlzGetAbilityPosY = "abilCode,",
BlzSetAbilityPosX = "abilCode, x,",
BlzSetAbilityPosY = "abilCode, y,",
BlzGetAbilityActivatedPosX = "abilCode,",
BlzGetAbilityActivatedPosY = "abilCode,",
BlzSetAbilityActivatedPosX = "abilCode, x,",
BlzSetAbilityActivatedPosY = "abilCode, y,",
BlzGetUnitMaxHP = "whichUnit,",
BlzSetUnitMaxHP = "whichUnit, hp,",
BlzGetUnitMaxMana = "whichUnit,",
BlzSetUnitMaxMana = "whichUnit, mana,",
BlzSetItemName = "whichItem, name,",
BlzSetItemDescription = "whichItem, description,",
BlzGetItemDescription = "whichItem,",
BlzSetItemTooltip = "whichItem, tooltip,",
BlzGetItemTooltip = "whichItem,",
BlzSetItemExtendedTooltip = "whichItem, extendedTooltip,",
BlzGetItemExtendedTooltip = "whichItem,",
BlzSetItemIconPath = "whichItem, iconPath,",
BlzGetItemIconPath = "whichItem,",
BlzSetUnitName = "whichUnit, name,",
BlzSetHeroProperName = "whichUnit, heroProperName,",
BlzGetUnitBaseDamage = "whichUnit, weaponIndex,",
BlzSetUnitBaseDamage = "whichUnit, baseDamage, weaponIndex,",
BlzGetUnitDiceNumber = "whichUnit, weaponIndex,",
BlzSetUnitDiceNumber = "whichUnit, diceNumber, weaponIndex,",
BlzGetUnitDiceSides = "whichUnit, weaponIndex,",
BlzSetUnitDiceSides = "whichUnit, diceSides, weaponIndex,",
BlzGetUnitAttackCooldown = "whichUnit, weaponIndex,",
BlzSetUnitAttackCooldown = "whichUnit, cooldown, weaponIndex,",
BlzSetSpecialEffectColorByPlayer = "whichEffect, whichPlayer,",
BlzSetSpecialEffectColor = "whichEffect, r, g, b,",
BlzSetSpecialEffectAlpha = "whichEffect, alpha,",
BlzSetSpecialEffectScale = "whichEffect, scale,",
BlzSetSpecialEffectPosition = "whichEffect, x, y, z,",
BlzSetSpecialEffectHeight = "whichEffect, height,",
BlzSetSpecialEffectTimeScale = "whichEffect, timeScale,",
BlzSetSpecialEffectTime = "whichEffect, time,",
BlzSetSpecialEffectOrientation = "whichEffect, yaw, pitch, roll,",
BlzSetSpecialEffectYaw = "whichEffect, yaw,",
BlzSetSpecialEffectPitch = "whichEffect, pitch,",
BlzSetSpecialEffectRoll = "whichEffect, roll,",
BlzSetSpecialEffectX = "whichEffect, x,",
BlzSetSpecialEffectY = "whichEffect, y,",
BlzSetSpecialEffectZ = "whichEffect, z,",
BlzSetSpecialEffectPositionLoc = "whichEffect, loc,",
BlzGetLocalSpecialEffectX = "whichEffect,",
BlzGetLocalSpecialEffectY = "whichEffect,",
BlzGetLocalSpecialEffectZ = "whichEffect,",
BlzSpecialEffectClearSubAnimations = "whichEffect,",
BlzSpecialEffectRemoveSubAnimation = "whichEffect, whichSubAnim,",
BlzSpecialEffectAddSubAnimation = "whichEffect, whichSubAnim,",
BlzPlaySpecialEffect = "whichEffect, whichAnim,",
BlzPlaySpecialEffectWithTimeScale = "whichEffect, whichAnim, timeScale,",
BlzGetAnimName = "whichAnim,",
BlzGetUnitArmor = "whichUnit,",
BlzSetUnitArmor = "whichUnit, armorAmount,",
BlzUnitHideAbility = "whichUnit, abilId, flag,",
BlzUnitDisableAbility = "whichUnit, abilId, flag, hideUI,",
BlzUnitCancelTimedLife = "whichUnit,",
BlzIsUnitSelectable = "whichUnit,",
BlzIsUnitInvulnerable = "whichUnit,",
BlzUnitInterruptAttack = "whichUnit,",
BlzGetUnitCollisionSize = "whichUnit,",
BlzGetAbilityManaCost = "abilId, level,",
BlzGetAbilityCooldown = "abilId, level,",
BlzSetUnitAbilityCooldown = "whichUnit, abilId, level, cooldown,",
BlzGetUnitAbilityCooldown = "whichUnit, abilId, level,",
BlzGetUnitAbilityCooldownRemaining = "whichUnit, abilId,",
BlzEndUnitAbilityCooldown = "whichUnit, abilCode,",
BlzStartUnitAbilityCooldown = "whichUnit, abilCode, cooldown,",
BlzGetUnitAbilityManaCost = "whichUnit, abilId, level,",
BlzSetUnitAbilityManaCost = "whichUnit, abilId, level, manaCost,",
BlzGetLocalUnitZ = "whichUnit,",
BlzDecPlayerTechResearched = "whichPlayer, techid, levels,",
BlzSetEventDamage = "damage,",
BlzGetEventDamageTarget = ",",
BlzGetEventAttackType = ",",
BlzGetEventDamageType = ",",
BlzGetEventWeaponType = ",",
BlzSetEventAttackType = "attackType,",
BlzSetEventDamageType = "damageType,",
BlzSetEventWeaponType = "weaponType,",
BlzGetEventIsAttack = ",",
RequestExtraIntegerData = "dataType, whichPlayer, param1, param2, param3, param4, param5, param6,",
RequestExtraBooleanData = "dataType, whichPlayer, param1, param2, param3, param4, param5, param6,",
RequestExtraStringData = "dataType, whichPlayer, param1, param2, param3, param4, param5, param6,",
RequestExtraRealData = "dataType, whichPlayer, param1, param2, param3, param4, param5, param6,",
BlzGetUnitZ = "whichUnit,",
BlzEnableSelections = "enableSelection, enableSelectionCircle,",
BlzIsSelectionEnabled = ",",
BlzIsSelectionCircleEnabled = ",",
BlzCameraSetupApplyForceDurationSmooth = "whichSetup, doPan, forcedDuration, easeInDuration, easeOutDuration, smoothFactor,",
BlzEnableTargetIndicator = "enable,",
BlzIsTargetIndicatorEnabled = ",",
BlzShowTerrain = "show,",
BlzShowSkyBox = "show,",
BlzStartRecording = "fps,",
BlzEndRecording = ",",
BlzShowUnitTeamGlow = "whichUnit, show,",
BlzGetOriginFrame = "frameType, index,",
BlzEnableUIAutoPosition = "enable,",
BlzHideOriginFrames = "enable,",
BlzConvertColor = "a, r, g, b,",
BlzLoadTOCFile = "TOCFile,",
BlzCreateFrame = "name, owner, priority, createContext,",
BlzCreateSimpleFrame = "name, owner, createContext,",
BlzCreateFrameByType = "typeName, name, owner, inherits, createContext,",
BlzDestroyFrame = "frame,",
BlzFrameSetPoint = "frame, point, relative, relativePoint, x, y,",
BlzFrameSetAbsPoint = "frame, point, x, y,",
BlzFrameClearAllPoints = "frame,",
BlzFrameSetAllPoints = "frame, relative,",
BlzFrameSetVisible = "frame, visible,",
BlzFrameIsVisible = "frame,",
BlzGetFrameByName = "name, createContext,",
BlzFrameGetName = "frame,",
BlzFrameClick = "frame,",
BlzFrameSetText = "frame, text,",
BlzFrameGetText = "frame,",
BlzFrameAddText = "frame, text,",
BlzFrameSetTextSizeLimit = "frame, size,",
BlzFrameGetTextSizeLimit = "frame,",
BlzFrameSetTextColor = "frame, color,",
BlzFrameSetFocus = "frame, flag,",
BlzFrameSetModel = "frame, modelFile, cameraIndex,",
BlzFrameSetEnable = "frame, enabled,",
BlzFrameGetEnable = "frame,",
BlzFrameSetAlpha = "frame, alpha,",
BlzFrameGetAlpha = "frame,",
BlzFrameSetSpriteAnimate = "frame, primaryProp, flags,",
BlzFrameSetTexture = "frame, texFile, flag, blend,",
BlzFrameSetScale = "frame, scale,",
BlzFrameSetTooltip = "frame, tooltip,",
BlzFrameCageMouse = "frame, enable,",
BlzFrameSetValue = "frame, value,",
BlzFrameGetValue = "frame,",
BlzFrameSetMinMaxValue = "frame, minValue, maxValue,",
BlzFrameSetStepSize = "frame, stepSize,",
BlzFrameSetSize = "frame, width, height,",
BlzFrameSetVertexColor = "frame, color,",
BlzFrameSetLevel = "frame, level,",
BlzFrameSetParent = "frame, parent,",
BlzFrameGetParent = "frame,",
BlzFrameGetHeight = "frame,",
BlzFrameGetWidth = "frame,",
BlzFrameSetFont = "frame, fileName, height, flags,",
BlzFrameSetTextAlignment = "frame, vert, horz,",
BlzFrameGetChildrenCount = "frame,",
BlzFrameGetChild = "frame, index,",
BlzTriggerRegisterFrameEvent = "whichTrigger, frame, eventId,",
BlzGetTriggerFrame = ",",
BlzGetTriggerFrameEvent = ",",
BlzGetTriggerFrameValue = ",",
BlzGetTriggerTextFrame = ",",
BlzTriggerRegisterPlayerSyncEvent = "whichTrigger, whichPlayer, prefix, fromServer,",
BlzSendSyncData = "prefix, data,",
BlzGetTriggerSyncPrefix = ",",
BlzGetTriggerSyncData = ",",
BlzTriggerRegisterPlayerKeyEvent = "whichTrigger, whichPlayer, key, metaKey, keyDown,",
BlzGetTriggerPlayerKey = ",",
BlzGetTriggerPlayerMetaKey = ",",
BlzGetTriggerPlayerIsKeyDown = ",",
BlzEnableCursor = "enable,",
BlzSetMousePos = "x, y,",
BlzGetLocalClientWidth = ",",
BlzGetLocalClientHeight = ",",
BlzIsLocalClientActive = ",",
BlzGetMouseFocusUnit = ",",
BlzChangeMinimapTerrainTex = "texFile,",
BlzGetLocale = ",",
BlzGetSpecialEffectScale = "whichEffect,",
BlzSetSpecialEffectMatrixScale = "whichEffect, x, y, z,",
BlzResetSpecialEffectMatrix = "whichEffect,",
BlzGetUnitAbility = "whichUnit, abilId,",
BlzGetUnitAbilityByIndex = "whichUnit, index,",
BlzGetAbilityId = "whichAbility,",
BlzDisplayChatMessage = "whichPlayer, recipient, message,",
BlzPauseUnitEx = "whichUnit, flag,",
BlzSetUnitFacingEx = "whichUnit, facingAngle,",
CreateCommandButtonEffect = "abilityId, order,",
CreateUpgradeCommandButtonEffect = "whichUprgade,",
CreateLearnCommandButtonEffect = "abilityId,",DestroyCommandButtonEffect = "whichEffect,",
BlzBitOr = "x, y,",
BlzBitAnd = "x, y,",
BlzBitXor = "x, y,",
BlzGetAbilityBooleanField = "whichAbility, whichField,",
BlzGetAbilityIntegerField = "whichAbility, whichField,",
BlzGetAbilityRealField = "whichAbility, whichField,",
BlzGetAbilityStringField = "whichAbility, whichField,",
BlzGetAbilityBooleanLevelField = "whichAbility, whichField, level,",
BlzGetAbilityIntegerLevelField = "whichAbility, whichField, level,",
BlzGetAbilityRealLevelField = "whichAbility, whichField, level,",
BlzGetAbilityStringLevelField = "whichAbility, whichField, level,",
BlzGetAbilityBooleanLevelArrayField = "whichAbility, whichField, level, index,",
BlzGetAbilityIntegerLevelArrayField = "whichAbility, whichField, level, index,",
BlzGetAbilityRealLevelArrayField = "whichAbility, whichField, level, index,",
BlzGetAbilityStringLevelArrayField = "whichAbility, whichField, level, index,",
BlzSetAbilityBooleanField = "whichAbility, whichField, value,",
BlzSetAbilityIntegerField = "whichAbility, whichField, value,",
BlzSetAbilityRealField = "whichAbility, whichField, value,",
BlzSetAbilityStringField = "whichAbility, whichField, value,",
BlzSetAbilityBooleanLevelField = "whichAbility, whichField, level, value,",
BlzSetAbilityIntegerLevelField = "whichAbility, whichField, level, value,",
BlzSetAbilityRealLevelField = "whichAbility, whichField, level, value,",
BlzSetAbilityStringLevelField = "whichAbility, whichField, level, value,",
BlzSetAbilityBooleanLevelArrayField = "whichAbility, whichField, level, index, value,",
BlzSetAbilityIntegerLevelArrayField = "whichAbility, whichField, level, index, value,",
BlzSetAbilityRealLevelArrayField = "whichAbility, whichField, level, index, value,",
BlzSetAbilityStringLevelArrayField = "whichAbility, whichField, level, index, value,",
BlzAddAbilityBooleanLevelArrayField = "whichAbility, whichField, level, value,",
BlzAddAbilityIntegerLevelArrayField = "whichAbility, whichField, level, value,",
BlzAddAbilityRealLevelArrayField = "whichAbility, whichField, level, value,",
BlzAddAbilityStringLevelArrayField = "whichAbility, whichField, level, value,",
BlzRemoveAbilityBooleanLevelArrayField = "whichAbility, whichField, level, value,",
BlzRemoveAbilityIntegerLevelArrayField = "whichAbility, whichField, level, value,",
BlzRemoveAbilityRealLevelArrayField = "whichAbility, whichField, level, value,",
BlzRemoveAbilityStringLevelArrayField = "whichAbility, whichField, level, value,",
BlzGetItemAbilityByIndex = "whichItem, index,",
BlzGetItemAbility = "whichItem, abilCode,",
BlzItemAddAbility = "whichItem, abilCode,",
BlzGetItemBooleanField = "whichItem, whichField,",
BlzGetItemIntegerField = "whichItem, whichField,",
BlzGetItemRealField = "whichItem, whichField,",
BlzGetItemStringField = "whichItem, whichField,",
BlzSetItemBooleanField = "whichItem, whichField, value,",
BlzSetItemIntegerField = "whichItem, whichField, value,",
BlzSetItemRealField = "whichItem, whichField, value,",
BlzSetItemStringField = "whichItem, whichField, value,",
BlzItemRemoveAbility = "whichItem, abilCode,",
BlzGetUnitBooleanField = "whichUnit, whichField,",
BlzGetUnitIntegerField = "whichUnit, whichField,",
BlzGetUnitRealField = "whichUnit, whichField,",
BlzGetUnitStringField = "whichUnit, whichField,",
BlzSetUnitBooleanField = "whichUnit, whichField, value,",
BlzSetUnitIntegerField = "whichUnit, whichField, value,",
BlzSetUnitRealField = "whichUnit, whichField, value,",
BlzSetUnitStringField = "whichUnit, whichField, value,",
BlzGetUnitWeaponBooleanField = "whichUnit, whichField, index,",
BlzGetUnitWeaponIntegerField = "whichUnit, whichField, index,",
BlzGetUnitWeaponRealField = "whichUnit, whichField, index,",
BlzGetUnitWeaponStringField = "whichUnit, whichField, index,",
BlzSetUnitWeaponBooleanField = "whichUnit, whichField, index, value,",
BlzSetUnitWeaponIntegerField = "whichUnit, whichField, index, value,",
BlzSetUnitWeaponRealField = "whichUnit, whichField, index, value,",
BlzSetUnitWeaponStringField = "whichUnit, whichField, index, value,",
BlzGetUnitSkin = "whichUnit,",
BlzGetItemSkin = "whichItem,",
BlzSetUnitSkin = "whichUnit, skinId,",
BlzSetItemSkin = "whichItem, skinId,",
BlzCreateItemWithSkin = "itemid, x, y, skinId,",
BlzCreateUnitWithSkin = "id, unitid, x, y, face, skinId,",
BlzCreateDestructableWithSkin = "objectid, x, y, face, scale, variation, skinId,",
BlzCreateDestructableZWithSkin = "objectid, x, y, z, face, scale, variation, skinId,",
BlzCreateDeadDestructableWithSkin = "objectid, x, y, face, scale, variation, skinId,",
BlzCreateDeadDestructableZWithSkin = "objectid, x, y, z, face, scale, variation, skinId,",
BlzGetPlayerTownHallCount = "whichPlayer,",
BlzQueueImmediateOrderById = "whichUnit, order,",
BlzQueuePointOrderById = "whichUnit, order, x, y,",
BlzQueueTargetOrderById = "whichUnit, order, targetWidget,",
BlzQueueInstantPointOrderById = "whichUnit, order, x, y, instantTargetWidget,",
BlzQueueInstantTargetOrderById = "whichUnit, order, targetWidget, instantTargetWidget,",
BlzQueueBuildOrderById = "whichPeon, unitId, x, y,",
BlzQueueNeutralImmediateOrderById = "forWhichPlayer, neutralStructure, unitId,",
BlzQueueNeutralPointOrderById = "forWhichPlayer, neutralStructure, unitId, x, y,",
BlzQueueNeutralTargetOrderById = "forWhichPlayer, neutralStructure, unitId, target,",
BlzGetUnitOrderCount = "whichUnit,",
BlzUnitClearOrders = "whichUnit, onlyQueued,",
BlzUnitForceStopOrder = "whichUnit, clearQueue,",
FourCC = "whichString,",
UnitAlive = "whichUnit,",
}
WSCode.Parse([===[
if Debug then Debug.beginFile("HandleType") end
do
--[[
===============================================================================================================================================================
Handle Type
by Antares
===============================================================================================================================================================
Determine the type of a Wacraft 3 object (handle). The result is stored in a table on the first execution to increase performance.
HandleType[whichHandle] -> string Returns an empty string if variable is not a handle.
IsHandle[whichHandle] -> boolean
IsWidget[whichHandle] -> boolean
IsUnit[whichHandle] -> boolean
These can also be called as a function, which has a nil-check, but is slower than the table-lookup.
===============================================================================================================================================================
]]
local widgetTypes = {
unit = true,
destructable = true,
item = true
}
HandleType = setmetatable({}, {
__mode = "k",
__index = function(self, key)
if type(key) == "userdata" then
local str = tostring(key)
self[key] = str:sub(1, (str:find(":", nil, true) or 0) - 1)
return self[key]
else
self[key] = ""
return ""
end
end,
__call = function(self, key)
if key then
return self[key]
else
return ""
end
end
})
IsHandle = setmetatable({}, {
__mode = "k",
__index = function(self, key)
self[key] = HandleType[key] ~= ""
return self[key]
end,
__call = function(self, key)
if key then
return self[key]
else
return false
end
end
})
IsWidget = setmetatable({}, {
__mode = "k",
__index = function(self, key)
self[key] = widgetTypes[HandleType[key]] == true
return self[key]
end,
__call = function(self, key)
if key then
return self[key]
else
return false
end
end
})
IsUnit = setmetatable({}, {
__mode = "k",
__index = function(self, key)
self[key] = HandleType[key] == "unit"
return self[key]
end,
__call = function(self, key)
if key then
return self[key]
else
return false
end
end
})
end
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "FastWorld2ScreenTransform" end
do
--[[
=============================================================================================================================================================
Fast World2Screen Transform
by Antares
Transform from world to screen coordinates and back.
Requires:
TotalInitialization https://www.hiveworkshop.com/threads/.317099/
PrecomputedHeightMap (optional) https://www.hiveworkshop.com/threads/.353477/
=============================================================================================================================================================
A P I
=============================================================================================================================================================
World2Screen(x, y, z, player) Takes the world coordinates x, y, and z and returns the screen positions x and y of that point on the
specified player's screen. The third return value specifies if the point is on the screen.
Screen2World(x, y, player) Takes the screen coordinates x and y and the player for which the calculation should be performed and
returns the x, y, and z world coordinates. The fourth return value specifies whether or not the search for
the world intersection point converged. This function may fail when a ray pointing from the camera to the
world intersects at multiple points with the terrain.
=============================================================================================================================================================
C O N F I G
=============================================================================================================================================================
]]
--The interval between updates of each player's camera parameters.
local TIME_STEP = 0.02 ---@type number
--[[
=============================================================================================================================================================
E N D O F C O N F I G
=============================================================================================================================================================
]]
local cos = math.cos
local sin = math.sin
local sqrt = math.sqrt
local preCalc = false ---@type boolean
local eyeX = 0 ---@type number
local eyeY = 0 ---@type number
local eyeZ = 0 ---@type number
local cosRot = 0 ---@type number
local sinRot = 0 ---@type number
local cosAttack = 0 ---@type number
local sinAttack = 0 ---@type number
local angleOfAttack = 0 ---@type number
local rotation = 0 ---@type number
local cosAttackCosRot = 0 ---@type number
local cosAttackSinRot = 0 ---@type number
local sinAttackCosRot = 0 ---@type number
local sinAttackSinRot = 0 ---@type number
local yCenterScreenShift = 0 ---@type number
local scaleFactor = 0 ---@type number
local GetTerrainZ ---@type function
local moveableLoc ---@type location
local function CameraPrecalc(player)
eyeX = GetCameraEyePositionX()
eyeY = GetCameraEyePositionY()
eyeZ = GetCameraEyePositionZ()
angleOfAttack = GetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK)
rotation = GetCameraField(CAMERA_FIELD_ROTATION)
local fieldOfView = GetCameraField(CAMERA_FIELD_FIELD_OF_VIEW)
cosAttack = cos(angleOfAttack)
sinAttack = sin(angleOfAttack)
cosRot = cos(rotation)
sinRot = sin(rotation)
yCenterScreenShift = 0.1284*cosAttack
scaleFactor = 0.0524*fieldOfView^3 - 0.0283*fieldOfView^2 + 1.061*fieldOfView
cosAttackCosRot = cosAttack*cosRot
cosAttackSinRot = cosAttack*sinRot
sinAttackCosRot = sinAttack*cosRot
sinAttackSinRot = sinAttack*sinRot
preCalc = true
end
---Takes the world coordinates x, y, and z and returns the screen positions x and y of that point on the specified player's screen. The third return value specifies if the point is on the screen.
---@param x number
---@param y number
---@param z number
---@return number, number, boolean
function World2Screen(x, y, z)
if not preCalc then
CameraPrecalc()
end
local dx = x - eyeX
local dy = y - eyeY
local dz = z - eyeZ
local xPrime = scaleFactor*(-cosAttackCosRot*dx - cosAttackSinRot*dy - sinAttack*dz)
local xs = 0.4 + (cosRot*dy - sinRot*dx)/xPrime
local ys = 0.42625 - yCenterScreenShift + (sinAttackCosRot*dx + sinAttackSinRot*dy - cosAttack*dz)/xPrime
return xs, ys, xPrime < 0 and xs > -0.1333 and xs < 0.9333 and ys > 0 and ys < 0.6
end
---Takes the screen coordinates x and y and the player for which the calculation should be performed and returns the x, y, and z world coordinates. The fourth return value specifies whether or not the search for the world intersection point converged. This function may fail when a ray pointing from the camera to the world intersects at multiple points with the terrain.
---@param x number
---@param y number
---@return number, number, number, boolean
function Screen2World(x, y)
if not preCalc then
CameraPrecalc()
end
local a = (x - 0.4)*scaleFactor
local b = (0.42625 - yCenterScreenShift - y)*scaleFactor
--This is the unit vector pointing towards the mouse cursor in the camera's coordinate system.
local nx = 1/sqrt(1 + a*a + b*b)
local ny = sqrt(1 - (1 + b*b)*nx*nx)
local nz = sqrt(1 - nx*nx - ny*ny)
if a > 0 then
ny = -ny
end
if b < 0 then
nz = -nz
end
--Constructs the unit vector pointing from the camera eye position to the mouse cursor.
local nxPrime = cosAttackCosRot*nx - sinRot*ny + sinAttackCosRot*nz
local nyPrime = cosAttackSinRot*nx + cosRot*ny + sinAttackSinRot*nz
local nzPrime = -sinAttack*nx + cosAttack*nz
--Try to find intersection point of vector with terrain.
local zGuess = GetTerrainZ(eyeX, eyeY)
local xGuess = eyeX + nxPrime*(eyeZ - zGuess)/nzPrime
local yGuess = eyeY + nyPrime*(eyeZ - zGuess)/nzPrime
local zWorld = GetTerrainZ(xGuess, yGuess)
local deltaZ = zWorld - zGuess
zGuess = zWorld
local zWorldOld, deltaZOld
local i = 0
while (deltaZ > 1 or deltaZ < -1) and i < 50 do
zWorldOld = zWorld
deltaZOld = deltaZ
xGuess = eyeX + nxPrime*(eyeZ - zGuess)/nzPrime
yGuess = eyeY + nyPrime*(eyeZ - zGuess)/nzPrime
zWorld = GetTerrainZ(xGuess, yGuess)
deltaZ = zWorld - zGuess
zGuess = (deltaZOld*zWorld - deltaZ*zWorldOld)/(deltaZOld - deltaZ)
i = i + 1
end
return xGuess, yGuess, zWorld, i < 50
end
OnInit.final("World2Screen", function()
local precomputedHeightMap = Require.optionally "PrecomputedHeightMap"
if precomputedHeightMap then
GetTerrainZ = _G.GetTerrainZ
else
moveableLoc = Location(0, 0)
GetTerrainZ = function(x, y)
MoveLocation(moveableLoc, x, y)
return GetLocationZ(moveableLoc)
end
end
TimerStart(CreateTimer(), TIME_STEP, true, function()
preCalc = false
end)
end)
end
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "PrecomputedHeightMap" end ---@diagnostic disable: param-type-mismatch
do
--[[
===============================================================================================================================================================
Precomputed Height Map
by Antares
===============================================================================================================================================================
GetLocZ(x, y) Returns the same value as GetLocationZ(x, y).
GetTerrainZ(x, y) Returns the exact height of the terrain geometry.
GetUnitZ(whichUnit) Returns the same value as BlzGetUnitZ(whichUnit).
GetUnitCoordinates(whichUnit) Returns x, y, and z-coordinates of a unit.
===============================================================================================================================================================
Computes the terrain height of your map on map initialization for later use. The function GetLocZ replaces the traditional GetLocZ, defined as:
function GetLocZ(x, y)
MoveLocation(moveableLoc, x, y)
return GetLocationZ(moveableLoc)
end
The function provided in this library cannot cause desyncs and is approximately twice as fast. GetTerrainZ is a variation of GetLocZ that returns the exact height
of the terrain geometry (around cliffs, it has to approximate).
Note: PrecomputedHeightMap initializes OnitInit.final, because otherwise walkable doodads would not be registered.
===============================================================================================================================================================
You have the option to save the height map to a file on map initialization. You can then reimport the data into the map to load the height map from that data.
This will make the use of Z-coordinates completely safe, as all clients are guaranteed to use exactly the same data. It is recommended to do this once for the
release version of your map.
To do this, set the flag for WRITE_HEIGHT_MAP and launch your map. The terrain height map will be generated on map initialization and saved to a file in your
Warcraft III\CustomMapData\ folder. Open that file in a text editor, then remove all occurances of
call Preload( "
" )
with find and replace (including the quotation marks and tab space). Then, remove
function PreloadFiles takes nothing returns nothing
call PreloadStart()
at the beginning of the file and
call PreloadEnd( 0.0 )
endfunction
at the end of the file. Finally, remove all line breaks by removing \n and \r. The result should be something like
HeightMapCode = "|pk44mM-b+b1-dr|krjdhWcy1aa1|eWcyaa"
except much longer.
Copy the entire string and paste it anywhere into the Lua root in your map, for example into the Config section of this library. Now, every time your map is
launched, the height map will be read from the string instead of being generated, making it guaranteed to be synced.
To check if the code has been generated correctly, launch your map one more time in single-player. The height map generated from the code will be checked against
one generated in the traditional way.
--=============================================================================================================================================================
C O N F I G
--=============================================================================================================================================================
]]
--Where to store data when exporting height map.
local SUBFOLDER = "PrecomputedHeightMap"
--If set to false, GetTerrainZ will be less accurate around cliffs, but slightly faster.
local STORE_CLIFF_DATA = true
--Set to true if you have water cliffs and have STORE_CLIFF_DATA enabled.
local STORE_WATER_DATA = true
--Write height map to file?
local WRITE_HEIGHT_MAP = false
--Check if height map read from string is accurate.
local VALIDATE_HEIGHT_MAP = true
--Create a special effect at each grid point to double-check if the height map is correct.
local VISUALIZE_HEIGHT_MAP = false
--=============================================================================================================================================================
local heightMap = {} ---@type table[]
local terrainHasCliffs = {} ---@type table[]
local terrainCliffLevel = {} ---@type table[]
local terrainHasWater = {} ---@type table[]
local moveableLoc = nil ---@type location
local MINIMUM_Z = -2048 ---@type number
local CLIFF_HEIGHT = 128 ---@type number
local worldMinX
local worldMinY
local worldMaxX
local worldMaxY
local iMax
local jMax
local chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!$&()[]=?:;,._#*~/{}<>^"
local NUMBER_OF_CHARS = string.len(chars)
---@param x number
---@param y number
---@return number
function GetLocZ(x, y)
MoveLocation(moveableLoc, x, y)
return GetLocationZ(moveableLoc)
end
GetTerrainZ = GetLocZ
---@param whichUnit unit
---@return number
function GetUnitZ(whichUnit)
return GetLocZ(GetUnitX(whichUnit), GetUnitY(whichUnit)) + GetUnitFlyHeight(whichUnit)
end
---@param whichUnit unit
---@return number, number, number
function GetUnitCoordinates(whichUnit)
local x = GetUnitX(whichUnit)
local y = GetUnitY(whichUnit)
return x, y, GetLocZ(x, y) + GetUnitFlyHeight(whichUnit)
end
local function OverwriteHeightFunctions()
---@param x number
---@param y number
---@return number
GetLocZ = function(x, y)
local rx = (x - worldMinX)*0.0078125 + 1
local ry = (y - worldMinY)*0.0078125 + 1
local i = rx // 1
local j = ry // 1
rx = rx - i
ry = ry - j
if i < 1 then
i = 1
rx = 0
elseif i > iMax then
i = iMax
rx = 1
end
if j < 1 then
j = 1
ry = 0
elseif j > jMax then
j = jMax
ry = 1
end
local heightMapI = heightMap[i]
local heightMapIplus1 = heightMap[i+1]
return (1 - ry)*((1 - rx)*heightMapI[j] + rx*heightMapIplus1[j]) + ry*((1 - rx)*heightMapI[j+1] + rx*heightMapIplus1[j+1])
end
if STORE_CLIFF_DATA then
---@param x number
---@param y number
---@return number
GetTerrainZ = function(x, y)
local rx = (x - worldMinX)*0.0078125 + 1
local ry = (y - worldMinY)*0.0078125 + 1
local i = rx // 1
local j = ry // 1
rx = rx - i
ry = ry - j
if i < 1 then
i = 1
rx = 0
elseif i > iMax then
i = iMax
rx = 1
end
if j < 1 then
j = 1
ry = 0
elseif j > jMax then
j = jMax
ry = 1
end
if terrainHasCliffs[i][j] then
if rx < 0.5 then
if ry < 0.5 then
if STORE_WATER_DATA and terrainHasWater[i][j] then
return heightMap[i][j]
else
return (1 - rx - ry)*heightMap[i][j] + (rx*(heightMap[i+1][j] - CLIFF_HEIGHT*(terrainCliffLevel[i+1][j] - terrainCliffLevel[i][j])) + ry*(heightMap[i][j+1] - CLIFF_HEIGHT*(terrainCliffLevel[i][j+1] - terrainCliffLevel[i][j])))
end
elseif STORE_WATER_DATA and terrainHasWater[i][j] then
return heightMap[i][j+1]
elseif rx + ry > 1 then
return (rx + ry - 1)*(heightMap[i+1][j+1] - CLIFF_HEIGHT*(terrainCliffLevel[i+1][j+1] - terrainCliffLevel[i][j+1])) + ((1 - rx)*heightMap[i][j+1] + (1 - ry)*(heightMap[i+1][j] - CLIFF_HEIGHT*(terrainCliffLevel[i+1][j] - terrainCliffLevel[i][j+1])))
else
return (1 - rx - ry)*(heightMap[i][j] - CLIFF_HEIGHT*(terrainCliffLevel[i][j] - terrainCliffLevel[i][j+1])) + (rx*(heightMap[i+1][j] - CLIFF_HEIGHT*(terrainCliffLevel[i+1][j] - terrainCliffLevel[i][j+1])) + ry*heightMap[i][j+1])
end
elseif ry < 0.5 then
if STORE_WATER_DATA and terrainHasWater[i][j] then
return heightMap[i+1][j]
elseif rx + ry > 1 then
return (rx + ry - 1)*(heightMap[i+1][j+1] - CLIFF_HEIGHT*(terrainCliffLevel[i+1][j+1] - terrainCliffLevel[i+1][j])) + ((1 - rx)*(heightMap[i][j+1] - CLIFF_HEIGHT*(terrainCliffLevel[i][j+1] - terrainCliffLevel[i+1][j])) + (1 - ry)*heightMap[i+1][j])
else
return (1 - rx - ry)*(heightMap[i][j] - CLIFF_HEIGHT*(terrainCliffLevel[i][j] - terrainCliffLevel[i+1][j])) + (rx*heightMap[i+1][j] + ry*(heightMap[i][j+1] - CLIFF_HEIGHT*(terrainCliffLevel[i][j+1] - terrainCliffLevel[i+1][j])))
end
elseif STORE_WATER_DATA and terrainHasWater[i][j] then
return heightMap[i+1][j+1]
else
return (rx + ry - 1)*heightMap[i+1][j+1] + ((1 - rx)*(heightMap[i][j+1] - CLIFF_HEIGHT*(terrainCliffLevel[i][j+1] - terrainCliffLevel[i+1][j+1])) + (1 - ry)*(heightMap[i+1][j] - CLIFF_HEIGHT*(terrainCliffLevel[i+1][j] - terrainCliffLevel[i+1][j+1])))
end
else
if rx + ry > 1 then --In top-right triangle
local heightMapIplus1 = heightMap[i+1]
return (rx + ry - 1)*heightMapIplus1[j+1] + ((1 - rx)*heightMap[i][j+1] + (1 - ry)*heightMapIplus1[j])
else
local heightMapI = heightMap[i]
return (1 - rx - ry)*heightMapI[j] + (rx*heightMap[i+1][j] + ry*heightMapI[j+1])
end
end
end
else
---@param x number
---@param y number
---@return number
GetTerrainZ = function(x, y)
local rx = (x - worldMinX)*0.0078125 + 1
local ry = (y - worldMinY)*0.0078125 + 1
local i = rx // 1
local j = ry // 1
rx = rx - i
ry = ry - j
if i < 1 then
i = 1
rx = 0
elseif i > iMax then
i = iMax
rx = 1
end
if j < 1 then
j = 1
ry = 0
elseif j > jMax then
j = jMax
ry = 1
end
if rx + ry > 1 then --In top-right triangle
local heightMapIplus1 = heightMap[i+1]
return (rx + ry - 1)*heightMapIplus1[j+1] + ((1 - rx)*heightMap[i][j+1] + (1 - ry)*heightMapIplus1[j])
else
local heightMapI = heightMap[i]
return (1 - rx - ry)*heightMapI[j] + (rx*heightMap[i+1][j] + ry*heightMapI[j+1])
end
end
end
end
local function CreateHeightMap()
local xMin = (worldMinX // 128)*128
local yMin = (worldMinY // 128)*128
local xMax = (worldMaxX // 128)*128 + 1
local yMax = (worldMaxY // 128)*128 + 1
local x = xMin
local y
local i = 1
local j
while x <= xMax do
heightMap[i] = {}
if STORE_CLIFF_DATA then
terrainHasCliffs[i] = {}
terrainCliffLevel[i] = {}
if STORE_WATER_DATA then
terrainHasWater[i] = {}
end
end
y = yMin
j = 1
while y <= yMax do
heightMap[i][j] = GetLocZ(x,y)
if VISUALIZE_HEIGHT_MAP then
BlzSetSpecialEffectZ(AddSpecialEffect("Doodads\\Cinematic\\GlowingRunes\\GlowingRunes0", x, y), heightMap[i][j] - 40)
end
if STORE_CLIFF_DATA then
local level1 = GetTerrainCliffLevel(x, y)
local level2 = GetTerrainCliffLevel(x, y + 128)
local level3 = GetTerrainCliffLevel(x + 128, y)
local level4 = GetTerrainCliffLevel(x + 128, y + 128)
if level1 ~= level2 or level1 ~= level3 or level1 ~= level4 then
terrainHasCliffs[i][j] = true
end
terrainCliffLevel[i][j] = level1
if STORE_WATER_DATA then
terrainHasWater[i][j] = not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)
or not IsTerrainPathable(x, y + 128, PATHING_TYPE_FLOATABILITY)
or not IsTerrainPathable(x + 128, y, PATHING_TYPE_FLOATABILITY)
or not IsTerrainPathable(x + 128, y + 128, PATHING_TYPE_FLOATABILITY)
end
end
j = j + 1
y = y + 128
end
i = i + 1
x = x + 128
end
iMax = i - 2
jMax = j - 2
end
local function ValidateHeightMap()
local xMin = (worldMinX // 128)*128
local yMin = (worldMinY // 128)*128
local xMax = (worldMaxX // 128)*128 + 1
local yMax = (worldMaxY // 128)*128 + 1
local numOutdated = 0
local x = xMin
local y
local i = 1
local j
while x <= xMax do
y = yMin
j = 1
while y <= yMax do
if heightMap[i][j] then
if VISUALIZE_HEIGHT_MAP then
BlzSetSpecialEffectZ(AddSpecialEffect("Doodads\\Cinematic\\GlowingRunes\\GlowingRunes0", x, y), heightMap[i][j] - 40)
end
if bj_isSinglePlayer and math.abs(heightMap[i][j] - GetLocZ(x, y)) > 1 then
numOutdated = numOutdated + 1
end
else
print("Height Map nil at x = " .. x .. ", y = " .. y)
end
j = j + 1
y = y + 128
end
i = i + 1
x = x + 128
end
if numOutdated > 0 then
print("|cffff0000Warning:|r Height Map is outdated at " .. numOutdated .. " locations...")
end
end
local function ReadHeightMap()
local charPos = 0
local numRepetitions = 0
local charValues = {}
for i = 1, NUMBER_OF_CHARS do
charValues[string.sub(chars, i, i)] = i - 1
end
local firstChar = nil
local PLUS = 0
local MINUS = 1
local ABS = 2
local segmentType = ABS
for i = 1, #heightMap do
for j = 1, #heightMap[i] do
if numRepetitions > 0 then
heightMap[i][j] = heightMap[i][j-1]
numRepetitions = numRepetitions - 1
else
local valueDetermined = false
while not valueDetermined do
charPos = charPos + 1
local char = string.sub(HeightMapCode, charPos, charPos)
if char == "+" then
segmentType = PLUS
charPos = charPos + 1
char = string.sub(HeightMapCode, charPos, charPos)
elseif char == "-" then
segmentType = MINUS
charPos = charPos + 1
char = string.sub(HeightMapCode, charPos, charPos)
elseif char == "|" then
segmentType = ABS
charPos = charPos + 1
char = string.sub(HeightMapCode, charPos, charPos)
end
if tonumber(char) then
local k = 0
while tonumber(string.sub(HeightMapCode, charPos + k + 1, charPos + k + 1)) do
k = k + 1
end
numRepetitions = tonumber(string.sub(HeightMapCode, charPos, charPos + k)) - 1
charPos = charPos + k
valueDetermined = true
heightMap[i][j] = heightMap[i][j-1]
else
if segmentType == PLUS then
heightMap[i][j] = heightMap[i][j-1] + charValues[char]
valueDetermined = true
elseif segmentType == MINUS then
heightMap[i][j] = heightMap[i][j-1] - charValues[char]
valueDetermined = true
elseif firstChar then
if charValues[firstChar] and charValues[char] then
heightMap[i][j] = charValues[firstChar]*NUMBER_OF_CHARS + charValues[char] + MINIMUM_Z
else
heightMap[i][j] = 0
end
firstChar = nil
valueDetermined = true
else
firstChar = char
end
end
end
end
end
end
HeightMapCode = nil
end
local function WriteHeightMap(subfolder)
PreloadGenClear()
PreloadGenStart()
local numRepetitions = 0
local firstChar
local secondChar
local stringLength = 0
local lastValue = 0
local PLUS = 0
local MINUS = 1
local ABS = 2
local segmentType = ABS
local preloadString = {'HeightMapCode = "'}
for i = 1, #heightMap do
for j = 1, #heightMap[i] do
if j > 1 then
local diff = (heightMap[i][j] - lastValue)//1
if diff == 0 then
numRepetitions = numRepetitions + 1
else
if numRepetitions > 0 then
table.insert(preloadString, numRepetitions)
end
numRepetitions = 0
if diff > 0 and diff < NUMBER_OF_CHARS then
if segmentType ~= PLUS then
segmentType = PLUS
table.insert(preloadString, "+")
end
elseif diff < 0 and diff > -NUMBER_OF_CHARS then
if segmentType ~= MINUS then
segmentType = MINUS
table.insert(preloadString, "-")
end
else
if segmentType ~= ABS then
segmentType = ABS
table.insert(preloadString, "|")
end
end
if segmentType == ABS then
firstChar = (heightMap[i][j] - MINIMUM_Z) // NUMBER_OF_CHARS + 1
secondChar = heightMap[i][j]//1 - MINIMUM_Z - (heightMap[i][j]//1 - MINIMUM_Z)//NUMBER_OF_CHARS*NUMBER_OF_CHARS + 1
table.insert(preloadString, string.sub(chars, firstChar, firstChar) .. string.sub(chars, secondChar, secondChar))
elseif segmentType == PLUS then
firstChar = diff//1 + 1
table.insert(preloadString, string.sub(chars, firstChar, firstChar))
elseif segmentType == MINUS then
firstChar = -diff//1 + 1
table.insert(preloadString, string.sub(chars, firstChar, firstChar))
end
end
else
if numRepetitions > 0 then
table.insert(preloadString, numRepetitions)
end
segmentType = ABS
table.insert(preloadString, "|")
numRepetitions = 0
firstChar = (heightMap[i][j] - MINIMUM_Z) // NUMBER_OF_CHARS + 1
secondChar = heightMap[i][j]//1 - MINIMUM_Z - (heightMap[i][j]//1 - MINIMUM_Z)//NUMBER_OF_CHARS*NUMBER_OF_CHARS + 1
table.insert(preloadString, string.sub(chars, firstChar, firstChar) .. string.sub(chars, secondChar, secondChar))
end
lastValue = heightMap[i][j]//1
stringLength = stringLength + 1
if stringLength == 100 then
Preload(table.concat(preloadString))
stringLength = 0
for k, __ in ipairs(preloadString) do
preloadString[k] = nil
end
end
end
end
if numRepetitions > 0 then
table.insert(preloadString, numRepetitions)
end
table.insert(preloadString, '"')
Preload(table.concat(preloadString))
PreloadGenEnd(subfolder .. "\\heightMap.txt")
print("Written Height Map to CustomMapData\\" .. subfolder .. "\\heightMap.txt")
end
local function InitHeightMap()
local xMin = (worldMinX // 128)*128
local yMin = (worldMinY // 128)*128
local xMax = (worldMaxX // 128)*128 + 1
local yMax = (worldMaxY // 128)*128 + 1
local x = xMin
local y
local i = 1
local j
while x <= xMax do
heightMap[i] = {}
if STORE_CLIFF_DATA then
terrainHasCliffs[i] = {}
terrainCliffLevel[i] = {}
if STORE_WATER_DATA then
terrainHasWater[i] = {}
end
end
y = yMin
j = 1
while y <= yMax do
heightMap[i][j] = 0
if STORE_CLIFF_DATA then
local level1 = GetTerrainCliffLevel(x, y)
local level2 = GetTerrainCliffLevel(x, y + 128)
local level3 = GetTerrainCliffLevel(x + 128, y)
local level4 = GetTerrainCliffLevel(x + 128, y + 128)
if level1 ~= level2 or level1 ~= level3 or level1 ~= level4 then
terrainHasCliffs[i][j] = true
end
terrainCliffLevel[i][j] = level1
if STORE_WATER_DATA then
terrainHasWater[i][j] = not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)
or not IsTerrainPathable(x, y + 128, PATHING_TYPE_FLOATABILITY)
or not IsTerrainPathable(x + 128, y, PATHING_TYPE_FLOATABILITY)
or not IsTerrainPathable(x + 128, y + 128, PATHING_TYPE_FLOATABILITY)
end
end
j = j + 1
y = y + 128
end
i = i + 1
x = x + 128
end
iMax = i - 2
jMax = j - 2
end
OnInit.final("PrecomputedHeightMap", function()
local worldBounds = GetWorldBounds()
worldMinX = GetRectMinX(worldBounds)
worldMinY = GetRectMinY(worldBounds)
worldMaxX = GetRectMaxX(worldBounds)
worldMaxY = GetRectMaxY(worldBounds)
moveableLoc = Location(0, 0)
if HeightMapCode then
InitHeightMap()
ReadHeightMap()
if bj_isSinglePlayer and VALIDATE_HEIGHT_MAP then
ValidateHeightMap()
end
else
CreateHeightMap()
if WRITE_HEIGHT_MAP then
WriteHeightMap(SUBFOLDER)
end
end
OverwriteHeightFunctions()
end)
end
if Debug then Debug.beginFile "FileIO" end
--[[
FileIO v1a
Provides functionality to read and write files, optimized with lua functionality in mind.
API:
FileIO.Save(filename, data)
- Write string data to a file
FileIO.Load(filename) -> string?
- Read string data from a file. Returns nil if file doesn't exist.
FileIO.SaveAsserted(filename, data, onFail?) -> bool
- Saves the file and checks that it was saved successfully.
If it fails, passes (filename, data, loadResult) to onFail.
FileIO.enabled : bool
- field that indicates that files can be accessed correctly.
Optional requirements:
DebugUtils by Eikonium @ https://www.hiveworkshop.com/threads/330758/
Total Initialization by Bribe @ https://www.hiveworkshop.com/threads/317099/
Inspired by:
- TriggerHappy's Codeless Save and Load @ https://www.hiveworkshop.com/threads/278664/
- ScrewTheTrees's Codeless Save/Sync concept @ https://www.hiveworkshop.com/threads/325749/
- Luashine's LUA variant of TH's FileIO @ https://www.hiveworkshop.com/threads/307568/post-3519040
- HerlySQR's LUA variant of TH's Save/Load @ https://www.hiveworkshop.com/threads/331536/post-3565884
Updated: 8 Mar 2023
--]]
OnInit("FileIO", function()
local RAW_PREFIX = ']]i([['
local RAW_SUFFIX = ']])--[['
local RAW_SIZE = 250 - #RAW_PREFIX - #RAW_SUFFIX
local LOAD_ABILITY = FourCC('ANdc')
local LOAD_EMPTY_KEY = '!@#$, empty data'
local name
local function open(filename)
name = filename
PreloadGenClear()
Preload('")\nendfunction\n//!beginusercode\nlocal p={} local i=function(s) table.insert(p,s) end--[[')
end
local function write(s)
for i = 1, #s, RAW_SIZE do
Preload(RAW_PREFIX .. s:sub(i, i + RAW_SIZE - 1) .. RAW_SUFFIX)
end
end
local function close()
Preload(']]BlzSetAbilityTooltip(' ..
LOAD_ABILITY .. ', table.concat(p), 0)\n//!endusercode\nfunction a takes nothing returns nothing\n//')
PreloadGenEnd(name)
name = nil
end
---
---@param filename string
---@param data string
local function savefile(filename, data)
open(filename)
write(data)
close()
end
---@param filename string
---@return string?
local function loadfile(filename)
local s = BlzGetAbilityTooltip(LOAD_ABILITY, 0)
BlzSetAbilityTooltip(LOAD_ABILITY, LOAD_EMPTY_KEY, 0)
Preloader(filename)
local loaded = BlzGetAbilityTooltip(LOAD_ABILITY, 0)
BlzSetAbilityTooltip(LOAD_ABILITY, s, 0)
if loaded == LOAD_EMPTY_KEY then
return nil
end
return loaded
end
---@param filename string
---@param data string
---@param onFail function?
---@return boolean
local function saveAsserted(filename, data, onFail)
savefile(filename, data)
local res = loadfile(filename)
if res == data then
return true
end
if onFail then
onFail(filename, data, res)
end
return false
end
local fileIO_enabled = saveAsserted('TestFileIO.pld', 'FileIO is Enabled')
FileIO = {
Save = savefile,
Load = loadfile,
SaveAsserted = saveAsserted,
enabled = fileIO_enabled,
}
end)
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "ALICE" end
---@diagnostic disable: need-check-nil
do
--[[
=============================================================================================================================================================
A Limitless Interaction Caller Engine
by Antares
v2.8.4
A Lua system to easily create highly performant checks and interactions, between any type of objects.
Requires:
TotalInitialization https://www.hiveworkshop.com/threads/total-initialization.317099/
Hook https://www.hiveworkshop.com/threads/hook.339153/
HandleType https://www.hiveworkshop.com/threads/get-handle-type.354436/
PrecomputedHeightMap (optional) https://www.hiveworkshop.com/threads/precomputed-synchronized-terrain-height-map.353477/
For tutorials & documentation, see here:
https://www.hiveworkshop.com/threads/.353126/
=============================================================================================================================================================
C O N F I G
=============================================================================================================================================================
]]
ALICE_Config = {
--Minimum interval between interactions in seconds. Sets the time step of the timer. All interaction intervals are an integer multiple of this value.
MIN_INTERVAL = 0.02 ---@constant number
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--Debugging
--Print out warnings, errors, and enable the "downtherabbithole" cheat code for the players with these names. #XXXX not required.
,MAP_CREATORS = { ---@constant string[]
"Antares",
"WorldEdit"
}
--Abort the cycle the first time it crashes. Makes it easier to identify a bug if downstream errors are prevented. Disable for release version.
,HALT_ON_FIRST_CRASH = true ---@constant boolean
--These constants control which hotkeys are used for the various commands in debug mode. The key combo is Ctrl + the specified hotkey.
,CYCLE_SELECTION_HOTKEY = "Q"
,LOCK_SELECTION_HOTKEY = "W"
,NEXT_STEP_HOTKEY = "R"
,HALT_CYCLE_HOTKEY = "T"
,PRINT_FUNCTION_NAMES_HOTKEY = "G"
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--Optimization
--Maximum interval between interactions in seconds.
,MAX_INTERVAL = 10.0 ---@constant number
--This interval is used by a second, faster timer that can be used to update visual effects at a faster rate than the MIN_INTERVAL with ALICE_PairInterpolate.
--Set to nil to disable.
,INTERPOLATION_INTERVAL = 0.005 ---@constant number
--The playable map area is divided into cells of this size. Objects only interact with other objects that share a cell with them. Smaller cells increase the
--efficiency of interactions at the cost of increased memory usage and overhead.
,CELL_SIZE = 256 ---@constant number
--How often the system checks if objects left their current cell. Should be overwritten with the cellCheckInterval flag for fast-moving objects.
,DEFAULT_CELL_CHECK_INTERVAL = 0.1 ---@constant number
--How large an actor is when it comes to determining in which cells it is in and its maximum interaction range. Should be overwritten with the radius flag for
--objects with a larger interaction range.
,DEFAULT_OBJECT_RADIUS = 75 ---@constant number
--You can integrate ALICE's internal table recycling system into your own by setting the GetTable and ReturnTable functions here.
,TABLE_RECYCLER_GET = nil ---@constant function
,TABLE_RECYCLER_RETURN = nil ---@constant function
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--Automatic actor creation for widgets
--Actor creation for user-defined objects is up to you. For widgets, ALICE offers automatic actor creation and destruction.
--Determine which widget types will automatically receive actors and be registered with ALICE. The created actors are passive and only receive pairs. You can
--add exceptions with ALICE_IncludeType and ALICE_ExcludeType.
,NO_UNIT_ACTOR = false ---@constant boolean
,NO_DESTRUCTABLE_ACTOR = false ---@constant boolean
,NO_ITEM_ACTOR = false ---@constant boolean
--Add widget names (converted to camelCase) as identifiers to widget actors. Note that the names of widgets are localized and you risk a desync if you reference
--a widget by name unless it's a custom name.
,ADD_WIDGET_NAMES = false ---@constant boolean
--Disable to destroy unit actors on death. Units will not regain an actor when revived.
,UNITS_LEAVE_BEHIND_CORPSES = true ---@constant boolean
--Disable if corpses are relevant and you're moving them around.
,UNIT_CORPSES_ARE_STATIONARY = true ---@constant boolean
--Add identifiers such as "hero" or "mechanical" to units if they have the corresponding classification and "nonhero", "nonmechanical" etc. if they do not.
--The identifiers will not get updated automatically when a unit gains or loses classifications and you must update them manually with ALICE_SwapIdentifier.
,UNIT_ADDED_CLASSIFICATIONS = { ---@constant unittype[]
UNIT_TYPE_HERO,
UNIT_TYPE_STRUCTURE
}
--The radius of the unit actors. Set to nil to use DEFAULT_OBJECT_RADIUS.
,DEFAULT_UNIT_RADIUS = nil ---@constant number
--The radius of the destructable actors. Set to nil to use DEFAULT_OBJECT_RADIUS.
,DEFAULT_DESTRUCTABLE_RADIUS = nil ---@constant number
--Disable if items are relevant and you're moving them around.
,ITEMS_ARE_STATIONARY = true ---@constant boolean
--The radius of the item actors. Set to nil to use DEFAULT_OBJECT_RADIUS.
,DEFAULT_ITEM_RADIUS = nil ---@constant number
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--For EmmyLua annotations. If you're passing any types into object functions other than these types, add them here to disable warnings.
---@alias Object unit | destructable | item | table
--[[
=============================================================================================================================================================
E N D O F C O N F I G
=============================================================================================================================================================
]]
--#region Variables
ALICE_TimeElapsed = 0.0 ---@readonly number
ALICE_CPULoad = 0 ---@readonly number
ALICE_Where = "outsideofcycle" ---@readonly "outsideofcycle" | "precleanup" | "postcleanup" | "callbacks" | "everystep" | "cellcheck" | "variablestep"
MATCHING_TYPE_ANY = {} ---@constant table
MATCHING_TYPE_ALL = {} ---@constant table
local max = math.max
local min = math.min
local sqrt = math.sqrt
local atan = math.atan
local insert = table.insert
local concat = table.concat
local sort = table.sort
local pack = table.pack
local unpack = table.unpack
local config = ALICE_Config
local timers = {} ---@type timer[]
local MAX_STEPS = 0 ---@type integer
local CYCLE_LENGTH = 0 ---@type integer
local DO_NOT_EVALUATE = 0 ---@type integer
local MAP_MIN_X ---@type number
local MAP_MAX_X ---@type number
local MAP_MIN_Y ---@type number
local MAP_MAX_Y ---@type number
local MAP_SIZE_X ---@type number
local MAP_SIZE_Y ---@type number
local NUM_CELLS_X ---@type integer
local NUM_CELLS_Y ---@type integer
local CELL_MIN_X = {} ---@type number[]
local CELL_MIN_Y = {} ---@type number[]
local CELL_MAX_X = {} ---@type number[]
local CELL_MAX_Y = {} ---@type number[]
local CELL_LIST = {} ---@type Cell[][]
--[[Array indices for pair fields. Storing as a sequence up to 8 reduces memory usage. Constants have been inlined because of local variable limit. Hexadecimals to be able to revert with find&replace.
ACTOR A = 0x1
ACTOR_B = 0x2
HOST_A = 0x3
HOST_B = 0x4
CURRENT_POSITION / NEXT = 0x5
POSITION_IN_STEP / PREVIOUS = 0x6
EVERY_STEP = 0x7
INTERACTION_FUNC = 0x8
]]
local EMPTY_TABLE = {} ---@constant table
local SELF_INTERACTION_ACTOR ---@type Actor
local DUMMY_PAIR ---@constant Pair
= {destructionQueued = true}
local OUTSIDE_OF_CYCLE = ---@constant Pair
setmetatable({}, {__index = function()
error("Attempted to call Pair API function from outside of allowed functions.")
end})
local cycle = {
counter = 0, ---@type integer
unboundCounter = 0, ---@type integer
isHalted = false, ---@type boolean
isCrash = false, ---@type boolean
freezeCounter = 0, ---@type number
}
local currentPair = OUTSIDE_OF_CYCLE ---@type Pair | nil
local totalActors = 0 ---@type integer
local moveableLoc ---@type location
local numPairs = {} ---@type integer[]
local whichPairs = {} ---@type table[]
local firstEveryStepPair = DUMMY_PAIR ---@type Pair
local lastEveryStepPair = DUMMY_PAIR ---@type Pair
local numEveryStepPairs = 0 ---@type integer
local actorList = {} ---@type Actor[]
local celllessActorList = {} ---@type Actor[]
local pairList = {} ---@type Pair[]
local pairingExcluded = {} ---@type table[]
local numCellChecks = {} ---@type integer[]
local cellCheckedActors = {} ---@type Actor[]
local actorAlreadyChecked = {} ---@type boolean[]
local unusedPairs = {} ---@type Pair[]
local unusedActors = {} ---@type Actor[]
local unusedTables = {} ---@type table[]
local additionalFlags = {} ---@type table
local destroyedActors = {} ---@type Actor[]
local actorOf = {} ---@type Actor[]
local alreadyEnumerated = {} ---@type any[]
local interpolatedPairs = {} ---@type Pair[]
local actorsOfActorClass = ---@type Actor[]
setmetatable({}, {__index = function(self, key) self[key] = {} return self[key] end})
local isInterpolated ---@type boolean
local interpolationCounter = 10 ---@type integer
local functionIsEveryStep = {} ---@type table<function,boolean>
local functionIsUnbreakable = {} ---@type table<function,boolean>
local functionIsUnsuspendable = {} ---@type table<function,boolean>
local functionInitializer = {} ---@type table<function,function>
local functionDelay = {} ---@type table<function,number>
local functionDelayIsDistributed = {} ---@type table<function,boolean>
local functionDelayCurrent = {} ---@type table<function,number>
local functionOnDestroy = {} ---@type table<function,function>
local functionOnBreak = {} ---@type table<function,function>
local functionOnReset = {} ---@type table<function,function>
local functionPauseOnStationary = {} ---@type table<function,boolean>
local functionRequiredFields = {} ---@type table<function,table>
local functionKey = {} ---@type table<function,integer>
local highestFunctionKey = 0 ---@type integer
local delayedCallbackFunctions = {} ---@type function[]
local delayedCallbackArgs = {} ---@type table[]
local unitOwnerFunc = {} ---@type function[]
local userCallbacks = {} ---@type table[]
local pairingFunctions = {} ---@type table[]
local objectIsStationary ---@type table<any,boolean>
= setmetatable({}, {__mode = "k"})
local widgets = {
bindChecks = {}, ---@type Actor[]
deathTriggers ---@type table<destructable|item,trigger>
= setmetatable({}, {__mode = "k"}),
reviveTriggers = {}, ---@type table<unit,trigger>
idExclusions = {}, ---@type table<integer,boolean>
idInclusions = {}, ---@type table<integer,boolean>
hash = nil ---@type hashtable
}
local onCreation = { ---@type table
flags = {}, ---@type table<string,table>
funcs = {}, ---@type table<string,table>
identifiers = {}, ---@type table<string,table>
interactions = {}, ---@type table<string,table>
selfInteractions = {} ---@type table<string,table>
}
local INV_MIN_INTERVAL ---@constant number
= 1/config.MIN_INTERVAL - 0.001
local OVERWRITEABLE_FLAGS = { ---@constant table<string,boolean>
priority = true,
zOffset = true,
cellCheckInterval = true,
isStationary = true,
radius = true,
persistOnDeath = true,
onActorDestroy = true,
hasInfiniteRange = true,
isUnselectable = true,
}
local RECOGNIZED_FLAGS = { ---@constant table<string,boolean>
anchor = true,
zOffset = true,
cellCheckInterval = true,
isStationary = true,
radius = true,
persistOnDeath = true,
width = true,
height = true,
bindToBuff = true,
bindToOrder = true,
onActorDestroy = true,
hasInfiniteRange = true,
isGlobal = true,
isAnonymous = true,
isUnselectable = true,
actorClass = true,
selfInteractions = true
}
---@class ALICE_Flags
---@field anchor Object
---@field zOffset number
---@field cellCheckInterval number
---@field isStationary boolean
---@field radius number
---@field persistOnDeath boolean
---@field width number
---@field height number
---@field bindToBuff string | integer
---@field bindToOrder string | integer
---@field onActorDestroy function
---@field hasInfiniteRange boolean
---@field isGlobal boolean
---@field isAnonymous boolean
---@field isUnselectable boolean
---@field actorClass string
---@field selfInteractions string | string[]
local UNIT_CLASSIFICATION_NAMES = { ---@constant table<unittype,string>
[UNIT_TYPE_HERO] = "hero",
[UNIT_TYPE_STRUCTURE] = "structure",
[UNIT_TYPE_MECHANICAL] = "mechanical",
[UNIT_TYPE_UNDEAD] = "undead",
[UNIT_TYPE_TAUREN] = "tauren",
[UNIT_TYPE_ANCIENT] = "ancient",
[UNIT_TYPE_SAPPER] = "sapper",
[UNIT_TYPE_PEON] = "worker",
[UNIT_TYPE_FLYING] = "flying",
[UNIT_TYPE_GIANT] = "giant",
[UNIT_TYPE_SUMMONED] = "summoned",
[UNIT_TYPE_TOWNHALL] = "townhall",
}
local GetTable ---@type function
local ReturnTable ---@type function
local Create ---@type function
local Destroy ---@type function
local Release ---@type function
local CreateReference ---@type function
local RemoveReference ---@type function
local SetCoordinateFuncs ---@type function
local SetOwnerFunc ---@type function
local InitCells ---@type function
local InitCellChecks ---@type function
local AssignActorClass ---@type function
local Flicker ---@type function
local SharesCellWith ---@type function
local CreateBinds ---@type function
local DestroyObsoletePairs ---@type function
local Unpause ---@type function
local SetStationary ---@type function
local VisualizeCells ---@type function
local RedrawCellVisualizers ---@type function
local Suspend ---@type function
local Deselect ---@type function
local Select ---@type function
local GetMissingRequiredFieldsString ---@type function
local GetDescription ---@type function
local CreateVisualizer ---@type function
local EnterCell ---@type function
local RemoveCell ---@type function
local LeaveCell ---@type function
local VisualizationLightning ---@type function
local GetTerrainZ ---@type function
local GetActor ---@type function
local EnableDebugMode ---@type function
local UpdateSelectedActor ---@type function
local OnDestructableDeath ---@type function
local OnItemDeath ---@type function
local debug = {} ---@type table
debug.enabled = false ---@type boolean
debug.mouseClickTrigger = nil ---@type trigger
debug.cycleSelectTrigger = nil ---@type trigger
debug.nextStepTrigger = nil ---@type trigger
debug.lockSelectionTrigger = nil ---@type trigger
debug.haltTrigger = nil ---@type trigger
debug.printFunctionsTrigger = nil ---@type trigger
debug.selectedActor = nil ---@type Actor | nil
debug.tooltip = nil ---@type framehandle
debug.tooltipText = nil ---@type framehandle
debug.tooltipTitle = nil ---@type framehandle
debug.visualizationLightnings = {} ---@type lightning[]
debug.selectionLocked = false ---@type boolean
debug.benchmark = false ---@type boolean
debug.visualizeAllActors = false ---@type boolean
debug.visualizeAllCells = false ---@type boolean
debug.printFunctionNames = false ---@type boolean
debug.evaluationTime = {} ---@type table
debug.gameIsPaused = nil ---@type boolean
debug.trackedVariables = {} ---@type table<string,boolean>
debug.functionName = {} ---@type table<function,string>
debug.controlIsPressed = false ---@type boolean
local eventHooks = { ---@type table[]
onUnitEnter = {},
onUnitDeath = {},
onUnitRevive = {},
onUnitRemove = {},
onUnitChangeOwner = {},
onDestructableEnter = {},
onDestructableDestroy = {},
onItemEnter = {},
onItemDestroy = {}
}
--#endregion
--===========================================================================================================================================================
--Filter Functions
--===========================================================================================================================================================
--#region Filter Functions
---For debug functions.
---@param whichIdentifier string | string[] | nil
local function Identifier2String(whichIdentifier)
local toString = "("
local i = 1
for key, __ in pairs(whichIdentifier) do
if i > 1 then
toString = toString .. ", "
end
toString = toString .. key
i = i + 1
end
toString = toString .. ")"
return toString
end
local function HasIdentifierFromTable(actor, whichIdentifier)
if whichIdentifier[#whichIdentifier] == MATCHING_TYPE_ANY then
for i = 1, #whichIdentifier - 1 do
if actor.identifier[whichIdentifier[i]] then
return true
end
end
return false
elseif whichIdentifier[#whichIdentifier] == MATCHING_TYPE_ALL then
for i = 1, #whichIdentifier - 1 do
if not actor.identifier[whichIdentifier[i]] then
return false
end
end
return true
elseif type(whichIdentifier[#whichIdentifier]) == "string" then
error("Matching type missing in identifier table.")
else
error("Invalid matching type specified in identifier table.")
end
end
--#endregion
--===========================================================================================================================================================
--Utility
--===========================================================================================================================================================
--#region Utility
local function Warning(whichWarning)
for __, name in ipairs(config.MAP_CREATORS) do
if string.find(GetPlayerName(GetLocalPlayer()), name) then
print(whichWarning)
end
end
end
local function AddDelayedCallback(func, arg1, arg2, arg3)
local index = #delayedCallbackFunctions + 1
delayedCallbackFunctions[index] = func
local args = delayedCallbackArgs[index] or {}
args[1], args[2], args[3] = arg1, arg2, arg3
delayedCallbackArgs[index] = args
end
local function RemoveUserCallbackFromList(self)
if self == userCallbacks.first then
if self.next then
self.next.previous = nil
else
userCallbacks.last = nil
end
userCallbacks.first = self.next
elseif self == userCallbacks.last then
userCallbacks.last = self.previous
self.previous.next = nil
else
self.previous.next = self.next
self.next.previous = self.previous
end
end
local function ExecuteUserCallback(self)
if self.pair then
if self.pair[0x3] == self.hostA and self.pair[0x4] == self.hostB then
currentPair = self.pair
self.callback(self.hostA, self.hostB, true)
if functionOnDestroy[self.callback] then
functionOnDestroy[self.callback](self.hostA, self.hostB, true)
end
currentPair = OUTSIDE_OF_CYCLE
else
self.callback(self.hostA, self.hostB, false)
if functionOnDestroy[self.callback] then
functionOnDestroy[self.callback](self.hostA, self.hostB, false)
end
end
elseif self.args then
if self.unpack then
self.callback(unpack(self.args))
if functionOnDestroy[self.callback] then
functionOnDestroy[self.callback](unpack(self.args))
end
ReturnTable(self.args)
else
self.callback(self.args)
if functionOnDestroy[self.callback] then
functionOnDestroy[self.callback](self.args)
end
end
else
self.callback()
if functionOnDestroy[self.callback] then
functionOnDestroy[self.callback]()
end
end
RemoveUserCallbackFromList(self)
for key, __ in pairs(self) do
self[key] = nil
end
end
local function AddUserCallback(self)
if userCallbacks.first == nil then
userCallbacks.first = self
userCallbacks.last = self
else
local node = userCallbacks.last
local callCounter = self.callCounter
while node and node.callCounter > callCounter do
node = node.previous
end
if node == nil then
--Insert at the beginning
userCallbacks.first.previous = self
self.next = userCallbacks.first
userCallbacks.first = self
else
if node == userCallbacks.last then
--Insert at the end
userCallbacks.last = self
else
--Insert in the middle
self.next = node.next
self.next.previous = self
end
self.previous = node
node.next = self
end
end
end
local function PeriodicWrapper(caller)
if caller.excess > 0 then
local returnValue = caller.excess
caller.excess = caller.excess - config.MAX_INTERVAL
return returnValue
end
local returnValue = caller.callback(unpack(caller))
if returnValue and returnValue > config.MAX_INTERVAL then
caller.excess = returnValue - config.MAX_INTERVAL
end
return returnValue
end
local function RepeatedWrapper(caller)
if caller.excess > 0 then
local returnValue = caller.excess
caller.excess = caller.excess - config.MAX_INTERVAL
return returnValue
end
caller.currentExecution = caller.currentExecution + 1
local returnValue = caller.callback(caller.currentExecution, unpack(caller))
if caller.currentExecution == caller.howOften then
ALICE_DisableCallback()
end
if returnValue and returnValue > config.MAX_INTERVAL then
caller.excess = returnValue - config.MAX_INTERVAL
end
return returnValue
end
local function ToUpperCase(__, letter)
return letter:upper()
end
local toCamelCase = setmetatable({}, {
__index = function(self, whichString)
whichString = whichString:gsub("|[cC]\x25x\x25x\x25x\x25x\x25x\x25x\x25x\x25x", "") --remove color codes
whichString = whichString:gsub("|[rR]", "") --remove closing color codes
whichString = whichString:gsub("(\x25s)(\x25a)", ToUpperCase) --remove spaces and convert to upper case after space
whichString = whichString:gsub("[^\x25w]", "") --remove special characters
self[whichString] = string.lower(whichString:sub(1,1)) .. string.sub(whichString,2) --converts first character to lower case
return self[whichString]
end
})
--For debug mode
local function Function2String(func)
if debug.functionName[func] then
return debug.functionName[func]
end
local string = string.gsub(tostring(func), "function: ", "")
if string.sub(string,1,1) == "0" then
return string.sub(string, string.len(string) - 3, string.len(string))
else
return string
end
end
--For debug mode
local function Object2String(object)
if IsHandle[object] then
if IsWidget[object] then
if HandleType[object] == "unit" then
local str = string.gsub(tostring(object), "unit: ", "")
if str:sub(1,1) == "0" then
return GetUnitName(object) .. ": " .. str:sub(str:len() - 3, str:len())
else
return str
end
elseif HandleType[object] == "destructable" then
local str = string.gsub(tostring(object), "destructable: ", "")
if str:sub(1,1) == "0" then
return GetDestructableName(object) .. ": " .. str:sub(str:len() - 3, str:len())
else
return str
end
else
local str = string.gsub(tostring(object), "item: ", "")
if str:sub(1,1) == "0" then
return GetItemName(object) .. ": " .. str:sub(str:len() - 3, str:len())
else
return str
end
end
else
local str = tostring(object)
local address = str:sub((str:find(":", nil, true) or 0) + 2, str:len())
return HandleType[object] .. " " .. (address:sub(1,1) == "0" and address:sub(address:len() - 3, address:len()))
end
elseif object.__name then
local str = string.gsub(tostring(object), object.__name .. ": ", "")
str = string.sub(str, string.len(str) - 3, string.len(str))
return object.__name .. " " .. str
else
local str = tostring(object)
if string.sub(str,8,8) == "0" then
return "table: " .. string.sub(str, string.len(str) - 3, string.len(str))
else
return str
end
end
end
local function OnUnitChangeOwner()
local u = GetTriggerUnit()
local newOwner = GetOwningPlayer(u)
unitOwnerFunc[newOwner] = unitOwnerFunc[newOwner] or function() return newOwner end
if actorOf[u] then
if actorOf[u].isActor then
actorOf[u].getOwner = unitOwnerFunc[newOwner]
else
for __, actor in ipairs(actorOf[u]) do
actor.getOwner = unitOwnerFunc[newOwner]
end
end
end
for __, func in ipairs(eventHooks.onUnitChangeOwner) do
func(u)
end
end
--#endregion
--===========================================================================================================================================================
--Getter functions
--===========================================================================================================================================================
--#region Getter Functions
--x and y are stored with an anchor key because GetUnitX etc. cannot take an actor as an argument and they are identical for all actors anchored to the same
--object. z is stored with an actor key because it requires zOffset, which is stored on the actor, and the z-values are not guaranteed to be identical for all
--actors anchored to the same object.
local coord = {
classX = setmetatable({}, {__index = function(self, key) self[key] = key.x return self[key] end}),
classY = setmetatable({}, {__index = function(self, key) self[key] = key.y return self[key] end}),
classZ = setmetatable({}, {__index = function(self, key) self[key] = key.anchor.z + key.zOffset return self[key] end}),
unitX = setmetatable({}, {__index = function(self, key) self[key] = GetUnitX(key) return self[key] end}),
unitY = setmetatable({}, {__index = function(self, key) self[key] = GetUnitY(key) return self[key] end}),
unitZ = setmetatable({}, {__index = function(self, key) self[key] = GetTerrainZ(key.x[key.anchor], key.y[key.anchor]) + GetUnitFlyHeight(key.anchor) + key.zOffset return self[key] end}),
destructableX = setmetatable({}, {__index = function(self, key) self[key] = GetDestructableX(key) return self[key] end}),
destructableY = setmetatable({}, {__index = function(self, key) self[key] = GetDestructableY(key) return self[key] end}),
destructableZ = setmetatable({}, {__index = function(self, key) self[key] = GetTerrainZ(key.x[key.anchor], key.y[key.anchor]) + key.zOffset return self[key] end}),
itemX = setmetatable({}, {__index = function(self, key) self[key] = GetItemX(key) return self[key] end}),
itemY = setmetatable({}, {__index = function(self, key) self[key] = GetItemY(key) return self[key] end}),
itemZ = setmetatable({}, {__index = function(self, key) self[key] = GetTerrainZ(key.x[key.anchor], key.y[key.anchor]) + key.zOffset return self[key] end}),
terrainZ = setmetatable({}, {__index = function(self, key) self[key] = GetTerrainZ(key.x[key.anchor], key.y[key.anchor]) + key.zOffset return self[key] end}),
globalXYZ = setmetatable({}, {__index = function(self, key) self[key] = 0 return 0 end})
}
local function GetClassOwner(source)
return source.owner
end
local function GetClassOwnerById(source)
return Player(source.owner - 1)
end
--#endregion
--===========================================================================================================================================================
--Pair Class
--===========================================================================================================================================================
--#region Pair
---@class Pair
local Pair = {
destructionQueued = nil, ---@type boolean
userData = nil, ---@type table
hadContact = nil, ---@type boolean
cooldown = nil, ---@type number
paused = nil, ---@type boolean
}
local function GetInteractionFunc(male, female)
local func = pairingFunctions[female.identifierClass][male.interactionsClass]
if func ~= nil then
return func
end
local identifier = female.identifier
local level = 0
local conflict = false
for key, value in pairs(male.interactions) do
if type(key) == "string" then
if identifier[key] then
if level < 1 then
func = value
level = 1
elseif level == 1 then
conflict = true
end
end
else
local match = true
for __, tableKey in ipairs(key) do
if not identifier[tableKey] then
match = false
break
end
end
if match then
if #key > level then
func = value
level = #key
conflict = false
elseif #key == level then
conflict = true
end
end
end
end
if conflict then
error("InteractionFunc ambiguous for actors with identifiers " .. Identifier2String(male.identifier) .. " and " .. Identifier2String(female.identifier) .. ".")
end
if func then
pairingFunctions[female.identifierClass][male.interactionsClass] = func
return func
else
pairingFunctions[female.identifierClass][male.interactionsClass] = false
return false
end
end
---@param whichPair Pair
local function AddPairToEveryStepList(whichPair)
whichPair[0x6] = lastEveryStepPair
whichPair[0x5] = nil
lastEveryStepPair[0x5] = whichPair
lastEveryStepPair = whichPair
numEveryStepPairs = numEveryStepPairs + 1
end
---@param whichPair Pair
local function RemovePairFromEveryStepList(whichPair)
if whichPair[0x6] == nil then
return
end
if whichPair[0x5] then
whichPair[0x5][0x6] = whichPair[0x6]
else
lastEveryStepPair = whichPair[0x6]
end
whichPair[0x6][0x5] = whichPair[0x5]
whichPair[0x6] = nil
numEveryStepPairs = numEveryStepPairs - 1
end
---@param actorA Actor
---@param actorB Actor
---@param interactionFunc function
---@return Pair | nil
local function CreatePair(actorA, actorB, interactionFunc)
if pairingExcluded[actorA][actorB] or interactionFunc == nil or actorA.host == actorB.host or actorA.originalAnchor == actorB.originalAnchor then
pairingExcluded[actorA][actorB] = true
pairingExcluded[actorB][actorA] = true
return nil
end
if (actorA.isSuspended or actorB.isSuspended) and not functionIsUnsuspendable[interactionFunc] then
return nil
end
local self ---@type Pair
if #unusedPairs == 0 then
self = {}
else
self = unusedPairs[#unusedPairs]
unusedPairs[#unusedPairs] = nil
end
self[0x1] = actorA
self[0x2] = actorB
self[0x3] = actorA.host
if actorB == SELF_INTERACTION_ACTOR then
self[0x4] = actorA.host
else
self[0x4] = actorB.host
end
self[0x8] = interactionFunc
local lastPair = actorA.lastPair
actorA.previousPair[self] = lastPair
actorA.nextPair[lastPair] = self
actorA.lastPair = self
lastPair = actorB.lastPair
actorB.previousPair[self] = lastPair
actorB.nextPair[lastPair] = self
actorB.lastPair = self
self.destructionQueued = nil
pairList[actorA][actorB] = self
if functionInitializer[interactionFunc] then
local tempPair = currentPair
currentPair = self
functionInitializer[interactionFunc](self[0x3], self[0x4])
currentPair = tempPair
end
if (functionPauseOnStationary[interactionFunc] and actorA.isStationary) then
if functionIsEveryStep[interactionFunc] then
self[0x7] = true
else
self[0x5] = DO_NOT_EVALUATE
end
self.paused = true
elseif functionIsEveryStep[interactionFunc] then
AddPairToEveryStepList(self)
self[0x7] = true
else
local firstStep
if functionDelay[interactionFunc] then
if functionDelayIsDistributed[interactionFunc] then
functionDelayCurrent[interactionFunc] = functionDelayCurrent[interactionFunc] + config.MIN_INTERVAL
if functionDelayCurrent[interactionFunc] > functionDelay[interactionFunc] then
functionDelayCurrent[interactionFunc] = functionDelayCurrent[interactionFunc] - functionDelay[interactionFunc]
end
firstStep = cycle.counter + (functionDelayCurrent[interactionFunc]*INV_MIN_INTERVAL + 1) // 1
else
firstStep = cycle.counter + (functionDelay[interactionFunc]*INV_MIN_INTERVAL + 1) // 1
end
else
firstStep = cycle.counter + 1
end
if firstStep > CYCLE_LENGTH then
firstStep = firstStep - CYCLE_LENGTH
end
numPairs[firstStep] = numPairs[firstStep] + 1
whichPairs[firstStep][numPairs[firstStep]] = self
self[0x5] = firstStep
self[0x6] = numPairs[firstStep]
end
return self
end
local function DestroyPair(self)
if self[0x7] then
RemovePairFromEveryStepList(self)
else
whichPairs[self[0x5]][self[0x6]] = DUMMY_PAIR
end
self[0x5] = nil
self[0x6] = nil
self.destructionQueued = true
local tempPair = currentPair
currentPair = self
if self.hadContact then
if functionOnReset[self[0x8]] and not cycle.isCrash then
functionOnReset[self[0x8]](self[0x3], self[0x4], self.userData, true)
end
self.hadContact = nil
end
if functionOnBreak[self[0x8]] and not cycle.isCrash then
functionOnBreak[self[0x8]](self[0x3], self[0x4], self.userData, true)
end
if functionOnDestroy[self[0x8]] and not cycle.isCrash then
functionOnDestroy[self[0x8]](self[0x3], self[0x4], self.userData)
end
if self.userData then
ReturnTable(self.userData)
end
currentPair = tempPair
--Reset ALICE_PairIsUnoccupied()
if self[0x2][self[0x8]] == self then
self[0x2][self[0x8]] = nil
end
if self[0x2] == SELF_INTERACTION_ACTOR then
self[0x1].selfInteractions[self[0x8]] = nil
end
local actorA = self[0x1]
local previous = actorA.previousPair
local next = actorA.nextPair
if next[self] then
previous[next[self]] = previous[self]
else
actorA.lastPair = previous[self]
end
next[previous[self]] = next[self]
previous[self] = nil
next[self] = nil
local actorB = self[0x2]
previous = actorB.previousPair
next = actorB.nextPair
if next[self] then
previous[next[self]] = previous[self]
else
actorB.lastPair = previous[self]
end
next[previous[self]] = next[self]
previous[self] = nil
next[self] = nil
unusedPairs[#unusedPairs + 1] = self
pairList[actorA][actorB] = nil
pairList[actorB][actorA] = nil
self.userData = nil
self.hadContact = nil
self[0x7] = nil
self.paused = nil
if self.cooldown then
ReturnTable(self.cooldown)
self.cooldown = nil
end
end
local function PausePair(self)
if self.destructionQueued then
return
end
if self[0x7] then
RemovePairFromEveryStepList(self)
else
if self[0x5] ~= DO_NOT_EVALUATE then
whichPairs[self[0x5]][self[0x6]] = DUMMY_PAIR
local nextStep = DO_NOT_EVALUATE
numPairs[nextStep] = numPairs[nextStep] + 1
whichPairs[nextStep][numPairs[nextStep]] = self
self[0x5] = nextStep
self[0x6] = numPairs[nextStep]
end
end
self.paused = true
end
local function UnpausePair(self)
if self.destructionQueued then
return
end
local actorA = self[0x1]
local actorB = self[0x2]
if self[0x7] then
if self[0x6] == nil and (not actorA.usesCells or not actorB.usesCells or SharesCellWith(actorA, actorB)) then
AddPairToEveryStepList(self)
end
else
if self[0x5] == DO_NOT_EVALUATE and (not actorA.usesCells or not actorB.usesCells or SharesCellWith(actorA, actorB)) then
local nextStep = cycle.counter + 1
if nextStep > CYCLE_LENGTH then
nextStep = nextStep - CYCLE_LENGTH
end
numPairs[nextStep] = numPairs[nextStep] + 1
whichPairs[nextStep][numPairs[nextStep]] = self
self[0x5] = nextStep
self[0x6] = numPairs[nextStep]
end
end
self.paused = nil
end
--#endregion
--===========================================================================================================================================================
--Actor Class
--===========================================================================================================================================================
local function GetUnusedActor()
local self
if #unusedActors == 0 then
--Actors have their own table recycling system. These fields do not get nilled on destroy.
self = {} ---@type Actor
self.isActor = true
self.identifier = {}
self.firstPair = {}
self.lastPair = self.firstPair
self.nextPair = {}
self.previousPair = {}
self.isInCell = {}
self.nextInCell = {}
self.previousInCell = {}
self.interactions = {}
self.selfInteractions = {}
self.references = {}
pairList[self] = {}
pairingExcluded[self] = {}
else
self = unusedActors[#unusedActors]
unusedActors[#unusedActors] = nil
end
return self
end
GetActor = function(object, keyword)
if object == nil then
if debug.selectedActor then
return debug.selectedActor
end
return nil
end
local actorOf = actorOf[object]
if actorOf then
if actorOf.isActor then
if keyword == nil or actorOf.identifier[keyword] then
return actorOf
else
return nil
end
elseif keyword == nil then
--If called within interactionFunc and keyword is not specified, prioritize returning actor that's in the current pair, then an actor for which the object is the
--host, not the anchor.
if currentPair ~= OUTSIDE_OF_CYCLE then
if object == currentPair[0x3] then
return currentPair[0x1]
elseif object == currentPair[0x4] then
return currentPair[0x2]
end
end
for __, actor in ipairs(actorOf) do
if actor.host == object then
return actor
end
end
return actorOf[1]
else
for __, actor in ipairs(actorOf) do
if actor.identifier[keyword] then
return actor
end
end
return nil
end
elseif type(object) == "table" and object.isActor then
return object
end
return nil
end
--#region Actor
---@class Actor
local Actor = {
--Main:
isActor = nil, ---@type boolean
host = nil, ---@type any
anchor = nil, ---@type any
originalAnchor = nil, ---@type any
getOwner = nil, ---@type function
interactions = nil, ---@type function | table | nil
selfInteractions = nil, ---@type table
identifier = nil, ---@type table
visualizer = nil, ---@type effect
alreadyDestroyed = nil, ---@type boolean
causedCrash = nil, ---@type boolean
isSuspended = nil, ---@type boolean
identifierClass = nil, ---@type string
interactionsClass = nil, ---@type string
references = nil, ---@type table
periodicPair = nil, ---@type Pair
--Pairs:
firstPair = nil, ---@type table
lastPair = nil, ---@type table
nextPair = nil, ---@type table
previousPair = nil, ---@type table
--Coordinates:
x = nil, ---@type table
y = nil, ---@type table
z = nil, ---@type table
lastX = nil, ---@type number
lastY = nil, ---@type number
zOffset = nil, ---@type number
--Flags:
priority = nil, ---@type integer
index = nil, ---@type integer
isStationary = nil, ---@type boolean
unique = nil, ---@type integer
bindToBuff = nil, ---@type string | nil
persistOnDeath = nil, ---@type boolean
unit = nil, ---@type unit | nil
waitingForBuff = nil, ---@type boolean
bindToOrder = nil, ---@type integer | nil
onDestroy = nil, ---@type function | nil
isUnselectable = nil, ---@type boolean
isAnonymous = nil, ---@type boolean
--Cell interaction:
isGlobal = nil, ---@type boolean
usesCells = nil, ---@type boolean
halfWidth = nil, ---@type number
halfHeight = nil, ---@type number
minX = nil, ---@type integer
minY = nil, ---@type integer
maxX = nil, ---@type integer
maxY = nil, ---@type integer
cellCheckInterval = nil, ---@type integer
nextCellCheck = nil, ---@type integer
positionInCellCheck = nil, ---@type integer
isInCell = nil, ---@type boolean[]
nextInCell = nil, ---@type Actor[]
previousInCell = nil, ---@type Actor[]
cellsVisualized = nil, ---@type boolean
cellVisualizers = nil, ---@type lightning[]
}
---@param host any
---@param identifier string | string[]
---@param interactions table | nil
---@param flags table
---@return Actor | nil
Create = function(host, identifier, interactions, flags)
local recycle, self, actorsOfClass
if flags.actorClass then
actorsOfClass = actorsOfActorClass[flags.actorClass]
if #actorsOfClass > 0 then
recycle = true
end
end
if not recycle then
local identifierType = type(identifier)
local tempIdentifier = GetTable()
if identifierType == "string" then
tempIdentifier[1] = identifier
elseif identifierType == "table" then
for i = 1, #identifier do
tempIdentifier[i] = identifier[i]
end
else
if identifier == nil then
error("Object identifier is nil.")
else
error("Object identifier must be string or table, but was " .. identifierType)
end
end
self = GetUnusedActor()
totalActors = totalActors + 1
self.unique = totalActors
self.causedCrash = nil
self.actorClass = flags.actorClass
self.host = host or EMPTY_TABLE
if flags.anchor then
local anchor = flags.anchor
while type(anchor) == "table" and anchor.anchor do
CreateReference(self, anchor)
anchor = anchor.anchor --Sup dawg, I heard you like anchors.
end
self.anchor = anchor
CreateReference(self, anchor)
if host then
CreateReference(self, host)
end
elseif host then
self.anchor = host
CreateReference(self, host)
end
self.originalAnchor = self.anchor
--Execute onCreation functions before flags are initialized.
for __, keyword in ipairs(tempIdentifier) do
if onCreation.funcs[keyword] then
for __, func in ipairs(onCreation.funcs[keyword]) do
func(self.host)
end
end
end
--Add additional flags from onCreation hooks.
for __, keyword in ipairs(tempIdentifier) do
if onCreation.flags[keyword] then
local onCreationFlags = onCreation.flags[keyword]
for key, __ in pairs(OVERWRITEABLE_FLAGS) do
if onCreationFlags[key] then
if type(onCreationFlags[key]) == "function" then
additionalFlags[key] = onCreationFlags[key](host)
else
additionalFlags[key] = onCreationFlags[key]
end
end
end
end
end
--Transform identifier sequence into hashmap.
local onCreationIdentifiers
for __, keyword in ipairs(tempIdentifier) do
self.identifier[keyword] = true
if onCreation.identifiers[keyword] then
onCreationIdentifiers = onCreationIdentifiers or GetTable()
for __, newIdentifier in ipairs(onCreation.identifiers[keyword]) do
if type(newIdentifier) == "function" then
onCreationIdentifiers[#onCreationIdentifiers + 1] = newIdentifier(self.host)
else
onCreationIdentifiers[#onCreationIdentifiers + 1] = newIdentifier
end
end
end
end
if onCreationIdentifiers then
for __, keyword in ipairs(onCreationIdentifiers) do
self.identifier[keyword] = true
end
end
--Copy interactions.
if interactions then
for keyword, func in pairs(interactions) do
if keyword ~= "self" then
self.interactions[keyword] = func
end
end
end
--Add additional interactions from onCreation hooks.
for keyword, __ in pairs(self.identifier) do
if onCreation.interactions[keyword] then
for target, func in pairs(onCreation.interactions[keyword]) do
self.interactions[target] = func
end
end
end
AssignActorClass(self, true, true)
self.zOffset = additionalFlags.zOffset or flags.zOffset or 0
SetOwnerFunc(self, host)
--Set or inherit stationary.
if objectIsStationary[self.anchor] then
self.isStationary = true
elseif additionalFlags.isStationary or flags.isStationary then
if not objectIsStationary[self.anchor] then
objectIsStationary[self.anchor] = true
if not actorOf[self.anchor].isActor then
for __, actor in ipairs(actorOf[self.anchor]) do
if actor ~= self then
SetStationary(actor, true)
end
end
end
end
self.isStationary = true
else
self.isStationary = nil
end
--Set coordinate getter functions.
SetCoordinateFuncs(self)
if flags.isAnonymous then
if interactions then
error("An anonymous actor cannot carry an interactions table. To add self-interactions, use the selfInteractions flag.")
end
self.isAnonymous = true
else
self.isAnonymous = nil
end
self.isGlobal = flags.isGlobal or (self.x == coord.globalXYZ or self.x == nil) or self.isAnonymous or nil
self.usesCells = not self.isGlobal and not additionalFlags.hasInfiniteRange and not flags.hasInfiniteRange
if not self.isGlobal then
self.lastX = self.x[self.anchor]
self.lastY = self.y[self.anchor]
end
self.priority = additionalFlags.priority or flags.priority or 0
--Pair with global actors or all actors if self is global.
local selfFunc, actorFunc
for __, actor in ipairs(self.usesCells and celllessActorList or actorList) do
selfFunc = GetInteractionFunc(self, actor)
actorFunc = GetInteractionFunc(actor, self)
if selfFunc and actorFunc then
if self.priority < actor.priority then
CreatePair(actor, self, actorFunc)
else
CreatePair(self, actor, selfFunc)
end
elseif selfFunc then
CreatePair(self, actor, selfFunc)
elseif actorFunc then
CreatePair(actor, self, actorFunc)
end
end
--Create self-interactions.
local selfInteractions = interactions and interactions.self or flags.selfInteractions
if selfInteractions then
if type(selfInteractions) == "table" then
for __, func in ipairs(selfInteractions) do
self.selfInteractions[func] = CreatePair(self, SELF_INTERACTION_ACTOR, func)
end
if self.actorClass then
self.orderedSelfInteractions = {}
for i = 1, #selfInteractions do
self.orderedSelfInteractions[i] = selfInteractions[i]
end
end
else
self.selfInteractions[selfInteractions] = CreatePair(self, SELF_INTERACTION_ACTOR, selfInteractions)
if self.actorClass then
self.orderedSelfInteractions = {selfInteractions}
end
end
self.interactions.self = nil
end
--Add additional self-interactions from onCreation hooks.
for __, keyword in ipairs(tempIdentifier) do
if onCreation.selfInteractions[keyword] then
for __, func in ipairs(onCreation.selfInteractions[keyword]) do
self.selfInteractions[func] = CreatePair(self, SELF_INTERACTION_ACTOR, func)
end
if self.actorClass then
for i = 1, #onCreation.selfInteractions[keyword] do
self.orderedSelfInteractions[#self.orderedSelfInteractions + 1] = onCreation.selfInteractions[keyword][i]
end
end
end
end
--Add additional self-interactions from onCreation hooks to onCreation identifiers.
if onCreationIdentifiers then
for __, keyword in ipairs(onCreationIdentifiers) do
if onCreation.selfInteractions[keyword] then
for __, func in ipairs(onCreation.selfInteractions[keyword]) do
self.selfInteractions[func] = CreatePair(self, SELF_INTERACTION_ACTOR, func)
end
if self.actorClass then
for i = 1, #onCreation.selfInteractions[keyword] do
self.orderedSelfInteractions[#self.orderedSelfInteractions + 1] = onCreation.selfInteractions[keyword][i]
end
end
end
end
ReturnTable(onCreationIdentifiers)
end
--Set actor size and initialize cells and cell checks.
if not self.isGlobal then
if flags.width then
self.halfWidth = flags.width/2
if flags.height then
self.halfHeight = flags.height/2
else
Warning("|cffff0000Warning:|r width flag set for actor, but not height flag.")
self.halfHeight = flags.width/2
end
else
local radius = additionalFlags.radius or flags.radius
if radius then
self.halfWidth = radius
self.halfHeight = radius
else
self.halfWidth = config.DEFAULT_OBJECT_RADIUS
self.halfHeight = config.DEFAULT_OBJECT_RADIUS
end
end
InitCells(self)
if self.isStationary then
self.nextCellCheck = DO_NOT_EVALUATE
local interval = additionalFlags.cellCheckInterval or flags.cellCheckInterval or config.DEFAULT_CELL_CHECK_INTERVAL
self.cellCheckInterval = min(MAX_STEPS, max(1, (interval*INV_MIN_INTERVAL) // 1 + 1))
else
InitCellChecks(self, additionalFlags.cellCheckInterval or flags.cellCheckInterval or config.DEFAULT_CELL_CHECK_INTERVAL)
end
end
--Create onDeath trigger.
self.persistOnDeath = flags.persistOnDeath
if (HandleType[self.anchor] == "destructable" or HandleType[self.anchor] == "item") and widgets.deathTriggers[self.anchor] == nil then
widgets.deathTriggers[self.anchor] = CreateTrigger()
TriggerRegisterDeathEvent(widgets.deathTriggers[self.anchor], self.anchor)
if HandleType[self.anchor] == "destructable" then
TriggerAddAction(widgets.deathTriggers[self.anchor], OnDestructableDeath)
else
TriggerAddAction(widgets.deathTriggers[self.anchor], OnItemDeath)
end
end
--Create binds.
if flags.bindToBuff then
CreateBinds(self, flags.bindToBuff, nil)
elseif flags.bindToOrder then
CreateBinds(self, nil, flags.bindToOrder)
end
--Misc.
self.onDestroy = additionalFlags.onActorDestroy or flags.onActorDestroy
if debug.visualizeAllActors and not self.isGlobal then
CreateVisualizer(self)
end
self.alreadyDestroyed = nil
actorList[#actorList + 1] = self
if not self.usesCells and not self.isAnonymous then
celllessActorList[#celllessActorList + 1] = self
end
self.index = #actorList
self.isUnselectable = additionalFlags.isUnselectable or flags.isUnselectable
for key, __ in pairs(additionalFlags) do
additionalFlags[key] = nil
end
ReturnTable(tempIdentifier)
else
self = actorsOfClass[#actorsOfClass]
actorsOfClass[#actorsOfClass] = nil
totalActors = totalActors + 1
self.unique = totalActors
self.causedCrash = nil
self.host = host or EMPTY_TABLE
if flags.anchor then
local anchor = flags.anchor
while type(anchor) == "table" and anchor.anchor do
CreateReference(self, anchor)
anchor = anchor.anchor --Sup dawg, I heard you like anchors.
end
self.anchor = anchor
CreateReference(self, anchor)
if host then
CreateReference(self, host)
end
elseif host then
self.anchor = host
CreateReference(self, host)
end
self.originalAnchor = self.anchor
SetOwnerFunc(self, host)
if not self.isGlobal then
self.lastX = self.x[self.anchor]
self.lastY = self.y[self.anchor]
end
--Pair with global actors or all actors if self is global.
local selfFunc, actorFunc
for __, actor in ipairs(self.usesCells and celllessActorList or actorList) do
selfFunc = GetInteractionFunc(self, actor)
actorFunc = GetInteractionFunc(actor, self)
if selfFunc and actorFunc then
if self.priority < actor.priority then
CreatePair(actor, self, actorFunc)
else
CreatePair(self, actor, selfFunc)
end
elseif selfFunc then
CreatePair(self, actor, selfFunc)
elseif actorFunc then
CreatePair(actor, self, actorFunc)
end
end
--Create self-interactions.
if self.orderedSelfInteractions then
for __, func in ipairs(self.orderedSelfInteractions) do
self.selfInteractions[func] = CreatePair(self, SELF_INTERACTION_ACTOR, func)
end
end
--Set actor size and initialize cells and cell checks.
if not self.isGlobal then
if flags.width then
self.halfWidth = flags.width/2
if flags.height then
self.halfHeight = flags.height/2
else
Warning("|cffff0000Warning:|r width flag set for actor, but not height flag.")
self.halfHeight = flags.width/2
end
else
local radius = additionalFlags.radius or flags.radius
if radius then
self.halfWidth = radius
self.halfHeight = radius
else
self.halfWidth = config.DEFAULT_OBJECT_RADIUS
self.halfHeight = config.DEFAULT_OBJECT_RADIUS
end
end
InitCells(self)
if self.isStationary then
self.nextCellCheck = DO_NOT_EVALUATE
local interval = additionalFlags.cellCheckInterval or flags.cellCheckInterval or config.DEFAULT_CELL_CHECK_INTERVAL
self.cellCheckInterval = min(MAX_STEPS, max(1, (interval*INV_MIN_INTERVAL) // 1 + 1))
else
InitCellChecks(self, additionalFlags.cellCheckInterval or flags.cellCheckInterval or config.DEFAULT_CELL_CHECK_INTERVAL)
end
end
--Create onDeath trigger.
if (HandleType[self.anchor] == "destructable" or HandleType[self.anchor] == "item") and widgets.deathTriggers[self.anchor] == nil then
widgets.deathTriggers[self.anchor] = CreateTrigger()
TriggerRegisterDeathEvent(widgets.deathTriggers[self.anchor], self.anchor)
if HandleType[self.anchor] == "destructable" then
TriggerAddAction(widgets.deathTriggers[self.anchor], OnDestructableDeath)
else
TriggerAddAction(widgets.deathTriggers[self.anchor], OnItemDeath)
end
end
--Create binds.
if self.bindToBuff then
CreateBinds(self, flags.bindToBuff, nil)
elseif self.bindToOrder then
CreateBinds(self, nil, flags.bindToOrder)
end
--Misc.
if debug.visualizeAllActors and not self.isGlobal then
CreateVisualizer(self)
end
self.alreadyDestroyed = nil
actorList[#actorList + 1] = self
if not self.usesCells and not self.isAnonymous then
celllessActorList[#celllessActorList + 1] = self
end
self.index = #actorList
end
return self
end
Destroy = function(self)
if self == nil or self.alreadyDestroyed then
return
end
self.alreadyDestroyed = true
destroyedActors[#destroyedActors + 1] = self
if self.onDestroy then
self.onDestroy(self.host)
if not self.actorClass then
self.onDestroy = nil
end
end
local next = self.nextPair
local pair = next[self.firstPair]
while pair do
pair.destructionQueued = true
pair = next[pair]
end
if self.index then
actorList[#actorList].index = self.index
actorList[self.index] = actorList[#actorList]
actorList[#actorList] = nil
end
if not self.usesCells and not self.isAnonymous then
for i, actor in ipairs(celllessActorList) do
if self == actor then
celllessActorList[i] = celllessActorList[#celllessActorList]
celllessActorList[#celllessActorList] = nil
break
end
end
end
if not self.isGlobal then
local nextCheck = self.nextCellCheck
if nextCheck ~= DO_NOT_EVALUATE then
local actorAtHighestPosition = cellCheckedActors[nextCheck][numCellChecks[nextCheck]]
actorAtHighestPosition.positionInCellCheck = self.positionInCellCheck
cellCheckedActors[nextCheck][self.positionInCellCheck] = actorAtHighestPosition
numCellChecks[nextCheck] = numCellChecks[nextCheck] - 1
end
for X = self.minX, self.maxX do
for Y = self.minY, self.maxY do
RemoveCell(CELL_LIST[X][Y], self)
end
end
end
if debug.visualizeAllActors and not self.isGlobal then
DestroyEffect(self.visualizer)
end
for object, __ in pairs(self.references) do
RemoveReference(self, object)
end
if self == debug.selectedActor then
DestroyEffect(self.visualizer)
debug.selectedActor = nil
end
end
Release = function(self)
for key, __ in pairs(pairingExcluded[self]) do
pairingExcluded[self][key] = nil
pairingExcluded[key][self] = nil
end
self.bindToBuff = nil
self.bindToOrder = nil
if self.cellsVisualized then
for __, bolt in ipairs(self.cellVisualizers) do
DestroyLightning(bolt)
end
self.cellsVisualized = nil
end
if self.host ~= EMPTY_TABLE then
self.host = nil
self.x[self.anchor] = nil
self.y[self.anchor] = nil
self.z[self] = nil
self.anchor = nil
end
if self.actorClass then
local actorsOfClass = actorsOfActorClass[self.actorClass]
actorsOfClass[#actorsOfClass + 1] = self
else
for key, __ in pairs(self.interactions) do
self.interactions[key] = nil
end
for key, __ in pairs(self.identifier) do
self.identifier[key] = nil
end
for key, __ in pairs(self.selfInteractions) do
self.selfInteractions[key] = nil
end
self.x = nil
self.y = nil
self.z = nil
unusedActors[#unusedActors + 1] = self
end
self.lastX = nil
self.lastY = nil
self.isSuspended = nil
end
---Create a reference to the actor. If more than one actor, transform into a table and store actors in a sequence.
CreateReference = function(self, object)
if actorOf[object] == nil then
actorOf[object] = self
elseif actorOf[object].isActor then
actorOf[object] = {actorOf[object], self}
else
actorOf[object][#actorOf[object] + 1] = self
end
self.references[object] = true
end
RemoveReference = function(self, object)
if actorOf[object].isActor then
actorOf[object] = nil
else
for j, v in ipairs(actorOf[object]) do
if self == v then
table.remove(actorOf[object], j)
end
end
if #actorOf[object] == 1 then
actorOf[object] = actorOf[object][1]
end
end
self.references[object] = nil
end
SetCoordinateFuncs = function(self)
if self.anchor ~= nil then
if IsHandle[self.anchor] then
local type = HandleType[self.anchor]
if type == "unit" then
self.x = coord.unitX
self.y = coord.unitY
self.z = coord.unitZ
elseif type == "destructable" then
self.x = coord.destructableX
self.y = coord.destructableY
self.z = coord.destructableZ
elseif type == "item" then
self.x = coord.itemX
self.y = coord.itemY
self.z = coord.itemZ
else
self.x = coord.globalXYZ
self.y = coord.globalXYZ
self.z = coord.globalXYZ
end
elseif self.anchor.x then
self.x = coord.classX
self.y = coord.classY
if self.anchor.z then
self.z = coord.classZ
else
self.z = coord.terrainZ
end
else
self.x = coord.globalXYZ
self.y = coord.globalXYZ
self.z = coord.globalXYZ
end
self.x[self.anchor] = nil
self.y[self.anchor] = nil
self.z[self] = nil
end
end
SetOwnerFunc = function(self, source)
if source then
if IsHandle[source] then
if HandleType[source] == "unit" then
local owner = GetOwningPlayer(source)
unitOwnerFunc[owner] = unitOwnerFunc[owner] or function() return owner end
self.getOwner = unitOwnerFunc[owner]
else
self.getOwner = DoNothing
end
elseif type(source) == "table" then
if type(source.owner) == "number" then
self.getOwner = GetClassOwnerById
elseif source.owner then
self.getOwner = GetClassOwner
else
self.getOwner = DoNothing
end
end
end
end
InitCells = function(self)
local x = self.x[self.anchor]
local y = self.y[self.anchor]
self.minX = min(NUM_CELLS_X, max(1, (NUM_CELLS_X*(x - self.halfWidth - MAP_MIN_X)/MAP_SIZE_X) // 1 + 1))
self.minY = min(NUM_CELLS_Y, max(1, (NUM_CELLS_Y*(y - self.halfHeight - MAP_MIN_Y)/MAP_SIZE_Y) // 1 + 1))
self.maxX = min(NUM_CELLS_X, max(1, (NUM_CELLS_X*(x + self.halfWidth - MAP_MIN_X)/MAP_SIZE_X) // 1 + 1))
self.maxY = min(NUM_CELLS_Y, max(1, (NUM_CELLS_Y*(y + self.halfHeight - MAP_MIN_Y)/MAP_SIZE_Y) // 1 + 1))
for key, __ in pairs(actorAlreadyChecked) do
actorAlreadyChecked[key] = nil
end
actorAlreadyChecked[self] = true
for X = self.minX, self.maxX do
for Y = self.minY, self.maxY do
EnterCell(CELL_LIST[X][Y], self)
end
end
end
InitCellChecks = function(self, interval)
self.cellCheckInterval = min(MAX_STEPS, max(1, (interval*INV_MIN_INTERVAL) // 1 + 1))
local nextStep = cycle.counter + self.cellCheckInterval
if nextStep > CYCLE_LENGTH then
nextStep = nextStep - CYCLE_LENGTH
end
numCellChecks[nextStep] = numCellChecks[nextStep] + 1
cellCheckedActors[nextStep][numCellChecks[nextStep]] = self
self.nextCellCheck = nextStep
self.positionInCellCheck = numCellChecks[nextStep]
end
AssignActorClass = function(self, doIdentifier, doInteractions)
--Concatenates all identifiers and interaction table keys to generate a unique string for an interactionFunc lookup table.
if doIdentifier then
local i = 1
local identifierClass = GetTable()
for id, __ in pairs(self.identifier) do
identifierClass[i] = id
i = i + 1
end
sort(identifierClass)
self.identifierClass = concat(identifierClass)
pairingFunctions[self.identifierClass] = pairingFunctions[self.identifierClass] or {}
ReturnTable(identifierClass)
end
if doInteractions then
local j = 1
local first, entry
local interactionsClass = GetTable()
for target, func in pairs(self.interactions) do
if type(target) == "string" then
if not functionKey[func] then
highestFunctionKey = highestFunctionKey + 1
functionKey[func] = highestFunctionKey
end
interactionsClass[j] = target .. functionKey[func]
elseif type(target) == "table" then
first = true
entry = ""
for __, subtarget in ipairs(target) do
if not first then
entry = entry .. "+"
else
first = false
end
if not functionKey[func] then
highestFunctionKey = highestFunctionKey + 1
functionKey[func] = highestFunctionKey
end
entry = entry .. subtarget
end
interactionsClass[j] = entry .. functionKey[func]
else
error("Interactions table key must be string or table, but was " .. type(target) .. ".")
end
j = j + 1
end
sort(interactionsClass)
self.interactionsClass = concat(interactionsClass)
ReturnTable(interactionsClass)
end
end
--Repair with all actors currently in interaction range.
Flicker = function(self)
if self.alreadyDestroyed then
return
end
if not self.isGlobal then
for key, __ in pairs(actorAlreadyChecked) do
actorAlreadyChecked[key] = nil
end
actorAlreadyChecked[self] = true
for cell, __ in pairs(self.isInCell) do
LeaveCell(cell, self)
end
InitCells(self)
if self.cellsVisualized then
RedrawCellVisualizers(self)
end
end
local selfFunc, actorFunc
for __, actor in ipairs(self.usesCells and celllessActorList or actorList) do
if not pairList[actor][self] and not pairList[self][actor] then
selfFunc = GetInteractionFunc(self, actor)
actorFunc = GetInteractionFunc(actor, self)
if selfFunc and actorFunc then
if self.priority < actor.priority then
CreatePair(actor, self, actorFunc)
else
CreatePair(self, actor, selfFunc)
end
elseif selfFunc then
CreatePair(self, actor, selfFunc)
elseif actorFunc then
CreatePair(actor, self, actorFunc)
end
end
end
end
SharesCellWith = function(self, actor)
if self.halfWidth < actor.halfWidth then
for cellA in pairs(self.isInCell) do
if actor.isInCell[cellA] then
return true
end
end
return false
else
for cellB in pairs(actor.isInCell) do
if self.isInCell[cellB] then
return true
end
end
return false
end
end
CreateBinds = function(self, bindToBuff, bindToOrder)
if bindToBuff then
self.bindToBuff = type(bindToBuff) == "number" and bindToBuff or FourCC(bindToBuff)
self.waitingForBuff = true
insert(widgets.bindChecks, self)
elseif bindToOrder then
self.bindToOrder = type(bindToOrder) == "number" and bindToOrder or OrderId(bindToOrder)
insert(widgets.bindChecks, self)
end
if HandleType[self.host] == "unit" then
self.unit = self.host
elseif HandleType[self.anchor] == "unit" then
self.unit = self.anchor
else
Warning("|cffff0000Warning:|r Attempted to bind actor with identifier " .. Identifier2String(self.identifier) .. " to a buff or order, but that actor doesn't have a unit host or anchor.")
end
end
DestroyObsoletePairs = function(self)
if self.alreadyDestroyed then
return
end
local actor
local next = self.nextPair
local pair = next[self.firstPair]
local thisPair
while pair do
if pair[0x2] ~= SELF_INTERACTION_ACTOR then
if self == pair[0x1] then
actor = pair[0x2]
else
actor = pair[0x1]
end
if not actor.alreadyDestroyed and not GetInteractionFunc(self, actor) and not GetInteractionFunc(actor, self) then
thisPair = pair
pair = next[pair]
if not thisPair.destructionQueued then
thisPair.destructionQueued = true
AddDelayedCallback(DestroyPair, thisPair)
end
else
pair = next[pair]
end
else
pair = next[pair]
end
end
end
Unpause = function(self, whichFunctions)
local actorA, actorB, nextStep
local DO_NOT_EVALUATE = DO_NOT_EVALUATE
local CYCLE_LENGTH = CYCLE_LENGTH
local next = self.nextPair
local pair = next[self.firstPair]
while pair do
if whichFunctions == nil or whichFunctions[pair[0x8]] then
actorA = pair[0x1]
actorB = pair[0x2]
if pair[0x7] then
if pair[0x6] == nil and (not actorA.usesCells or not actorB.usesCells or SharesCellWith(actorA, actorB)) then
AddPairToEveryStepList(pair)
end
else
if pair[0x5] == DO_NOT_EVALUATE and (not actorA.usesCells or not actorB.usesCells or SharesCellWith(actorA, actorB)) then
nextStep = cycle.counter + 1
if nextStep > CYCLE_LENGTH then
nextStep = nextStep - CYCLE_LENGTH
end
numPairs[nextStep] = numPairs[nextStep] + 1
whichPairs[nextStep][numPairs[nextStep]] = pair
pair[0x5] = nextStep
pair[0x6] = numPairs[nextStep]
end
end
pair.paused = nil
end
pair = next[pair]
end
end
SetStationary = function(self, enable)
if (self.isStationary == true) == enable then
return
end
self.isStationary = enable
if enable then
local nextCheck = self.nextCellCheck
local actorAtHighestPosition = cellCheckedActors[nextCheck][numCellChecks[nextCheck]]
actorAtHighestPosition.positionInCellCheck = self.positionInCellCheck
cellCheckedActors[nextCheck][self.positionInCellCheck] = actorAtHighestPosition
numCellChecks[nextCheck] = numCellChecks[nextCheck] - 1
self.nextCellCheck = DO_NOT_EVALUATE
local next = self.nextPair
local thisPair = next[self.firstPair]
while thisPair do
if functionPauseOnStationary[thisPair[0x8]] and self == thisPair[0x1] then
AddDelayedCallback(PausePair, thisPair)
end
thisPair = next[thisPair]
end
else
local nextStep = cycle.counter + 1
if nextStep > CYCLE_LENGTH then
nextStep = nextStep - CYCLE_LENGTH
end
numCellChecks[nextStep] = numCellChecks[nextStep] + 1
cellCheckedActors[nextStep][numCellChecks[nextStep]] = self
self.nextCellCheck = nextStep
self.positionInCellCheck = numCellChecks[nextStep]
AddDelayedCallback(Unpause, self, functionPauseOnStationary)
end
SetCoordinateFuncs(self)
end
---For debug mode.
VisualizeCells = function(self, enable)
if enable == self.cellsVisualized then
return
end
if self.cellsVisualized then
self.cellsVisualized = false
DestroyLightning(self.cellVisualizers[1])
DestroyLightning(self.cellVisualizers[2])
DestroyLightning(self.cellVisualizers[3])
DestroyLightning(self.cellVisualizers[4])
elseif not self.isGlobal then
self.cellVisualizers = {}
self.cellsVisualized = true
local minx = CELL_MIN_X[self.minX]
local miny = CELL_MIN_Y[self.minY]
local maxx = CELL_MAX_X[self.maxX]
local maxy = CELL_MAX_Y[self.maxY]
self.cellVisualizers[1] = AddLightning("LEAS", false, maxx, miny, maxx, maxy)
self.cellVisualizers[2] = AddLightning("LEAS", false, maxx, maxy, minx, maxy)
self.cellVisualizers[3] = AddLightning("LEAS", false, minx, maxy, minx, miny)
self.cellVisualizers[4] = AddLightning("LEAS", false, minx, miny, maxx, miny)
end
end
---For debug mode.
RedrawCellVisualizers = function(self)
local minx = CELL_MIN_X[self.minX]
local miny = CELL_MIN_Y[self.minY]
local maxx = CELL_MAX_X[self.maxX]
local maxy = CELL_MAX_Y[self.maxY]
MoveLightning(self.cellVisualizers[1], false, maxx, miny, maxx, maxy)
MoveLightning(self.cellVisualizers[2], false, maxx, maxy, minx, maxy)
MoveLightning(self.cellVisualizers[3], false, minx, maxy, minx, miny)
MoveLightning(self.cellVisualizers[4], false, minx, miny, maxx, miny)
end
---Called when an object is loaded into a transport actor crashes a thread.
Suspend = function(self, enable)
if enable then
local pair
for __, actor in ipairs(actorList) do
pair = pairList[actor][self] or pairList[self][actor]
if pair and not functionIsUnsuspendable[pair[0x8]] then
DestroyPair(pair)
end
end
self.isSuspended = true
else
self.isSuspended = false
AddDelayedCallback(Flicker, self)
end
end
---For debug mode.
Deselect = function(self)
debug.selectedActor = nil
if not debug.visualizeAllActors then
DestroyEffect(self.visualizer)
end
VisualizeCells(self, false)
BlzFrameSetVisible(debug.tooltip, false)
end
GetMissingRequiredFieldsString = function(self, func, isMaleInFunc, isFemaleInFunc)
local description = ""
if functionRequiredFields[func] then
local first = true
if functionRequiredFields[func] then
if isMaleInFunc and functionRequiredFields[func].male then
for field, value in pairs(functionRequiredFields[func].male) do
if not self.host[field] and (value == true or self.host[value]) then
if first then
first = false
else
description = description .. ", "
end
description = description .. field
end
end
end
if isFemaleInFunc and functionRequiredFields[func].female then
for field, value in pairs(functionRequiredFields[func].female) do
if not self.host[field] and (value == true or self.host[value]) then
if first then
first = false
else
description = description .. ", "
end
description = description .. field
end
end
end
end
end
return description
end
---For debug mode.
GetDescription = function(self)
local description = setmetatable({}, {__add = function(old, new) old[#old + 1] = new return old end})
description = description + "|cffffcc00Identifiers:|r "
local first = true
for key, __ in pairs(self.identifier) do
if not first then
description = description + ", "
else
first = false
end
description = description + key
end
if self.host ~= EMPTY_TABLE then
description = description + "\n\n|cffffcc00Host:|r " + Object2String(self.host)
end
if self.originalAnchor ~= self.host then
description = description + "\n|cffffcc00Anchor:|r " + Object2String(self.originalAnchor)
end
description = description + "\n|cffffcc00Interactions:|r "
first = true
for key, func in pairs(self.interactions) do
if not first then
description = description + ", "
end
if type(key) == "string" then
description = description + key + " - " + Function2String(func)
else
local subFirst = true
for __, word in ipairs(key) do
if not subFirst then
description = description + " + "
end
description = description + word
subFirst = false
end
description = description + " - " + Function2String(func)
end
first = false
end
if next(self.selfInteractions) then
description = description + "\n|cffffcc00Self-Interactions:|r "
first = true
for key, __ in pairs(self.selfInteractions) do
if not first then
description = description + ", "
end
first = false
description = description + Function2String(key)
end
end
if self.priority ~= 0 then
description = description + "\n|cffffcc00Priority:|r " + self.priority
end
if self.zOffset ~= 0 then
description = description + "\n|cffffcc00Z-Offset:|r " + self.zOffset
end
if self.cellCheckInterval and math.abs(self.cellCheckInterval*config.MIN_INTERVAL - config.DEFAULT_CELL_CHECK_INTERVAL) > 0.001 then
description = description + "\n|cffffcc00Cell Check Interval:|r " + self.cellCheckInterval*config.MIN_INTERVAL
end
if self.isStationary then
description = description + "\n|cffffcc00Stationary:|r true"
end
if not self.isGlobal and not self.usesCells then
description = description + "\n|cffffcc00Has infinite range:|r true"
end
if self.halfWidth and self.halfWidth ~= config.DEFAULT_OBJECT_RADIUS then
if self.halfWidth ~= self.halfHeight then
description = description + "\n|cffffcc00Width:|r " + 2*self.halfWidth
description = description + "\n|cffffcc00Height:|r " + 2*self.halfHeight
else
description = description + "\n|cffffcc00Radius:|r " + self.halfWidth
end
end
if self.getOwner(self.host) then
description = description + "\n|cffffcc00Owner:|r Player " + (GetPlayerId(self.getOwner(self.host)) + 1)
end
if self.bindToBuff then
description = description + "\n|cffffcc00Bound to buff:|r " + string.pack(">I4", self.bindToBuff)
if self.waitingForBuff then
description = description + " |cffaaaaaa(waiting for buff to be applied)|r"
end
end
if self.bindToOrder then
description = description + "\n|cffffcc00Bound to order:|r " + OrderId2String(self.bindToOrder) + " |cffaaaaaa(current order = " + OrderId2String(GetUnitCurrentOrder(self.anchor)) + ")|r"
end
if self.onDestroy then
description = description + "\n|cffffcc00On Destroy:|r " + Function2String(self.onDestroy)
end
description = description + "\n\n|cffffcc00Unique Number:|r " + self.unique
local numOutgoing = 0
local numIncoming = 0
local hasError = false
local outgoingFuncs = {}
local incomingFuncs = {}
local funcs = {}
local isMaleInFunc = {}
local isFemaleInFunc = {}
local nextPair = self.nextPair
local pair = nextPair[self.firstPair]
while pair do
if pair[0x2] ~= SELF_INTERACTION_ACTOR then
if pair[0x1] == self then
numOutgoing = numOutgoing + 1
outgoingFuncs[pair[0x8]] = (outgoingFuncs[pair[0x8]] or 0) + 1
elseif pair[0x2] == self then
numIncoming = numIncoming + 1
incomingFuncs[pair[0x8]] = (incomingFuncs[pair[0x8]] or 0) + 1
else
hasError = true
end
end
funcs[pair[0x8]] = true
if pair[0x1] == self then
isMaleInFunc[pair[0x8]] = true
else
isFemaleInFunc[pair[0x8]] = true
end
pair = nextPair[pair]
end
description = description + "\n|cffffcc00Outgoing pairs:|r " + numOutgoing
if numOutgoing > 0 then
first = true
description = description + "|cffaaaaaa ("
for key, number in pairs(outgoingFuncs) do
if not first then
description = description + ", |r"
end
description = description + "|cffffcc00" + number + "|r |cffaaaaaa" + Function2String(key)
first = false
end
description = description + ")|r"
end
description = description + "\n|cffffcc00Incoming pairs:|r " + numIncoming
if numIncoming > 0 then
first = true
description = description + "|cffaaaaaa ("
for key, number in pairs(incomingFuncs) do
if not first then
description = description + ", |r"
end
description = description + "|cffffcc00" + number + "|r |cffaaaaaa" + Function2String(key)
first = false
end
description = description + ")|r"
end
if not self.isGlobal then
local x, y = self.x[self.anchor], self.y[self.anchor]
description = description + "\n\n|cffffcc00x:|r " + x
description = description + "\n|cffffcc00y:|r " + y
description = description + "\n|cffffcc00z:|r " + self.z[self]
end
if hasError then
description = description + "\n\n|cffff0000DESYNCED PAIR DETECTED!|r"
end
if self.causedCrash then
description = description + "\n\n|cffff0000CAUSED CRASH!|r"
end
if type(self.host) == "table" then
first = true
local requiredFieldString
for func, __ in pairs(funcs) do
requiredFieldString = GetMissingRequiredFieldsString(self, func, isMaleInFunc[func], isFemaleInFunc[func])
if requiredFieldString ~= "" then
if first then
description = description + "\n\n|cffff0000Missing required fields:|r"
first = false
end
description = description + "\n" + requiredFieldString + " |cffaaaaaa(" + Function2String(func) + ")|r"
end
end
end
if next(debug.trackedVariables) then
description = description + "\n\n|cffff0000Tracked variables:|r"
for key, __ in pairs(debug.trackedVariables) do
if type(self.host) == "table" and self.host[key] then
description = description + "\n|cffffcc00" + key + "|r: " + tostring(self.host[key])
end
if self.host ~= self.anchor and type(self.anchor) == "table" and self.anchor[key] then
description = description + "\n|cffffcc00" + key + "|r: " + tostring(self.host[key])
end
if _G[key] then
if _G[key][self.host] then
description = description + "\n|cffffcc00" + key + "|r: " + tostring(_G[key][self.host])
end
if self.host ~= self.anchor and _G[key][self.anchor] then
description = description + "\n|cffffcc00" + key + "|r: " + tostring(_G[key][self.anchor])
end
end
end
end
local str = concat(description)
if self.isGlobal then
return str, "|cff00bb00Global Actor|r"
else
if numOutgoing == 0 and numIncoming == 0 then
return str, "|cffaaaaaaUnpaired Actor|r"
elseif numOutgoing == 0 then
return str, "|cffffc0cbFemale Actor|r"
elseif numIncoming == 0 then
return str, "|cff90b5ffMale Actor|r"
else
return str, "|cffffff00Hybrid Actor|r"
end
end
end
---For debug mode.
Select = function(self)
debug.selectedActor = self
local description, title = GetDescription(self)
BlzFrameSetText(debug.tooltipText, description)
BlzFrameSetText(debug.tooltipTitle, title )
BlzFrameSetSize(debug.tooltipText, 0.28, 0.0)
BlzFrameSetSize(debug.tooltip, 0.29, BlzFrameGetHeight(debug.tooltipText) + 0.0315)
BlzFrameSetVisible(debug.tooltip, true)
if not self.isGlobal then
VisualizeCells(self, true)
if not self.isGlobal and not debug.visualizeAllActors then
CreateVisualizer(self)
end
end
end
---For debug mode.
CreateVisualizer = function(self)
local x, y = self.x[self.anchor], self.y[self.anchor]
self.visualizer = AddSpecialEffect("Abilities\\Spells\\Other\\Aneu\\AneuTarget.mdl", x, y)
BlzSetSpecialEffectColorByPlayer(self.visualizer, self.getOwner(self.host) or Player(21))
BlzSetSpecialEffectZ(self.visualizer, self.z[self] + 75)
end
--#endregion
--Stub actors are used by periodic callers.
CreateStub = function(host)
local actor = GetUnusedActor()
actor.host = host
actor.anchor = host
actor.isGlobal = true
actor.x = coord.globalXYZ
actor.y = coord.globalXYZ
actor.z = coord.globalXYZ
actor.unique = 0
actor.alreadyDestroyed = nil
actorOf[host] = actor
return actor
end
DestroyStub = function(self)
if self == nil or self.alreadyDestroyed then
return
end
self.periodicPair = nil
actorOf[self.host] = nil
self.host = nil
self.anchor = nil
self.isGlobal = nil
self.x = nil
self.y = nil
self.z = nil
self.alreadyDestroyed = true
unusedActors[#unusedActors + 1] = self
end
local SetFlag = {
radius = function(self, radius)
self.halfWidth = radius
self.halfHeight = radius
AddDelayedCallback(Flicker, self)
end,
anchor = function(self, anchor)
if anchor == self.originalAnchor then
return
end
for object, __ in pairs(self.references) do
if object ~= self.host then
RemoveReference(self, object)
end
end
if anchor then
while type(anchor) == "table" and anchor.anchor do
CreateReference(self, anchor)
anchor = anchor.anchor
end
self.anchor = anchor
self.originalAnchor = self.anchor
CreateReference(self, anchor)
SetCoordinateFuncs(self)
else
local oldAnchor = self.anchor
self.anchor = self.host
self.originalAnchor = self.anchor
if type(self.host) == "table" then
self.host.x, self.host.y, self.host.z = ALICE_GetCoordinates3D(oldAnchor)
end
SetCoordinateFuncs(self)
end
AddDelayedCallback(Flicker, self)
end,
width = function(self, width)
self.halfWidth = width/2
AddDelayedCallback(Flicker, self)
end,
height = function(self, height)
self.halfHeight = height/2
AddDelayedCallback(Flicker, self)
end,
cellCheckInterval = function(self, cellCheckInterval)
self.cellCheckInterval = min(MAX_STEPS, max(1, (cellCheckInterval*INV_MIN_INTERVAL) // 1 + 1))
end,
onActorDestroy = function(self, onActorDestroy)
self.onActorDestroy = onActorDestroy
end,
isUnselectable = function(self, isUnselectable)
self.isUnselectable = isUnselectable
end,
persistOnDeath = function(self, persistOnDeath)
self.persistOnDeath = persistOnDeath
end,
priority = function(self, priority)
self.priority = priority
end,
zOffset = function(self, zOffset)
self.zOffset = zOffset
self.z[self] = nil
end,
bindToBuff = function(self, buff)
if self.bindToBuff then
self.bindToBuff = type(buff) == "number" and buff or FourCC(buff)
else
CreateBinds(self, buff, nil)
end
end,
bindToOrder = function(self, order)
if self.bindToOrder then
self.bindToorder = type(order) == "number" and order or OrderId(order)
else
CreateBinds(self, nil, order)
end
end
}
--===========================================================================================================================================================
--Cell Class
--===========================================================================================================================================================
--#region Cell
---@class Cell
local Cell = {
horizontalLightning = nil, ---@type lightning
verticalLightning = nil, ---@type lightning
first = nil, ---@type Actor
last = nil, ---@type Actor
numActors = nil ---@type integer
}
EnterCell = function(self, actorA)
local aFunc, bFunc
actorA.isInCell[self] = true
actorA.nextInCell[self] = nil
actorA.previousInCell[self] = self.last
if self.first == nil then
self.first = actorA
else
self.last.nextInCell[self] = actorA
end
self.last = actorA
if actorA.hasInfiniteRange then
return
end
local DO_NOT_EVALUATE = DO_NOT_EVALUATE
local CYCLE_LENGTH = CYCLE_LENGTH
local actorB = self.first
for __ = 1, self.numActors do
if not actorAlreadyChecked[actorB] then
actorAlreadyChecked[actorB] = true
local thisPair = pairList[actorA][actorB] or pairList[actorB][actorA]
if thisPair then
if not thisPair.paused then
if thisPair[0x7] then
if thisPair[0x6] == nil then
AddPairToEveryStepList(thisPair)
end
elseif thisPair[0x5] == DO_NOT_EVALUATE then
local nextStep
if functionDelay[0x8] then
local interactionFunc = thisPair[0x8]
if functionDelayIsDistributed[interactionFunc] then
functionDelayCurrent[interactionFunc] = functionDelay[interactionFunc] + config.MIN_INTERVAL
if functionDelayCurrent[interactionFunc] > functionDelay[interactionFunc] then
functionDelayCurrent[interactionFunc] = functionDelayCurrent[interactionFunc] - functionDelay[interactionFunc]
end
nextStep = cycle.counter + (functionDelayCurrent[interactionFunc]*INV_MIN_INTERVAL + 1) // 1
else
nextStep = cycle.counter + (functionDelay[interactionFunc]*INV_MIN_INTERVAL + 1) // 1
end
else
nextStep = cycle.counter + 1
end
if nextStep > CYCLE_LENGTH then
nextStep = nextStep - CYCLE_LENGTH
end
numPairs[nextStep] = numPairs[nextStep] + 1
whichPairs[nextStep][numPairs[nextStep]] = thisPair
thisPair[0x5] = nextStep
thisPair[0x6] = numPairs[nextStep]
end
end
elseif not pairingExcluded[actorA][actorB] then
aFunc = GetInteractionFunc(actorA, actorB)
if aFunc then
if actorA.priority < actorB.priority then
bFunc = GetInteractionFunc(actorB, actorA)
if bFunc then
CreatePair(actorB, actorA, bFunc)
else
CreatePair(actorA, actorB, aFunc)
end
else
CreatePair(actorA, actorB, aFunc)
end
else
bFunc = GetInteractionFunc(actorB, actorA)
if bFunc then
CreatePair(actorB, actorA, bFunc)
end
end
end
end
actorB = actorB.nextInCell[self]
end
self.numActors = self.numActors + 1
end
RemoveCell = function(self, actorA)
if self.first == actorA then
self.first = actorA.nextInCell[self]
else
actorA.previousInCell[self].nextInCell[self] = actorA.nextInCell[self]
end
if self.last == actorA then
self.last = actorA.previousInCell[self]
else
actorA.nextInCell[self].previousInCell[self] = actorA.previousInCell[self]
end
actorA.isInCell[self] = nil
self.numActors = self.numActors - 1
end
LeaveCell = function(self, actorA, wasLoaded)
RemoveCell(self, actorA)
if actorA.hasInfiniteRange then
return
end
local DO_NOT_EVALUATE = DO_NOT_EVALUATE
local actorB = self.first
for __ = 1, self.numActors do
if not actorAlreadyChecked[actorB] then
actorAlreadyChecked[actorB] = true
local thisPair = pairList[actorA][actorB] or pairList[actorB][actorA]
if thisPair then
if thisPair[0x7] then
if thisPair[0x6] and (((actorA.maxX < actorB.minX or actorA.minX > actorB.maxX or actorA.maxY < actorB.minY or actorA.minY > actorB.maxY) and not functionIsUnbreakable[thisPair[0x8]]) or wasLoaded) and actorB.usesCells then
RemovePairFromEveryStepList(thisPair)
if thisPair.hadContact then
if functionOnReset[thisPair[0x8]] and not cycle.isCrash then
local tempPair = currentPair
currentPair = thisPair
functionOnReset[thisPair[0x8]](thisPair[0x3], thisPair[0x4], thisPair.userData, false)
currentPair = tempPair
end
thisPair.hadContact = nil
end
if functionOnBreak[thisPair[0x8]] then
local tempPair = currentPair
currentPair = thisPair
functionOnBreak[thisPair[0x8]](thisPair[0x3], thisPair[0x4], thisPair.userData, false)
currentPair = tempPair
end
end
elseif thisPair[0x5] ~= DO_NOT_EVALUATE and (actorA.maxX < actorB.minX or actorA.minX > actorB.maxX or actorA.maxY < actorB.minY or actorA.minY > actorB.maxY) and not functionIsUnbreakable[thisPair[0x8]] and actorB.usesCells then
whichPairs[thisPair[0x5]][thisPair[0x6]] = DUMMY_PAIR
local nextStep = DO_NOT_EVALUATE
numPairs[nextStep] = numPairs[nextStep] + 1
whichPairs[nextStep][numPairs[nextStep]] = thisPair
thisPair[0x5] = nextStep
thisPair[0x6] = numPairs[nextStep]
if thisPair.hadContact then
if functionOnReset[thisPair[0x8]] and not cycle.isCrash then
local tempPair = currentPair
currentPair = thisPair
functionOnReset[thisPair[0x8]](thisPair[0x3], thisPair[0x4], thisPair.userData, false)
currentPair = tempPair
end
thisPair.hadContact = nil
end
if functionOnBreak[thisPair[0x8]] then
local tempPair = currentPair
currentPair = thisPair
functionOnBreak[thisPair[0x8]](thisPair[0x3], thisPair[0x4], thisPair.userData, false)
currentPair = tempPair
end
end
end
end
actorB = actorB.nextInCell[self]
end
end
--#endregion
--===========================================================================================================================================================
--Repair
--===========================================================================================================================================================
--#region Repair
local function RepairCycle(firstPosition)
local numSteps
local nextStep
--Variable Step Cycle
local returnValue
local pairsThisStep = whichPairs[cycle.counter]
for i = firstPosition, numPairs[cycle.counter] do
currentPair = pairsThisStep[i]
if currentPair.destructionQueued then
if currentPair ~= DUMMY_PAIR then
nextStep = cycle.counter + MAX_STEPS
if nextStep > CYCLE_LENGTH then
nextStep = nextStep - CYCLE_LENGTH
end
numPairs[nextStep] = numPairs[nextStep] + 1
whichPairs[nextStep][numPairs[nextStep]] = currentPair
currentPair[0x5] = nextStep
currentPair[0x6] = numPairs[nextStep]
end
else
returnValue = currentPair[0x8](currentPair[0x3], currentPair[0x4])
if returnValue then
numSteps = (returnValue*INV_MIN_INTERVAL + 1) // 1 --convert seconds to steps, then ceil.
if numSteps < 1 then
numSteps = 1
elseif numSteps > MAX_STEPS then
numSteps = MAX_STEPS
end
nextStep = cycle.counter + numSteps
if nextStep > CYCLE_LENGTH then
nextStep = nextStep - CYCLE_LENGTH
end
numPairs[nextStep] = numPairs[nextStep] + 1
whichPairs[nextStep][numPairs[nextStep]] = currentPair
currentPair[0x5] = nextStep
currentPair[0x6] = numPairs[nextStep]
else
AddPairToEveryStepList(currentPair)
functionIsEveryStep[currentPair[0x8]] = true
currentPair[0x7] = true
end
end
end
numPairs[cycle.counter] = 0
currentPair = OUTSIDE_OF_CYCLE
end
local function PrintCrashMessage(crashingPairOrCallback, A, B)
local warning
if A then
if B == SELF_INTERACTION_ACTOR then
if A.periodicPair then
warning = "\n|cffff0000Error:|r ALICE Cycle crashed during last execution. The crash occured during a periodic callback with function |cffaaaaff" .. Function2String(crashingPairOrCallback[0x8]) .. "|r. The interaction has been disabled."
else
warning = "\n|cffff0000Error:|r ALICE Cycle crashed during last execution. The identifier of the actor responsible is "
.. "|cffffcc00" .. Identifier2String(A.identifier) .. "|r. Unique number: " .. A.unique .. ". The crash occured during self-interaction with function |cffaaaaff" .. Function2String(crashingPairOrCallback[0x8]) .. "|r. The interaction has been disabled."
end
else
warning = "\n|cffff0000Error:|r ALICE Cycle crashed during last execution. The identifiers of the actors responsible are "
.. "|cffffcc00" .. Identifier2String(A.identifier) .. "|r and |cffaaaaff" .. Identifier2String(B.identifier) .. "|r. Unique numbers: " .. A.unique .. ", " .. B.unique .. ". The pair has been removed from the cycle."
end
if type(A.host) == "table" then
local requiredFieldString = GetMissingRequiredFieldsString(A, crashingPairOrCallback[0x8], true, false)
if requiredFieldString ~= "" then
warning = warning .. "\n\nThere are one or more |cffff0000required fields missing|r for the interaction function in the host table of actor " .. A.unique .. ":\n|cffaaaaff" .. requiredFieldString .. "|r"
end
end
if type(B.host) == "table" then
local requiredFieldString = GetMissingRequiredFieldsString(B, crashingPairOrCallback[0x8], false, true)
if requiredFieldString ~= "" then
warning = warning .. "\n\nThere are one or more |cffff0000required fields missing|r for the interaction function in the host table of actor " .. B.unique .. ":\n|cffaaaaff" .. requiredFieldString .. "|r"
end
end
else
warning = "\n|cffff0000Error:|r ALICE Cycle crashed during last execution. The crash occured during a delayed callback with function |cffaaaaff" .. Function2String(crashingPairOrCallback.callback) .. "|r."
end
Warning(warning)
end
local function OnCrash()
local crashingPair = currentPair
--Remove pair and continue with cycle after the crashing pair.
if crashingPair ~= OUTSIDE_OF_CYCLE then
local A = crashingPair[0x1]
local B = crashingPair[0x2]
pairingExcluded[A][B] = true
pairingExcluded[B][A] = true
PrintCrashMessage(crashingPair, A, B)
if not crashingPair[0x7] then
local nextPosition = crashingPair[0x6] + 1
numPairs[DO_NOT_EVALUATE] = numPairs[DO_NOT_EVALUATE] + 1
whichPairs[DO_NOT_EVALUATE][numPairs[DO_NOT_EVALUATE]] = crashingPair
crashingPair[0x5] = DO_NOT_EVALUATE
crashingPair[0x6] = numPairs[DO_NOT_EVALUATE]
cycle.isCrash = true
DestroyPair(crashingPair)
RepairCycle(nextPosition)
else
cycle.isCrash = true
DestroyPair(crashingPair)
end
--If this is the second time the same actor caused a crash, isolate it to prevent it from causing further crashes.
if A.causedCrash then
Warning("\nActor with identifier " .. Identifier2String(A.identifier) .. ", unique number: " .. A.unique .. " is repeatedly causing crashes. Isolating...")
Suspend(A)
elseif B.causedCrash and B ~= SELF_INTERACTION_ACTOR then
Warning("\nActor with identifier " .. Identifier2String(B.identifier) .. ", unique number: " .. B.unique .. " is repeatedly causing crashes. Isolating...")
Suspend(B)
end
A.causedCrash = true
B.causedCrash = true
elseif ALICE_Where == "callbacks" then
local crashingCallback = userCallbacks.first
PrintCrashMessage(crashingCallback)
RemoveUserCallbackFromList(crashingCallback)
while userCallbacks.first and userCallbacks.first.callCounter == cycle.unboundCounter do
ExecuteUserCallback(userCallbacks.first)
end
else
Warning("\n|cffff0000Error:|r ALICE Cycle crashed during last execution. The crash occured during " .. ALICE_Where .. ".")
end
cycle.isCrash = false
end
--#endregion
--===========================================================================================================================================================
--Main Functions
--===========================================================================================================================================================
--#region Main Functions
local function ResetCoordinateLookupTables()
local classX, classY = coord.classX, coord.classY
for key, __ in pairs(classX) do
classX[key], classY[key] = nil, nil
end
local classZ = coord.classZ
for key, __ in pairs(classZ) do
classZ[key] = nil
end
local unitX, unitY = coord.unitX, coord.unitY
for key, __ in pairs(unitX) do
unitX[key], unitY[key] = nil, nil
end
local unitZ = coord.unitZ
for key, __ in pairs(unitZ) do
unitZ[key] = nil
end
if not config.ITEMS_ARE_STATIONARY then
local itemX, itemY = coord.itemX, coord.itemY
for key, __ in pairs(itemX) do
itemX[key], itemY[key] = nil, nil
end
local itemZ = coord.itemZ
for key, __ in pairs(itemZ) do
itemZ[key] = nil
end
end
local terrainZ = coord.terrainZ
for key, __ in pairs(terrainZ) do
terrainZ[key] = nil
end
end
local function BindChecks()
for i = #widgets.bindChecks, 1, -1 do
local actor = widgets.bindChecks[i]
if actor.bindToBuff then
if actor.waitingForBuff then
if GetUnitAbilityLevel(actor.unit, FourCC(actor.bindToBuff)) > 0 then
actor.waitingForBuff = nil
end
elseif GetUnitAbilityLevel(actor.unit, FourCC(actor.bindToBuff)) == 0 then
Destroy(actor)
widgets.bindChecks[i] = widgets.bindChecks[#widgets.bindChecks]
widgets.bindChecks[#widgets.bindChecks] = nil
end
elseif actor.bindToOrder then
if GetUnitCurrentOrder(actor.unit) ~= actor.bindToOrder then
Destroy(actor)
widgets.bindChecks[i] = widgets.bindChecks[#widgets.bindChecks]
widgets.bindChecks[#widgets.bindChecks] = nil
end
end
end
end
---All destroyed pairs are only flagged and not destroyed until main cycle completes. This function destroys all pairs in one go.
local function RemovePairsFromCycle()
for i = #destroyedActors, 1, -1 do
local actor = destroyedActors[i]
if actor then
local firstPair = actor.firstPair
local thisPair = actor.lastPair
while thisPair ~= firstPair do
DestroyPair(thisPair)
thisPair = actor.lastPair
end
Release(actor)
end
destroyedActors[i] = nil
end
end
local function CellCheck()
local x, y
local minx, miny, maxx, maxy
local changedCells
local newMinX, newMinY, newMaxX, newMaxY
local oldMinX, oldMinY, oldMaxX, oldMaxY
local halfWidth, halfHeight
local actor
local currentCounter = cycle.counter
local actorsThisStep = cellCheckedActors[currentCounter]
local nextStep
local CYCLE_LENGTH = CYCLE_LENGTH
for i = 1, numCellChecks[currentCounter] do
actor = actorsThisStep[i]
x = actor.x[actor.anchor]
y = actor.y[actor.anchor]
halfWidth = actor.halfWidth
halfHeight = actor.halfHeight
minx = x - halfWidth
miny = y - halfHeight
maxx = x + halfWidth
maxy = y + halfHeight
newMinX = actor.minX
newMinY = actor.minY
newMaxX = actor.maxX
newMaxY = actor.maxY
if x > actor.lastX then
while minx > CELL_MAX_X[newMinX] and newMinX < NUM_CELLS_X do
changedCells = true
newMinX = newMinX + 1
end
while maxx > CELL_MAX_X[newMaxX] and newMaxX < NUM_CELLS_X do
changedCells = true
newMaxX = newMaxX + 1
end
else
while minx < CELL_MIN_X[newMinX] and newMinX > 1 do
changedCells = true
newMinX = newMinX - 1
end
while maxx < CELL_MIN_X[newMaxX] and newMaxX > 1 do
changedCells = true
newMaxX = newMaxX - 1
end
end
if y > actor.lastY then
while miny > CELL_MAX_Y[newMinY] and newMinY < NUM_CELLS_Y do
changedCells = true
newMinY = newMinY + 1
end
while maxy > CELL_MAX_Y[newMaxY] and newMaxY < NUM_CELLS_Y do
changedCells = true
newMaxY = newMaxY + 1
end
else
while miny < CELL_MIN_Y[newMinY] and newMinY > 1 do
changedCells = true
newMinY = newMinY - 1
end
while maxy < CELL_MIN_Y[newMaxY] and newMaxY > 1 do
changedCells = true
newMaxY = newMaxY - 1
end
end
if changedCells then
oldMinX = actor.minX
oldMinY = actor.minY
oldMaxX = actor.maxX
oldMaxY = actor.maxY
actor.minY = newMinY
actor.maxX = newMaxX
actor.maxY = newMaxY
for key, __ in pairs(actorAlreadyChecked) do
actorAlreadyChecked[key] = nil
end
actorAlreadyChecked[actor] = true
if newMinX > oldMinX then
actor.minX = newMinX
for X = oldMinX, newMinX - 1 < oldMaxX and newMinX - 1 or oldMaxX do
for Y = oldMinY, oldMaxY do
LeaveCell(CELL_LIST[X][Y], actor)
end
end
elseif newMinX < oldMinX then
actor.minX = newMinX
for X = newMinX, newMaxX < oldMinX - 1 and newMaxX or oldMinX - 1 do
for Y = newMinY, newMaxY do
EnterCell(CELL_LIST[X][Y], actor)
end
end
end
if newMaxX > oldMaxX then
for X = oldMaxX + 1 > newMinX and oldMaxX + 1 or newMinX, newMaxX do
for Y = newMinY, newMaxY do
EnterCell(CELL_LIST[X][Y], actor)
end
end
elseif newMaxX < oldMaxX then
for X = newMaxX + 1 > oldMinX and newMaxX + 1 or oldMinX , oldMaxX do
for Y = oldMinY, oldMaxY do
LeaveCell(CELL_LIST[X][Y], actor)
end
end
end
if newMinY > oldMinY then
for Y = oldMinY, newMinY - 1 < oldMaxY and newMinY - 1 or oldMaxY do
for X = oldMinX > newMinX and oldMinX or newMinX, oldMaxX < newMaxX and oldMaxX or newMaxX do
LeaveCell(CELL_LIST[X][Y], actor)
end
end
elseif newMinY < oldMinY then
for Y = newMinY, newMaxY < oldMinY - 1 and newMaxY or oldMinY - 1 do
for X = oldMinX > newMinX and oldMinX or newMinX, oldMaxX < newMaxX and oldMaxX or newMaxX do
EnterCell(CELL_LIST[X][Y], actor)
end
end
end
if newMaxY > oldMaxY then
for Y = oldMaxY + 1 > newMinY and oldMaxY + 1 or newMinY, newMaxY do
for X = oldMinX > newMinX and oldMinX or newMinX, oldMaxX < newMaxX and oldMaxX or newMaxX do
EnterCell(CELL_LIST[X][Y], actor)
end
end
elseif newMaxY < oldMaxY then
for Y = newMaxY + 1 > oldMinY and newMaxY + 1 or oldMinY , oldMaxY do
for X = oldMinX > newMinX and oldMinX or newMinX, oldMaxX < newMaxX and oldMaxX or newMaxX do
LeaveCell(CELL_LIST[X][Y], actor)
end
end
end
if actor.cellsVisualized then
RedrawCellVisualizers(actor)
end
changedCells = false
end
actor.lastX, actor.lastY = x, y
nextStep = currentCounter + actor.cellCheckInterval
if nextStep > CYCLE_LENGTH then
nextStep = nextStep - CYCLE_LENGTH
end
numCellChecks[nextStep] = numCellChecks[nextStep] + 1
cellCheckedActors[nextStep][numCellChecks[nextStep]] = actor
actor.nextCellCheck = nextStep
actor.positionInCellCheck = numCellChecks[nextStep]
end
if debug.visualizeAllActors then
for i = 1, numCellChecks[currentCounter] do
actor = actorsThisStep[i]
BlzSetSpecialEffectPosition(actor.visualizer, actor.x[actor.anchor], actor.y[actor.anchor], actor.z[actor] + 75)
end
end
numCellChecks[currentCounter] = 0
end
local function Interpolate()
interpolationCounter = interpolationCounter + 1
if interpolationCounter == 1 then
return
end
local actor, anchor
isInterpolated = true
RemovePairsFromCycle()
if currentPair ~= OUTSIDE_OF_CYCLE then
return
end
for i = 1, #interpolatedPairs do
currentPair = interpolatedPairs[i]
if not currentPair.destructionQueued then
actor = currentPair[0x1]
if actor.x then
anchor = actor.anchor
actor.x[anchor] = nil
actor.y[anchor] = nil
actor.z[actor] = nil
end
actor = currentPair[0x2]
if actor.x then
anchor = actor.anchor
actor.x[anchor] = nil
actor.y[anchor] = nil
actor.z[actor] = nil
end
currentPair[0x8](currentPair[0x3], currentPair[0x4], true)
end
end
isInterpolated = false
currentPair = OUTSIDE_OF_CYCLE
end
--#endregion
--===========================================================================================================================================================
--Main
--===========================================================================================================================================================
--#region Main
local function Main(nextStep)
local INV_MIN_INTERVAL = INV_MIN_INTERVAL
local CYCLE_LENGTH = CYCLE_LENGTH
local MAX_STEPS = MAX_STEPS
local evalCounter = cycle.unboundCounter - (cycle.unboundCounter // 10)*10 + 1
local startTime = os.clock()
interpolationCounter = 0
for i = 1, #interpolatedPairs do
interpolatedPairs[i] = nil
end
if ALICE_Where ~= "outsideofcycle" then
if config.HALT_ON_FIRST_CRASH then
if currentPair ~= OUTSIDE_OF_CYCLE then
local A = currentPair[0x1]
local B = currentPair[0x2]
PrintCrashMessage(currentPair, A, B)
elseif ALICE_Where == "callbacks" then
PrintCrashMessage(userCallbacks.first)
else
Warning("\n|cffff0000Error:|r ALICE Cycle crashed during last execution. The crash occured during " .. ALICE_Where .. ".")
end
ALICE_Halt()
if not debug.enabled then
EnableDebugMode()
end
if ALICE_Where == "callbacks" then
RemoveUserCallbackFromList(userCallbacks.first)
end
ALICE_Where = "outsideofcycle"
return
else
OnCrash()
end
end
ALICE_Where = "precleanup"
--First-in first-out.
local k = 1
while delayedCallbackFunctions[k] do
delayedCallbackFunctions[k](unpack(delayedCallbackArgs[k]))
k = k + 1
end
for i = 1, #delayedCallbackFunctions do
delayedCallbackFunctions[i] = nil
end
BindChecks()
RemovePairsFromCycle()
cycle.unboundCounter = cycle.unboundCounter + 1
ALICE_TimeElapsed = cycle.unboundCounter*config.MIN_INTERVAL
ALICE_Where = "callbacks"
while userCallbacks.first and userCallbacks.first.callCounter == cycle.unboundCounter do
ExecuteUserCallback(userCallbacks.first)
end
if cycle.isHalted and not nextStep then
ALICE_Where = "outsideofcycle"
return
end
--Must be after callbacks.
cycle.counter = cycle.counter + 1
if cycle.counter > CYCLE_LENGTH then
cycle.counter = 1
end
local currentCounter = cycle.counter
for i = 1, #debug.visualizationLightnings do
local lightning = debug.visualizationLightnings[i]
TimerStart(CreateTimer(), 0.02, false, function()
DestroyTimer(GetExpiredTimer())
DestroyLightning(lightning)
end)
debug.visualizationLightnings[i] = nil
end
if debug.benchmark then
local averageEvalTime = 0
for i = 1, 10 do
averageEvalTime = averageEvalTime + (debug.evaluationTime[i] or 0)/10
end
Warning("eval time: |cffffcc00" .. string.format("\x25.2f", 1000*averageEvalTime) .. "ms|r, actors: " .. #actorList .. ", pairs: " .. numPairs[currentCounter] + numEveryStepPairs .. ", cell checks: " .. numCellChecks[currentCounter])
end
if debug.enabled then
UpdateSelectedActor()
end
local numSteps, nextStep
ALICE_Where = "everystep"
--Every Step Cycle
currentPair = firstEveryStepPair
for __ = 1, numEveryStepPairs do
currentPair = currentPair[0x5]
if not currentPair.destructionQueued then
currentPair[0x8](currentPair[0x3], currentPair[0x4])
end
end
ALICE_Where = "cellcheck"
ResetCoordinateLookupTables()
CellCheck()
ALICE_Where = "variablestep"
--Variable Step Cycle
local returnValue
local pairsThisStep = whichPairs[currentCounter]
for i = 1, numPairs[currentCounter] do
currentPair = pairsThisStep[i]
if currentPair.destructionQueued then
if currentPair ~= DUMMY_PAIR then
nextStep = currentCounter + MAX_STEPS
if nextStep > CYCLE_LENGTH then
nextStep = nextStep - CYCLE_LENGTH
end
numPairs[nextStep] = numPairs[nextStep] + 1
whichPairs[nextStep][numPairs[nextStep]] = currentPair
currentPair[0x5] = nextStep
currentPair[0x6] = numPairs[nextStep]
end
else
returnValue = currentPair[0x8](currentPair[0x3], currentPair[0x4])
if returnValue then
numSteps = (returnValue*INV_MIN_INTERVAL + 1) // 1 --convert seconds to steps, then ceil.
if numSteps < 1 then
numSteps = 1
elseif numSteps > MAX_STEPS then
numSteps = MAX_STEPS
end
nextStep = currentCounter + numSteps
if nextStep > CYCLE_LENGTH then
nextStep = nextStep - CYCLE_LENGTH
end
numPairs[nextStep] = numPairs[nextStep] + 1
whichPairs[nextStep][numPairs[nextStep]] = currentPair
currentPair[0x5] = nextStep
currentPair[0x6] = numPairs[nextStep]
else
AddPairToEveryStepList(currentPair)
if currentPair[0x8] ~= PeriodicWrapper and currentPair[0x8] ~= RepeatedWrapper then
functionIsEveryStep[currentPair[0x8]] = true
end
currentPair[0x7] = true
end
end
end
numPairs[currentCounter] = 0
currentPair = OUTSIDE_OF_CYCLE
ALICE_Where = "postcleanup"
k = 1
while delayedCallbackFunctions[k] do
delayedCallbackFunctions[k](unpack(delayedCallbackArgs[k]))
k = k + 1
end
for i = 1, #delayedCallbackFunctions do
delayedCallbackFunctions[i] = nil
end
local endTime = os.clock()
debug.evaluationTime[evalCounter] = endTime - startTime
ALICE_CPULoad = (endTime - startTime)/config.MIN_INTERVAL
ALICE_Where = "outsideofcycle"
end
--#endregion
--===========================================================================================================================================================
--Debug Mode
--===========================================================================================================================================================
---@param whichPair Pair
---@param lightningType string
VisualizationLightning = function(whichPair, lightningType)
local A = whichPair[0x1]
local B = whichPair[0x2]
if A.alreadyDestroyed or B.alreadyDestroyed or A.isGlobal or B.isGlobal then
return
end
local xa = A.x[A.anchor]
local ya = A.y[A.anchor]
local za = A.z[A]
local xb = B.x[B.anchor]
local yb = B.y[B.anchor]
local zb = B.z[B]
if za and zb then
insert(debug.visualizationLightnings, AddLightningEx(lightningType, true, xa, ya, za, xb, yb, zb))
else
insert(debug.visualizationLightnings, AddLightning(lightningType, true, xa, ya, xb, yb))
end
end
UpdateSelectedActor = function()
if debug.selectedActor then
if not debug.selectedActor.isGlobal then
local x = debug.selectedActor.x[debug.selectedActor.anchor]
local y = debug.selectedActor.y[debug.selectedActor.anchor]
BlzSetSpecialEffectPosition(debug.selectedActor.visualizer, x, y, debug.selectedActor.z[debug.selectedActor] + 75)
SetCameraQuickPosition(x, y)
end
local description, title = GetDescription(debug.selectedActor)
BlzFrameSetText(debug.tooltipText, description)
BlzFrameSetText(debug.tooltipTitle, title )
BlzFrameSetSize(debug.tooltipText, 0.28, 0.0)
BlzFrameSetSize(debug.tooltip, 0.29, BlzFrameGetHeight(debug.tooltipText) + 0.0315)
local funcs = {}
local next = debug.selectedActor.nextPair
local pair = next[debug.selectedActor.firstPair]
while pair do
if (pair[0x7] and pair[0x6] ~= nil) or (not pair[0x7] and pair[0x5] == cycle.counter) then
VisualizationLightning(pair, "DRAL")
if debug.printFunctionNames then
funcs[pair[0x8]] = (funcs[pair[0x8]] or 0) + 1
end
end
pair = next[pair]
end
if debug.printFunctionNames then
local first = true
local message
for func, amount in pairs(funcs) do
if first then
message = "\n|cffffcc00Step " .. cycle.unboundCounter .. ":|r"
first = false
end
if amount > 1 then
message = message .. "\n" .. Function2String(func) .. " |cffaaaaffx" .. amount .. "|r"
else
message = message .. "\n" .. Function2String(func)
end
end
if message then
Warning(message)
end
end
end
end
---@param x number
---@param y number
---@param z number
---@return number, number
local function World2Screen(eyeX, eyeY, eyeZ, angleOfAttack, x, y, z)
local cosAngle = math.cos(angleOfAttack)
local sinAngle = math.sin(angleOfAttack)
local dx = x - eyeX
local dy = y - eyeY
local dz = (z or 0) - eyeZ
local yPrime = cosAngle*dy - sinAngle*dz
local zPrime = sinAngle*dy + cosAngle*dz
return 0.4 + 0.7425*dx/yPrime, 0.355 + 0.7425*zPrime/yPrime
end
--#region Debug Mode
local function OnMouseClick()
if debug.selectionLocked or BlzGetTriggerPlayerMouseButton() ~= MOUSE_BUTTON_TYPE_LEFT or not debug.controlIsPressed then
return
end
local previousSelectedActor = debug.selectedActor
if debug.selectedActor then
Deselect(debug.selectedActor)
end
local mouseX = BlzGetTriggerPlayerMouseX()
local mouseY = BlzGetTriggerPlayerMouseY()
local objects = ALICE_EnumObjectsInRange(mouseX, mouseY, 500, {MATCHING_TYPE_ALL}, nil)
local closestDist = 0.04
local closestObject = nil
local eyeX = GetCameraEyePositionX()
local eyeY = GetCameraEyePositionY()
local eyeZ = GetCameraEyePositionZ()
local angleOfAttack = -GetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK)
local mouseScreenX, mouseScreenY = World2Screen(eyeX, eyeY, eyeZ, angleOfAttack, mouseX, mouseY, GetTerrainZ(mouseX, mouseY))
local x, y, dx, dy
for __, object in ipairs(objects) do
local actor = GetActor(object)
--Find the actor that is closest to the mouse-cursor.
if not actor.isUnselectable and (previousSelectedActor == nil or ALICE_GetAnchor(object) ~= previousSelectedActor.anchor) then
x, y = World2Screen(eyeX, eyeY, eyeZ, angleOfAttack, ALICE_GetCoordinates3D(actor))
dx, dy = x - mouseScreenX, y - mouseScreenY
local dist = sqrt(dx*dx + dy*dy)
if dist < closestDist then
closestDist = dist
closestObject = actor.anchor
end
end
end
if closestObject then
if actorOf[closestObject].isActor then
Select(actorOf[closestObject])
else
Warning("Multiple actors are anchored to this object. Press |cffffcc00Ctrl + " .. ALICE_Config.CYCLE_SELECTION_HOTKEY .. "|r to cycle through.")
Select(actorOf[closestObject][1])
end
end
end
local function OnCtrlR()
Warning("Going to step " .. cycle.unboundCounter + 1 .. " |cffaaaaaa(" .. string.format("\x25.2f", config.MIN_INTERVAL*(cycle.unboundCounter + 1)) .. "s)|r.")
Main(true)
if debug.gameIsPaused then
ALICE_ForAllObjectsDo(function(unit) PauseUnit(unit, true) end, "unit")
end
end
local function OnCtrlG()
if debug.printFunctionNames then
Warning("\nPrinting function names disabled.")
else
Warning("\nPrinting function names enabled.")
end
debug.printFunctionNames = not debug.printFunctionNames
end
local function OnCtrlW()
if debug.selectionLocked then
Warning("\nSelection unlocked.")
else
Warning("\nSelection locked. To unlock, press |cffffcc00Ctrl + " .. ALICE_Config.LOCK_SELECTION_HOTKEY .. "|r.")
end
debug.selectionLocked = not debug.selectionLocked
end
---Cycle through actors anchored to the same object.
local function OnCtrlQ()
if debug.selectedActor == nil then
return
end
local selectedObject = debug.selectedActor.anchor
if actorOf[selectedObject].isActor then
return
end
for index, actor in ipairs(actorOf[selectedObject]) do
if debug.selectedActor == actor then
Deselect(debug.selectedActor)
if actorOf[selectedObject][index + 1] then
Select(actorOf[selectedObject][index + 1])
return
else
Select(actorOf[selectedObject][1])
return
end
end
end
end
local function OnCtrlT()
if cycle.isHalted then
ALICE_Resume()
Warning("\nALICE Cycle resumed.")
else
ALICE_Halt(BlzGetTriggerPlayerMetaKey() == 3)
Warning("\nALICE Cycle halted. To go to the next step, press |cffffcc00Ctrl + " .. ALICE_Config.HALT_CYCLE_HOTKEY .. "|r. To resume, press |cffffcc00Ctrl + T|r.")
end
end
local function DownTheRabbitHole()
EnableDebugMode()
if debug.enabled then
Warning("\nDebug mode enabled. Left-click near an actor to display attributes and enable visualization.")
else
Warning("\nDebug mode has been disabled.")
end
end
EnableDebugMode = function()
local playerName = GetPlayerName(GetTriggerPlayer())
local nameFound = false
for __, name in ipairs(config.MAP_CREATORS) do
if string.find(playerName, name) then
nameFound = true
break
end
end
if not nameFound then
if GetLocalPlayer() == GetTriggerPlayer() then
print("|cffff0000Warning:|r You need to set yourself as a map creator in the ALICE config to use debug mode.")
end
return
end
if not debug.enabled then
debug.enabled = true
BlzLoadTOCFile("CustomTooltip.toc")
debug.tooltip = BlzCreateFrame("CustomTooltip", BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), 0, 0)
BlzFrameSetAbsPoint(debug.tooltip, FRAMEPOINT_BOTTOMRIGHT, 0.8, 0.165)
BlzFrameSetSize(debug.tooltip, 0.32, 0.0)
debug.tooltipTitle = BlzGetFrameByName("CustomTooltipTitle", 0)
debug.tooltipText = BlzGetFrameByName("CustomTooltipValue", 0)
debug.nextStepTrigger = CreateTrigger()
BlzTriggerRegisterPlayerKeyEvent(debug.nextStepTrigger, GetTriggerPlayer() or Player(0), _G["OSKEY_" .. ALICE_Config.NEXT_STEP_HOTKEY], 2, true)
TriggerAddAction(debug.nextStepTrigger, OnCtrlR)
debug.mouseClickTrigger = CreateTrigger()
TriggerRegisterPlayerEvent(debug.mouseClickTrigger, GetTriggerPlayer() or Player(0), EVENT_PLAYER_MOUSE_DOWN)
TriggerAddAction(debug.mouseClickTrigger, OnMouseClick)
debug.lockSelectionTrigger = CreateTrigger()
BlzTriggerRegisterPlayerKeyEvent(debug.lockSelectionTrigger, GetTriggerPlayer() or Player(0), _G["OSKEY_" .. ALICE_Config.LOCK_SELECTION_HOTKEY], 2, true)
TriggerAddAction(debug.lockSelectionTrigger, OnCtrlW)
debug.cycleSelectTrigger = CreateTrigger()
BlzTriggerRegisterPlayerKeyEvent(debug.cycleSelectTrigger, GetTriggerPlayer() or Player(0), _G["OSKEY_" .. ALICE_Config.CYCLE_SELECTION_HOTKEY], 2, true)
TriggerAddAction(debug.cycleSelectTrigger, OnCtrlQ)
debug.haltTrigger = CreateTrigger()
BlzTriggerRegisterPlayerKeyEvent(debug.haltTrigger, GetTriggerPlayer() or Player(0), _G["OSKEY_" .. ALICE_Config.HALT_CYCLE_HOTKEY], 2, true)
BlzTriggerRegisterPlayerKeyEvent(debug.haltTrigger, GetTriggerPlayer() or Player(0), _G["OSKEY_" .. ALICE_Config.HALT_CYCLE_HOTKEY], 3, true)
TriggerAddAction(debug.haltTrigger, OnCtrlT)
debug.printFunctionsTrigger = CreateTrigger()
BlzTriggerRegisterPlayerKeyEvent(debug.printFunctionsTrigger, GetTriggerPlayer() or Player(0), _G["OSKEY_" .. ALICE_Config.PRINT_FUNCTION_NAMES_HOTKEY], 2, true)
TriggerAddAction(debug.printFunctionsTrigger, OnCtrlG)
debug.pressControlTrigger = CreateTrigger()
for i = 0, 3 do
BlzTriggerRegisterPlayerKeyEvent(debug.pressControlTrigger, GetTriggerPlayer() or Player(0), OSKEY_LCONTROL, i, true)
end
TriggerAddAction(debug.pressControlTrigger, function() debug.controlIsPressed = true end)
debug.releaseControlTrigger = CreateTrigger()
for i = 0, 3 do
BlzTriggerRegisterPlayerKeyEvent(debug.releaseControlTrigger, GetTriggerPlayer() or Player(0), OSKEY_LCONTROL, i, false)
end
TriggerAddAction(debug.releaseControlTrigger, function() debug.controlIsPressed = false end)
else
if debug.selectedActor then
Deselect(debug.selectedActor)
end
debug.enabled = false
DestroyTrigger(debug.nextStepTrigger)
DestroyTrigger(debug.lockSelectionTrigger)
DestroyTrigger(debug.mouseClickTrigger)
DestroyTrigger(debug.cycleSelectTrigger)
DestroyTrigger(debug.haltTrigger)
DestroyTrigger(debug.printFunctionsTrigger)
DestroyTrigger(debug.pressControlTrigger)
DestroyTrigger(debug.releaseControlTrigger)
BlzDestroyFrame(debug.tooltip)
end
end
--#endregion
--===========================================================================================================================================================
--Widget Actors
--===========================================================================================================================================================
--#region WidgetActors
local actorFlags = {}
local identifiers = {}
local function Kill(object)
if type(object) == "table" then
if object.destroy then
object:destroy()
elseif object.visual then
if HandleType[object.visual] == "effect" then
DestroyEffect(object.visual)
elseif HandleType[object.visual] == "unit" then
KillUnit(object.visual)
elseif HandleType[object.visual] == "image" then
DestroyImage(object.visual)
elseif HandleType[object.visual] == "lightning" then
DestroyLightning(object.visual)
end
end
elseif IsHandle[object] then
if HandleType[object] == "unit" then
KillUnit(object)
elseif HandleType[object] == "destructable" then
KillDestructable(object)
elseif HandleType[object] == "item" then
RemoveItem(object)
end
end
end
local function Clear(object, wasUnitDeath)
local actor = actorOf[object]
if actor then
if not actor.isActor then
for i = #actor, 1, -1 do
if not wasUnitDeath or not actor[i].persistOnDeath then
if actor[i].host ~= object then
Kill(actor[i].host)
end
Destroy(actor[i])
end
end
elseif not wasUnitDeath or not actor.persistOnDeath then
Destroy(actor)
end
end
end
local function OnLoad(widget, transport)
if actorOf[widget] == nil then
return
end
if actorOf[widget].isActor then
actorOf[widget].anchor = transport
SetCoordinateFuncs(actorOf[widget])
Suspend(actorOf[widget], true)
else
for __, actor in ipairs(actorOf[widget]) do
actor.anchor = transport
SetCoordinateFuncs(actor)
Suspend(actor, true)
end
end
end
local function OnUnload(widget)
if actorOf[widget].isActor then
local actor = actorOf[widget]
actor.anchor = actor.originalAnchor
actor.x[actor.anchor] = nil
actor.y[actor.anchor] = nil
actor.z[actor] = nil
SetCoordinateFuncs(actor)
Suspend(actorOf[widget], false)
else
for __, actor in ipairs(actorOf[widget]) do
actor.anchor = actor.originalAnchor
actor.x[actor.anchor] = nil
actor.y[actor.anchor] = nil
actor.z[actor] = nil
SetCoordinateFuncs(actor)
Suspend(actor, false)
end
end
end
--===========================================================================================================================================================
--Unit Actors
--===========================================================================================================================================================
local function CorpseCleanUp(u)
if GetUnitTypeId(u) == 0 then
DestroyTrigger(widgets.reviveTriggers[u])
widgets.reviveTriggers[u] = nil
for __, func in ipairs(eventHooks.onUnitRemove) do
func(u)
end
Clear(u)
end
return 1.0
end
---@param u unit
---@return boolean
local function CreateUnitActor(u)
local id = GetUnitTypeId(u)
if GetUnitAbilityLevel(u, 1097625443) > 0 then --FourCC "Aloc" (Locust)
return false
end
if not widgets.idInclusions[id] and (config.NO_UNIT_ACTOR or widgets.idExclusions[id]) then
return false
end
if id == 0 then
return false
end
for key, __ in pairs(identifiers) do
identifiers[key] = nil
end
local interactions
if GetUnitState(u, UNIT_STATE_LIFE) > 0.405 then
identifiers[#identifiers + 1] = "unit"
actorFlags.isStationary = IsUnitType(u, UNIT_TYPE_STRUCTURE)
elseif config.UNITS_LEAVE_BEHIND_CORPSES then
identifiers[#identifiers + 1] = "corpse"
interactions = {self = CorpseCleanUp}
actorFlags.isStationary = config.UNIT_CORPSES_ARE_STATIONARY
else
return false
end
if config.ADD_WIDGET_NAMES then
identifiers[#identifiers + 1] = toCamelCase[GetUnitName(u)]
if IsUnitType(u, UNIT_TYPE_HERO) then
identifiers[#identifiers + 1] = toCamelCase[GetHeroProperName(u)]
end
end
for __, unittype in ipairs(config.UNIT_ADDED_CLASSIFICATIONS) do
if IsUnitType(u, unittype) then
identifiers[#identifiers + 1] = UNIT_CLASSIFICATION_NAMES[unittype]
else
identifiers[#identifiers + 1] = "non" .. UNIT_CLASSIFICATION_NAMES[unittype]
end
end
identifiers[#identifiers + 1] = string.pack(">I4", id)
actorFlags.radius = config.DEFAULT_UNIT_RADIUS
actorFlags.persistOnDeath = config.UNITS_LEAVE_BEHIND_CORPSES
Create(u, identifiers, interactions, actorFlags)
return true
end
local function OnRevive()
local u = GetTriggerUnit()
ALICE_RemoveSelfInteraction(u, CorpseCleanUp, "corpse")
ALICE_SwapIdentifier(u, "corpse", "unit", "corpse")
if config.UNIT_CORPSES_ARE_STATIONARY and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
ALICE_SetStationary(u, false)
end
DestroyTrigger(widgets.reviveTriggers[u])
widgets.reviveTriggers[u] = nil
for __, func in ipairs(eventHooks.onUnitRevive) do
func(u)
end
end
local function OnUnitDeath()
local u = GetTriggerUnit()
local actor = actorOf[u]
if actor == nil then
return
end
if config.UNITS_LEAVE_BEHIND_CORPSES then
ALICE_AddSelfInteraction(u, CorpseCleanUp, "unit")
ALICE_SwapIdentifier(u, "unit", "corpse", "unit")
if config.UNIT_CORPSES_ARE_STATIONARY then
ALICE_SetStationary(u, true)
end
widgets.reviveTriggers[u] = CreateTrigger()
TriggerAddAction(widgets.reviveTriggers[u], OnRevive)
TriggerRegisterUnitStateEvent(widgets.reviveTriggers[u], u, UNIT_STATE_LIFE, GREATER_THAN_OR_EQUAL, 0.405)
for __, func in ipairs(eventHooks.onUnitDeath) do
func(u)
end
Clear(u, true)
else
for __, func in ipairs(eventHooks.onUnitRemove) do
func(u)
end
Clear(u)
end
end
local function OnUnitEnter()
local u = GetTrainedUnit() or GetTriggerUnit()
if GetActor(u, "unit") then
return
end
if CreateUnitActor(u) then
for __, func in ipairs(eventHooks.onUnitEnter) do
func(u)
end
end
end
local function OnUnitLoaded()
local u = GetLoadedUnit()
OnLoad(u, GetTransportUnit())
ALICE_CallPeriodic(function(unit)
if not IsUnitLoaded(unit) then
ALICE_DisableCallback()
if actorOf[unit] then
OnUnload(unit)
end
end
end, 0, u)
end
--===========================================================================================================================================================
--Destructable Actors
--===========================================================================================================================================================
---@param d destructable
local function CreateDestructableActor(d)
local id = GetDestructableTypeId(d)
if not widgets.idInclusions[id] and (config.NO_DESTRUCTABLE_ACTOR or widgets.idExclusions[id]) then
return
end
if id == 0 then
return
end
for key, __ in pairs(identifiers) do
identifiers[key] = nil
end
identifiers[#identifiers + 1] = "destructable"
local name = GetDestructableName(d)
if config.ADD_WIDGET_NAMES then
identifiers[#identifiers + 1] = toCamelCase[name]
end
identifiers[#identifiers + 1] = string.pack(">I4", id)
actorFlags.radius = config.DEFAULT_DESTRUCTABLE_RADIUS
actorFlags.isStationary = true
actorFlags.persistOnDeath = nil
Create(d, identifiers, nil, actorFlags)
end
OnDestructableDeath = function()
local whichObject = GetTriggerDestructable()
DestroyTrigger(widgets.deathTriggers[whichObject])
widgets.deathTriggers[whichObject] = nil
for __, func in ipairs(eventHooks.onDestructableDestroy) do
func(whichObject)
end
Clear(whichObject)
end
--===========================================================================================================================================================
--Item Actors
--===========================================================================================================================================================
---@param i item
local function CreateItemActor(i)
local id = GetItemTypeId(i)
if not widgets.idInclusions[id] and (config.NO_ITEM_ACTOR or widgets.idExclusions[id]) then
return
end
if id == 0 then
return
end
for key, __ in pairs(identifiers) do
identifiers[key] = nil
end
identifiers[#identifiers + 1] = "item"
if config.ADD_WIDGET_NAMES then
identifiers[#identifiers + 1] = toCamelCase[GetItemName(i)]
end
identifiers[#identifiers + 1] = string.pack(">I4", id)
actorFlags.radius = config.DEFAULT_ITEM_RADIUS
actorFlags.isStationary = config.ITEMS_ARE_STATIONARY
actorFlags.persistOnDeath = nil
Create(i, identifiers, nil, actorFlags)
end
local function OnItemPickup()
local item = GetManipulatedItem()
OnLoad(item, GetTriggerUnit())
if config.ITEMS_ARE_STATIONARY then
ALICE_SetStationary(item, false)
end
end
local function OnItemDrop()
local item = GetManipulatedItem()
if actorOf[item] then
OnUnload(item)
if config.ITEMS_ARE_STATIONARY then
ALICE_SetStationary(item, true)
end
else
AddDelayedCallback(function(whichItem)
if GetItemTypeId(whichItem) == 0 then
return
end
CreateItemActor(whichItem)
for __, func in ipairs(eventHooks.onItemEnter) do
func(whichItem)
end
end, item)
end
end
local function OnItemSold()
Clear(GetManipulatedItem())
end
OnItemDeath = function()
SaveWidgetHandle(widgets.hash, 0, 0, GetTriggerWidget())
local whichObject = LoadItemHandle(widgets.hash, 0, 0)
DestroyTrigger(widgets.deathTriggers[whichObject])
widgets.deathTriggers[whichObject] = nil
for __, func in ipairs(eventHooks.onItemDestroy) do
func(whichObject)
end
Clear(whichObject)
end
--#endregion
--===========================================================================================================================================================
--Init
--===========================================================================================================================================================
--#region Init
local function Init()
Require "HandleType"
Require "Hook"
timers.MASTER = CreateTimer()
timers.INTERPOLATION = CreateTimer()
MAX_STEPS = (config.MAX_INTERVAL/config.MIN_INTERVAL) // 1
CYCLE_LENGTH = MAX_STEPS + 1
DO_NOT_EVALUATE = CYCLE_LENGTH + 1
for i = 1, DO_NOT_EVALUATE do
numPairs[i] = 0
whichPairs[i] = {}
end
for i = 1, CYCLE_LENGTH do
numCellChecks[i] = 0
cellCheckedActors[i] = {}
end
local worldBounds = GetWorldBounds()
MAP_MIN_X = GetRectMinX(worldBounds)
MAP_MAX_X = GetRectMaxX(worldBounds)
MAP_MIN_Y = GetRectMinY(worldBounds)
MAP_MAX_Y = GetRectMaxY(worldBounds)
MAP_SIZE_X = MAP_MAX_X - MAP_MIN_X
MAP_SIZE_Y = MAP_MAX_Y - MAP_MIN_Y
NUM_CELLS_X = MAP_SIZE_X // config.CELL_SIZE
NUM_CELLS_Y = MAP_SIZE_Y // config.CELL_SIZE
for X = 1, NUM_CELLS_X do
CELL_LIST[X] = {}
for Y = 1, NUM_CELLS_Y do
CELL_LIST[X][Y] = {numActors = 0}
end
end
for x = 1, NUM_CELLS_X do
CELL_MIN_X[x] = MAP_MIN_X + (x-1)/NUM_CELLS_X*MAP_SIZE_X
CELL_MAX_X[x] = MAP_MIN_X + x/NUM_CELLS_X*MAP_SIZE_X
end
for y = 1, NUM_CELLS_Y do
CELL_MIN_Y[y] = MAP_MIN_Y + (y-1)/NUM_CELLS_Y*MAP_SIZE_Y
CELL_MAX_Y[y] = MAP_MIN_Y + y/NUM_CELLS_Y*MAP_SIZE_Y
end
GetTable = config.TABLE_RECYCLER_GET or function()
local numUnusedTables = #unusedTables
if numUnusedTables == 0 then
return {}
else
local returnTable = unusedTables[numUnusedTables]
unusedTables[numUnusedTables] = nil
return returnTable
end
end
ReturnTable = config.TABLE_RECYCLER_RETURN or function(whichTable)
for key, __ in pairs(whichTable) do
whichTable[key] = nil
end
unusedTables[#unusedTables + 1] = whichTable
setmetatable(whichTable, nil)
end
local trig = CreateTrigger()
for i = 0, 23 do
TriggerRegisterPlayerChatEvent(trig, Player(i), "downtherabbithole", true)
TriggerRegisterPlayerChatEvent(trig, Player(i), "-downtherabbithole", true)
end
TriggerAddAction(trig, DownTheRabbitHole)
SELF_INTERACTION_ACTOR = Create({}, "selfInteraction", nil, EMPTY_TABLE)
actorList[#actorList] = nil
celllessActorList[#celllessActorList] = nil
totalActors = totalActors - 1
SELF_INTERACTION_ACTOR.unique = 0
debug.functionName[CorpseCleanUp] = "CorpseCleanUp"
TimerStart(timers.MASTER, config.MIN_INTERVAL, true, Main)
if config.INTERPOLATION_INTERVAL then
TimerStart(timers.INTERPOLATION, config.INTERPOLATION_INTERVAL, true, Interpolate)
end
local precomputedHeightMap = Require.optionally "PrecomputedHeightMap"
if precomputedHeightMap then
GetTerrainZ = _G.GetTerrainZ
else
moveableLoc = Location(0, 0)
GetTerrainZ = function(x, y)
MoveLocation(moveableLoc, x, y)
return GetLocationZ(moveableLoc)
end
end
widgets.hash = InitHashtable()
trig = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_HERO_REVIVE_FINISH)
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SUMMON)
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_TRAIN_FINISH)
TriggerAddAction(trig, OnUnitEnter)
trig = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DEATH)
TriggerAddAction(trig, OnUnitDeath)
local G = CreateGroup()
GroupEnumUnitsInRect(G, GetPlayableMapRect(), nil)
ForGroup(G, function()
local u = GetEnumUnit()
if CreateUnitActor(u) then
for __, func in ipairs(eventHooks.onUnitEnter) do
func(u)
end
end
end)
DestroyGroup(G)
EnumDestructablesInRect(worldBounds, nil, function() CreateDestructableActor(GetEnumDestructable()) end)
EnumItemsInRect(worldBounds, nil, function() CreateItemActor(GetEnumItem()) end)
trig = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DROP_ITEM)
TriggerAddAction(trig, OnItemDrop)
trig = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_PICKUP_ITEM)
TriggerAddAction(trig, OnItemPickup)
trig = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_PAWN_ITEM)
TriggerAddAction(trig, OnItemSold)
trig = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_CHANGE_OWNER)
TriggerAddAction(trig, OnUnitChangeOwner)
trig = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_LOADED)
TriggerAddAction(trig, OnUnitLoaded)
local function CreateUnitHookFunc(self, ...)
local newUnit = self.old(...)
if CreateUnitActor(newUnit) then
for __, func in ipairs(eventHooks.onUnitEnter) do
func(newUnit)
end
end
return newUnit
end
---@diagnostic disable-next-line: duplicate-set-field
Hook.CreateUnit = CreateUnitHookFunc
---@diagnostic disable-next-line: duplicate-set-field
Hook.CreateUnitByName = CreateUnitHookFunc
---@diagnostic disable-next-line: duplicate-set-field
Hook.CreateUnitAtLoc = CreateUnitHookFunc
---@diagnostic disable-next-line: duplicate-set-field
Hook.CreateUnitAtLocByName = CreateUnitHookFunc
if config.UNITS_LEAVE_BEHIND_CORPSES then
---@diagnostic disable-next-line: duplicate-set-field
Hook.CreateCorpse = CreateUnitHookFunc
end
---@diagnostic disable-next-line: duplicate-set-field
function Hook:RemoveUnit(whichUnit)
local item
for i = 0, UnitInventorySize(whichUnit) - 1 do
item = UnitItemInSlot(whichUnit, i)
for __, func in ipairs(eventHooks.onItemDestroy) do
func(item)
end
Clear(item)
end
for __, func in ipairs(eventHooks.onUnitRemove) do
func(whichUnit)
end
Clear(whichUnit)
self.old(whichUnit)
end
---@diagnostic disable-next-line: duplicate-set-field
function Hook:ShowUnit(whichUnit, enable)
self.old(whichUnit, enable)
if actorOf[whichUnit] == nil then
return
end
if actorOf[whichUnit].isActor then
Suspend(actorOf[whichUnit], not enable)
else
for __, actor in ipairs(actorOf[whichUnit]) do
Suspend(actor, not enable)
end
end
end
local function CreateDestructableHookFunc(self, ...)
local newDestructable = self.old(...)
CreateDestructableActor(newDestructable)
for __, func in ipairs(eventHooks.onDestructableEnter) do
func(newDestructable)
end
return newDestructable
end
---@diagnostic disable-next-line: duplicate-set-field
Hook.CreateDestructable = CreateDestructableHookFunc
---@diagnostic disable-next-line: duplicate-set-field
Hook.CreateDestructableZ = CreateDestructableHookFunc
---@diagnostic disable-next-line: duplicate-set-field
Hook.BlzCreateDestructableWithSkin = CreateDestructableHookFunc
---@diagnostic disable-next-line: duplicate-set-field
Hook.BlzCreateDestructableZWithSkin = CreateDestructableHookFunc
---@diagnostic disable-next-line: duplicate-set-field
function Hook:RemoveDestructable(whichDestructable)
for __, func in ipairs(eventHooks.onDestructableDestroy) do
func(whichDestructable)
end
Clear(whichDestructable)
if widgets.deathTriggers[whichDestructable] then
DestroyTrigger(widgets.deathTriggers[whichDestructable])
widgets.deathTriggers[whichDestructable] = nil
end
self.old(whichDestructable)
end
---@diagnostic disable-next-line: duplicate-set-field
function Hook:DestructableRestoreLife(whichDestructable, life, birth)
self.old(whichDestructable, life, birth)
if GetDestructableLife(whichDestructable) > 0 and GetActor(whichDestructable, "destructable") == nil then
CreateDestructableActor(whichDestructable)
for __, func in ipairs(eventHooks.onDestructableEnter) do
func(whichDestructable)
end
end
end
---@diagnostic disable-next-line: duplicate-set-field
function Hook:CreateItem(...)
local newItem
newItem = self.old(...)
CreateItemActor(newItem)
for __, func in ipairs(eventHooks.onItemEnter) do
func(newItem)
end
return newItem
end
---@diagnostic disable-next-line: duplicate-set-field
function Hook:RemoveItem(whichItem)
for __, func in ipairs(eventHooks.onItemDestroy) do
func(whichItem)
end
Clear(whichItem)
if widgets.deathTriggers[whichItem] then
DestroyTrigger(widgets.deathTriggers[whichItem])
widgets.deathTriggers[whichItem] = nil
end
self.old(whichItem)
end
---@diagnostic disable-next-line: duplicate-set-field
function Hook:SetItemVisible(whichItem, enable)
self.old(whichItem, enable)
if actorOf[whichItem] == nil then
return
end
if actorOf[whichItem].isActor then
Suspend(actorOf[whichItem], not enable)
else
for __, actor in ipairs(actorOf[whichItem]) do
Suspend(actor, not enable)
end
end
end
end
OnInit.final("ALICE", Init)
--#endregion
--===========================================================================================================================================================
--API
--===========================================================================================================================================================
--#region API
--Core API
--===========================================================================================================================================================
---Create an actor for the object host and add it to the cycle. If the host is a table and is provided as the only input argument, all other arguments will be retrieved directly from that table.
---Recognized flags:
-- - anchor
-- - radius
-- - selfInteractions
-- - bindToBuff
-- - bindToOrder
-- - isStationary
-- - onActorDestroy
-- - zOffset
-- - cellCheckInterval
-- - persistOnDeath
-- - priority
-- - width
-- - height
-- - hasInfiniteRange
-- - isGlobal
-- - isAnonymous
-- - isUnselectable
-- - actorClass
---@param host any
---@param identifier? string | string[]
---@param interactions? table
---@param flags? ALICE_Flags
---@return any
function ALICE_Create(host, identifier, interactions, flags)
if host == nil then
error("Host is nil.")
end
if identifier then
Create(host, identifier, interactions, flags or EMPTY_TABLE)
else
Create(host, host.identifier, host.interactions, host)
end
return host
end
---Destroy the actor of the specified object. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param object Object
---@param keyword? string
function ALICE_Destroy(object, keyword)
local actor = GetActor(object, keyword)
if actor then
Destroy(actor)
end
end
---Calls the appropriate function to destroy the object, then destroys all actors attached to it. If the object is a table, the object:destroy() method will be called. If no destroy function exists, it will try to destroy the table's visual, which can be an effect, a unit, or an image.
---@param object Object
function ALICE_Kill(object)
if object == nil then
return
end
Kill(object)
Clear(object)
end
--Math API
--===========================================================================================================================================================
---Returns the distance between the objects of the pair currently being evaluated in two dimensions. This function uses cached values and may not be accurate if immediately called after changing an object's location.
---@return number
function ALICE_PairGetDistance2D()
local actorA = currentPair[0x1]
local actorB = currentPair[0x2]
if actorA and actorB == SELF_INTERACTION_ACTOR then
return 0
end
local anchorA = actorA.anchor
local anchorB = actorB.anchor
local dx = actorA.x[anchorA] - actorB.x[anchorB]
local dy = actorA.y[anchorA] - actorB.y[anchorB]
return sqrt(dx*dx + dy*dy)
end
---Returns the distance between the objects of the pair currently being evaluated in three dimensions. This function uses cached values and may not be accurate if immediately called after changing an object's location.
---@return number
function ALICE_PairGetDistance3D()
local actorA = currentPair[0x1]
local actorB = currentPair[0x2]
if actorA and actorB == SELF_INTERACTION_ACTOR then
return 0
end
local anchorA = actorA.anchor
local anchorB = actorB.anchor
local dx = actorA.x[anchorA] - actorB.x[anchorB]
local dy = actorA.y[anchorA] - actorB.y[anchorB]
local dz = actorA.z[actorA] - actorB.z[actorB]
return sqrt(dx*dx + dy*dy + dz*dz)
end
---Returns the angle from object A to object B of the pair currently being evaluated. This function uses cached values and may not be accurate if immediately called after changing an object's location.
---@return number
function ALICE_PairGetAngle2D()
local actorA = currentPair[0x1]
local actorB = currentPair[0x2]
if actorA and actorB == SELF_INTERACTION_ACTOR then
return 0
end
local anchorA = actorA.anchor
local anchorB = actorB.anchor
local dx = actorB.x[anchorB] - actorA.x[anchorA]
local dy = actorB.y[anchorB] - actorA.y[anchorA]
return atan(dy, dx)
end
---Returns the horizontal and vertical angles from object A to object B of the pair currently being evaluated. This function uses cached values and may not be accurate if immediately called after changing an object's location.
---@return number, number
function ALICE_PairGetAngle3D()
local actorA = currentPair[0x1]
local actorB = currentPair[0x2]
if actorA and actorB == SELF_INTERACTION_ACTOR then
return 0, 0
end
local anchorA = actorA.anchor
local anchorB = actorB.anchor
local dx = actorB.x[anchorB] - actorA.x[anchorA]
local dy = actorB.y[anchorB] - actorA.y[anchorA]
local dz = actorB.z[actorB] - actorA.z[actorA]
return atan(dy, dx), atan(dz, sqrt(dx*dx + dy*dy))
end
---Returns the coordinates of the objects in the pair currently being evaluated in the order x1, y1, x2, y2. This function uses cached values and may not be accurate if immediately called after changing an object's location.
---@return number, number, number, number
function ALICE_PairGetCoordinates2D()
local actorA = currentPair[0x1]
local actorB = currentPair[0x2]
local anchorA = actorA.anchor
local anchorB = actorB.anchor
return actorA.x[anchorA], actorA.y[anchorA], actorB.x[anchorB], actorB.y[anchorB]
end
---Returns the coordinates of the objects in the pair currently being evaluated in the order x1, y1, z1, x2, y2, z2. This function uses cached values and may not be accurate if immediately called after changing an object's location.
---@return number, number, number, number, number, number
function ALICE_PairGetCoordinates3D()
local actorA = currentPair[0x1]
local actorB = currentPair[0x2]
local anchorA = actorA.anchor
local anchorB = actorB.anchor
return actorA.x[anchorA], actorA.y[anchorA], actorA.z[actorA], actorB.x[anchorB], actorB.y[anchorB], actorB.z[actorB]
end
---Returns the coordinates x, y of an object. This function uses cached values and may not be accurate if immediately called after changing an object's location.
---@param object Object
---@param keyword? string
---@return number, number
function ALICE_GetCoordinates2D(object, keyword)
local actor = GetActor(object, keyword)
if actor == nil then
return 0, 0
end
local anchor = actor.anchor
return actor.x[anchor], actor.y[anchor]
end
---Returns the coordinates x, y, z of an object. This function uses cached values and may not be accurate if immediately called after changing an object's location.
---@param object Object
---@param keyword? string
---@return number, number, number
function ALICE_GetCoordinates3D(object, keyword)
local actor = GetActor(object, keyword)
if actor == nil then
return 0, 0, 0
end
local anchor = actor.anchor
return actor.x[anchor], actor.y[anchor], actor.z[actor]
end
--Callback API
--===========================================================================================================================================================
---Invokes the callback function after the specified delay, passing additional arguments into the callback function.
---@param callback function
---@param delay? number
---@vararg any
---@return table
function ALICE_CallDelayed(callback, delay, ...)
local new = GetTable()
new.callCounter = cycle.unboundCounter + ((delay or 0)*INV_MIN_INTERVAL + 1) // 1
new.callback = callback
local numArgs = select("#", ...)
if numArgs == 1 then
new.args = select(1, ...)
elseif numArgs > 1 then
new.args = pack(...)
new.unpack = true
end
AddUserCallback(new)
return new
end
---Invokes the callback function after the specified delay, passing the hosts of the current pair as arguments. A third parameter is passed into the callback, specifying whether you have access to the ALICE_Pair functions. You will not if the current pair has been destroyed after the callback was queued up.
---@param callback function
---@param delay? number
---@return table
function ALICE_PairCallDelayed(callback, delay)
local new = GetTable()
new.callCounter = cycle.unboundCounter + ((delay or 0)*INV_MIN_INTERVAL + 1) // 1
new.callback = callback
new.hostA = currentPair[0x3]
new.hostB = currentPair[0x4]
new.pair = currentPair
AddUserCallback(new)
return new
end
---Periodically invokes the callback function. Optional delay parameter to delay the first execution. Additional arguments are passed into the callback function. The return value of the callback function specifies the interval until next execution.
---@param callback function
---@param delay? number
---@vararg any
---@return table
function ALICE_CallPeriodic(callback, delay, ...)
local host = pack(...)
host.callback = callback
host.excess = delay or 0
host.isPeriodic = true
local actor = CreateStub(host)
actor.periodicPair = CreatePair(actor, SELF_INTERACTION_ACTOR, PeriodicWrapper)
return host
end
---Periodically invokes the callback function up to howOften times. Optional delay parameter to delay the first execution. The arguments passed into the callback function are the current iteration, followed by any additional arguments. The return value of the callback function specifies the interval until next execution.
---@param callback function
---@param howOften integer
---@param delay? number
---@vararg any
---@return table
function ALICE_CallRepeated(callback, howOften, delay, ...)
local host = pack(...)
host.callback = callback
host.howOften = howOften
host.currentExecution = 0
host.excess = delay or 0
host.isPeriodic = true
local actor = CreateStub(host)
if howOften > 0 then
actor.periodicPair = CreatePair(actor, SELF_INTERACTION_ACTOR, RepeatedWrapper)
end
return host
end
---Disables a callback returned by ALICE_CallDelayed, ALICE_CallPeriodic, or ALICE_CallRepeated. If called from within a periodic callback function itself, the parameter can be omitted. Returns whether the callback was interrupted.
---@param callback? table
---@return boolean
function ALICE_DisableCallback(callback)
local actor
if callback then
if callback.isPeriodic then
actor = GetActor(callback)
if actor == nil or actor.alreadyDestroyed then
return false
end
actor.periodicPair.destructionQueued = true
AddDelayedCallback(DestroyPair, actor.periodicPair)
if functionOnDestroy[callback.callback] then
functionOnDestroy[callback.callback](unpack(callback))
end
DestroyStub(actor)
for key, __ in pairs(callback) do
callback[key] = nil
end
return true
else
if callback.callCounter == nil or callback.callCounter <= cycle.unboundCounter then
return false
end
if functionOnDestroy[callback.callback] then
if callback.pair then
if callback.pair[0x3] == callback.hostA and callback.pair[0x4] == callback.hostB then
currentPair = callback.pair
functionOnDestroy[callback.callback](callback.hostA, callback.hostB, true)
currentPair = OUTSIDE_OF_CYCLE
else
functionOnDestroy[callback.callback](callback.hostA, callback.hostB, false)
end
elseif callback.args then
if callback.unpack then
functionOnDestroy[callback.callback](unpack(callback.args))
else
functionOnDestroy[callback.callback](callback.args)
end
else
functionOnDestroy[callback.callback]()
end
end
if not callback.isPaused then
RemoveUserCallbackFromList(callback)
end
for key, __ in pairs(callback) do
callback[key] = nil
end
return true
end
elseif currentPair ~= OUTSIDE_OF_CYCLE then
actor = currentPair[0x1]
if actor == nil or actor.alreadyDestroyed or actor.periodicPair ~= currentPair then
return false
end
actor.periodicPair.destructionQueued = true
AddDelayedCallback(DestroyPair, actor.periodicPair)
callback = actor.host
if functionOnDestroy[callback.callback] then
functionOnDestroy[callback.callback](unpack(callback))
end
DestroyStub(actor)
for key, __ in pairs(callback) do
callback[key] = nil
end
return true
end
return false
end
---Pauses or unpauses a callback returned by ALICE_CallDelayed, ALICE_CallPeriodic, or ALICE_CallRepeated. If a periodic callback is unpaused this way, the next iteration will be executed immediately. Otherwise, the remaining time will be waited. If called from within a periodic callback function itself, the callback parameter can be omitted.
---@param callback? table
---@param enable? boolean
function ALICE_PauseCallback(callback, enable)
enable = enable ~= false
local actor
if callback then
if callback.isPeriodic then
if callback.isPaused == enable then
return
end
callback.isPaused = enable
actor = GetActor(callback)
if enable then
PausePair(actor.periodicPair)
else
UnpausePair(actor.periodicPair)
end
else
if callback.callCounter == nil then
return
end
if callback.isPaused == enable then
return
end
callback.isPaused = enable
if enable then
if callback.callCounter <= cycle.unboundCounter then
return
end
callback.stepsRemaining = callback.callCounter - cycle.unboundCounter
RemoveUserCallbackFromList(callback)
else
callback.callCounter = cycle.unboundCounter + callback.stepsRemaining
AddUserCallback(callback)
end
return
end
elseif currentPair ~= OUTSIDE_OF_CYCLE then
if callback.isPaused == enable then
return
end
callback.isPaused = enable
actor = currentPair[0x1]
if enable then
PausePair(actor.periodicPair)
else
UnpausePair(actor.periodicPair)
end
else
end
end
--Enum API
--===========================================================================================================================================================
---Enum functions return a table with all objects that have the specified identifier. Identifier can be a string or a table. If it is a table, the last entry must be MATCHING_TYPE_ANY or MATCHING_TYPE_ALL. Optional condition to specify an additional filter function, which takes the enumerated objects as an argument and returns a boolean. Additional arguments are passed into the filter function.
---@param identifier string | table
---@param condition? function
---@vararg any
---@return table
function ALICE_EnumObjects(identifier, condition, ...)
local returnTable = GetTable()
if type(identifier) == "string" then
for __, actor in ipairs(actorList) do
if actor.identifier[identifier] and (condition == nil or condition(actor.host, ...)) then
if not actor.isSuspended then
returnTable[#returnTable + 1] = actor.host
end
end
end
else
for __, actor in ipairs(actorList) do
if HasIdentifierFromTable(actor, identifier) and (condition == nil or condition(actor.host, ...)) then
if not actor.isSuspended then
returnTable[#returnTable + 1] = actor.host
end
end
end
end
return returnTable
end
---Performs the action on all objects that have the specified identifier. Identifier can be a string or a table. If it is a table, the last entry must be MATCHING_TYPE_ANY or MATCHING_TYPE_ALL. Optional condition to specify an additional filter function, which takes the enumerated objects as an argument and returns a boolean. Additional arguments are passed into both the filter and the action function.
---@param action function
---@param identifier string | table
---@param condition function | nil
---@vararg any
function ALICE_ForAllObjectsDo(action, identifier, condition, ...)
local list = ALICE_EnumObjects(identifier, condition, ...)
for __, object in ipairs(list) do
action(object, ...)
end
ReturnTable(list)
end
---Enum functions return a table with all objects that have the specified identifier. Identifier can be a string or a table. If it is a table, the last entry must be MATCHING_TYPE_ANY or MATCHING_TYPE_ALL. Optional condition to specify an additional filter function, which takes the enumerated objects as an argument and returns a boolean. Additional arguments are passed into the filter function.
---@param x number
---@param y number
---@param range number
---@param identifier string | table
---@param condition? function
---@vararg any
---@return table
function ALICE_EnumObjectsInRange(x, y, range, identifier, condition, ...)
local returnTable = GetTable()
ResetCoordinateLookupTables()
local minX = min(NUM_CELLS_X, max(1, (NUM_CELLS_X*(x - range - MAP_MIN_X)/MAP_SIZE_X) // 1 + 1))
local minY = min(NUM_CELLS_Y, max(1, (NUM_CELLS_Y*(y - range - MAP_MIN_Y)/MAP_SIZE_Y) // 1 + 1))
local maxX = min(NUM_CELLS_X, max(1, (NUM_CELLS_X*(x + range - MAP_MIN_X)/MAP_SIZE_X) // 1 + 1))
local maxY = min(NUM_CELLS_Y, max(1, (NUM_CELLS_Y*(y + range - MAP_MIN_Y)/MAP_SIZE_Y) // 1 + 1))
local dx
local dy
local rangeSquared = range*range
local identifierIsString = type(identifier) == "string"
local actor, cell
for X = minX, maxX do
for Y = minY, maxY do
cell = CELL_LIST[X][Y]
actor = cell.first
if actor then
if identifierIsString then
for __ = 1, cell.numActors do
if actor.identifier[identifier] and not alreadyEnumerated[actor] and (condition == nil or condition(actor.host, ...)) then
alreadyEnumerated[actor] = true
dx = actor.x[actor.anchor] - x
dy = actor.y[actor.anchor] - y
if dx*dx + dy*dy < rangeSquared then
returnTable[#returnTable + 1] = actor.host
end
end
actor = actor.nextInCell[cell]
end
else
for __ = 1, cell.numActors do
if HasIdentifierFromTable(actor, identifier) and not alreadyEnumerated[actor] and (condition == nil or condition(actor.host, ...)) then
alreadyEnumerated[actor] = true
dx = actor.x[actor.anchor] - x
dy = actor.y[actor.anchor] - y
if dx*dx + dy*dy < rangeSquared then
returnTable[#returnTable + 1] = actor.host
end
end
actor = actor.nextInCell[cell]
end
end
end
end
end
for key, __ in pairs(alreadyEnumerated) do
alreadyEnumerated[key] = nil
end
return returnTable
end
---Performs the action on all objects that have the specified identifier. Identifier can be a string or a table. If it is a table, the last entry must be MATCHING_TYPE_ANY or MATCHING_TYPE_ALL. Optional condition to specify an additional filter function, which takes the enumerated objects as an argument and returns a boolean. Additional arguments are passed into both the filter and the action function.
---@param action function
---@param x number
---@param y number
---@param range number
---@param identifier string | table
---@param condition? function
---@vararg any
function ALICE_ForAllObjectsInRangeDo(action, x, y, range, identifier, condition, ...)
local list = ALICE_EnumObjectsInRange(x, y, range, identifier, condition, ...)
for __, object in ipairs(list) do
action(object, ...)
end
ReturnTable(list)
end
---Enum functions return a table with all objects that have the specified identifier. Identifier can be a string or a table. If it is a table, the last entry must be MATCHING_TYPE_ANY or MATCHING_TYPE_ALL. Optional condition to specify an additional filter function, which takes the enumerated objects as an argument and returns a boolean. Additional arguments are passed into the filter function.
---@param minx number
---@param miny number
---@param maxx number
---@param maxy number
---@param identifier string | table
---@param condition? function
---@vararg any
---@return table
function ALICE_EnumObjectsInRect(minx, miny, maxx, maxy, identifier, condition, ...)
local returnTable = GetTable()
ResetCoordinateLookupTables()
local minX = min(NUM_CELLS_X, max(1, (NUM_CELLS_X*(minx - MAP_MIN_X)/MAP_SIZE_X) // 1 + 1))
local minY = min(NUM_CELLS_Y, max(1, (NUM_CELLS_Y*(miny - MAP_MIN_Y)/MAP_SIZE_Y) // 1 + 1))
local maxX = min(NUM_CELLS_X, max(1, (NUM_CELLS_X*(maxx - MAP_MIN_X)/MAP_SIZE_X) // 1 + 1))
local maxY = min(NUM_CELLS_Y, max(1, (NUM_CELLS_Y*(maxy - MAP_MIN_Y)/MAP_SIZE_Y) // 1 + 1))
local x
local y
local identifierIsString = type(identifier) == "string"
local actor, cell
for X = minX, maxX do
for Y = minY, maxY do
cell = CELL_LIST[X][Y]
actor = cell.first
if actor then
if identifierIsString then
for __ = 1, cell.numActors do
if actor.identifier[identifier] and not alreadyEnumerated[actor] and (condition == nil or condition(actor.host, ...)) then
alreadyEnumerated[actor] = true
x = actor.x[actor.anchor]
y = actor.y[actor.anchor]
if x > minx and x < maxx and y > miny and y < maxy then
returnTable[#returnTable + 1] = actor.host
end
end
actor = actor.nextInCell[cell]
end
else
for __ = 1, cell.numActors do
if HasIdentifierFromTable(actor, identifier) and not alreadyEnumerated[actor] and (condition == nil or condition(actor.host, ...)) then
alreadyEnumerated[actor] = true
x = actor.x[actor.anchor]
y = actor.y[actor.anchor]
if x > minx and x < maxx and y > miny and y < maxy then
returnTable[#returnTable + 1] = actor.host
end
end
actor = actor.nextInCell[cell]
end
end
end
end
end
for key, __ in pairs(alreadyEnumerated) do
alreadyEnumerated[key] = nil
end
return returnTable
end
---Performs the action on all objects that have the specified identifier. Identifier can be a string or a table. If it is a table, the last entry must be MATCHING_TYPE_ANY or MATCHING_TYPE_ALL. Optional condition to specify an additional filter function, which takes the enumerated objects as an argument and returns a boolean. Additional arguments are passed into both the filter and the action function.
---@param action function
---@param minx number
---@param miny number
---@param maxx number
---@param maxy number
---@param identifier string | table
---@param condition? function
---@vararg any
function ALICE_ForAllObjectsInRectDo(action, minx, miny, maxx, maxy, identifier, condition, ...)
local list = ALICE_EnumObjectsInRect(minx, miny, maxx, maxy, identifier, condition, ...)
for __, object in ipairs(list) do
action(object, ...)
end
ReturnTable(list)
end
---Enum functions return a table with all objects that have the specified identifier. Identifier can be a string or a table. If it is a table, the last entry must be MATCHING_TYPE_ANY or MATCHING_TYPE_ALL. Optional condition to specify an additional filter function, which takes the enumerated objects as an argument and returns a boolean. Additional arguments are passed into the filter function.
---@param x1 number
---@param y1 number
---@param x2 number
---@param y2 number
---@param halfWidth number
---@param identifier string | table
---@param condition? function
---@vararg any
---@return table
function ALICE_EnumObjectsInLineSegment(x1, y1, x2, y2, halfWidth, identifier, condition, ...)
if x2 == x1 then
return ALICE_EnumObjectsInRect(x1 - halfWidth, min(y1, y2), x1 + halfWidth, max(y1, y2), identifier, condition, ...)
end
ResetCoordinateLookupTables()
local returnTable = GetTable()
local cells = GetTable()
local angle = atan(y2 - y1, x2 - x1)
local cosAngle = math.cos(angle)
local sinAngle = math.sin(angle)
local Xmin, Xmax, Ymin, Ymax, cRight, cLeft, slope
local XminRight = (NUM_CELLS_X*(x1 + halfWidth*sinAngle - MAP_MIN_X)/MAP_SIZE_X)
local XmaxRight = (NUM_CELLS_X*(x2 + halfWidth*sinAngle - MAP_MIN_X)/MAP_SIZE_X)
local YminRight = (NUM_CELLS_Y*(y1 - halfWidth*cosAngle - MAP_MIN_Y)/MAP_SIZE_Y)
local YmaxRight = (NUM_CELLS_Y*(y2 - halfWidth*cosAngle - MAP_MIN_Y)/MAP_SIZE_Y)
local XminLeft = (NUM_CELLS_X*(x1 - halfWidth*sinAngle - MAP_MIN_X)/MAP_SIZE_X)
local XmaxLeft = (NUM_CELLS_X*(x2 - halfWidth*sinAngle - MAP_MIN_X)/MAP_SIZE_X)
local YminLeft = (NUM_CELLS_Y*(y1 + halfWidth*cosAngle - MAP_MIN_Y)/MAP_SIZE_Y)
local YmaxLeft = (NUM_CELLS_Y*(y2 + halfWidth*cosAngle - MAP_MIN_Y)/MAP_SIZE_Y)
slope = (y2 - y1)/(x2 - x1)
cRight = YminRight - XminRight*slope
cLeft = YminLeft - XminLeft*slope
if x2 > x1 then
if y2 > y1 then
Ymin = min(NUM_CELLS_Y, max(1, YminRight // 1)) + 1
Ymax = min(NUM_CELLS_Y, max(1, YmaxLeft // 1)) + 1
for j = Ymin, Ymax do
Xmin = min(NUM_CELLS_X, max(1, max((j - 1 - cLeft)/slope, XminLeft) // 1 + 1))
Xmax = min(NUM_CELLS_X, max(1, min((j - cRight)/slope, XmaxRight) // 1 + 1))
for i = Xmin, Xmax do
cells[#cells + 1] = CELL_LIST[i][j]
end
end
else
Ymin = min(NUM_CELLS_Y, max(1, YmaxRight // 1)) + 1
Ymax = min(NUM_CELLS_Y, max(1, YminLeft // 1)) + 1
for j = Ymin, Ymax do
Xmin = min(NUM_CELLS_X, max(1, max((j - cRight)/slope, XminRight) // 1 + 1))
Xmax = min(NUM_CELLS_X, max(1, min((j - 1 - cLeft)/slope, XmaxLeft) // 1 + 1))
for i = Xmin, Xmax do
cells[#cells + 1] = CELL_LIST[i][j]
end
end
end
else
if y2 > y1 then
Ymin = min(NUM_CELLS_Y, max(1, YminLeft // 1)) + 1
Ymax = min(NUM_CELLS_Y, max(1, YmaxRight // 1)) + 1
for j = Ymin, Ymax do
Xmin = min(NUM_CELLS_X, max(1, max((j - cLeft)/slope, XmaxLeft) // 1 + 1))
Xmax = min(NUM_CELLS_X, max(1, min((j - 1 - cRight)/slope, XminRight) // 1 + 1))
for i = Xmin, Xmax do
cells[#cells + 1] = CELL_LIST[i][j]
end
end
else
Ymin = min(NUM_CELLS_Y, max(1, YmaxLeft // 1)) + 1
Ymax = min(NUM_CELLS_Y, max(1, YminRight // 1)) + 1
for j = Ymin, Ymax do
Xmin = min(NUM_CELLS_X, max(1, max((j - 1 - cRight)/slope, XmaxRight) // 1 + 1))
Xmax = min(NUM_CELLS_X, max(1, min((j - cLeft)/slope, XminLeft) // 1 + 1))
for i = Xmin, Xmax do
cells[#cells + 1] = CELL_LIST[i][j]
end
end
end
end
local identifierIsString = type(identifier) == "string"
local actor
local maxDist = sqrt((x2 - x1)^2 + (y2 - y1)^2)
local dx, dy, xPrime, yPrime
for __, cell in ipairs(cells) do
actor = cell.first
if actor then
if identifierIsString then
for __ = 1, cell.numActors do
if actor.identifier[identifier] and not alreadyEnumerated[actor] and (condition == nil or condition(actor.host, ...)) then
alreadyEnumerated[actor] = true
dx = actor.x[actor.anchor] - x1
dy = actor.y[actor.anchor] - y1
xPrime = cosAngle*dx + sinAngle*dy
yPrime = -sinAngle*dx + cosAngle*dy
if yPrime < halfWidth and yPrime > -halfWidth and xPrime > 0 and xPrime < maxDist then
returnTable[#returnTable + 1] = actor.host
end
end
actor = actor.nextInCell[cell]
end
else
for __ = 1, cell.numActors do
if HasIdentifierFromTable(actor, identifier) and not alreadyEnumerated[actor] and (condition == nil or condition(actor.host, ...)) then
alreadyEnumerated[actor] = true
dx = actor.x[actor.anchor] - x1
dy = actor.y[actor.anchor] - y1
xPrime = cosAngle*dx + sinAngle*dy
yPrime = -sinAngle*dx + cosAngle*dy
if yPrime < halfWidth and yPrime > -halfWidth and xPrime > 0 and xPrime < maxDist then
returnTable[#returnTable + 1] = actor.host
end
end
actor = actor.nextInCell[cell]
end
end
end
end
ReturnTable(cells)
for key, __ in pairs(alreadyEnumerated) do
alreadyEnumerated[key] = nil
end
return returnTable
end
---Performs the action on all objects that have the specified identifier. Identifier can be a string or a table. If it is a table, the last entry must be MATCHING_TYPE_ANY or MATCHING_TYPE_ALL. Optional condition to specify an additional filter function, which takes the enumerated objects as an argument and returns a boolean. Additional arguments are passed into both the filter and the action function.
---@param action function
---@param x1 number
---@param y1 number
---@param x2 number
---@param y2 number
---@param halfWidth number
---@param identifier string | table
---@param condition? function
---@vararg any
function ALICE_ForAllObjectsInLineSegmentDo(action, x1, y1, x2, y2, halfWidth, identifier, condition, ...)
local list = ALICE_EnumObjectsInLineSegment(x1, y1, x2, y2, halfWidth, identifier, condition, ...)
for __, object in ipairs(list) do
action(object, ...)
end
ReturnTable(list)
end
---Returns the closest object to a point from among objects with the specified identifier. Identifier can be a string or a table. If it is a table, the last entry must be MATCHING_TYPE_ANY or MATCHING_TYPE_ALL. Optional condition to specify an additional filter function, which takes the enumerated objects as an argument and returns a boolean. Additional arguments are passed into the filter function.
---@param x number
---@param y number
---@param identifier string | table
---@param cutOffDistance? number
---@param condition? function
---@vararg any
---@return Object | nil
function ALICE_GetClosestObject(x, y, identifier, cutOffDistance, condition, ...)
ResetCoordinateLookupTables()
cutOffDistance = cutOffDistance or 99999
local minX = min(NUM_CELLS_X, max(1, (NUM_CELLS_X*(x - cutOffDistance - MAP_MIN_X)/MAP_SIZE_X) // 1 + 1))
local minY = min(NUM_CELLS_Y, max(1, (NUM_CELLS_Y*(y - cutOffDistance - MAP_MIN_Y)/MAP_SIZE_Y) // 1 + 1))
local maxX = min(NUM_CELLS_X, max(1, (NUM_CELLS_X*(x + cutOffDistance - MAP_MIN_X)/MAP_SIZE_X) // 1 + 1))
local maxY = min(NUM_CELLS_Y, max(1, (NUM_CELLS_Y*(y + cutOffDistance - MAP_MIN_Y)/MAP_SIZE_Y) // 1 + 1))
local dx, dy
local closestDistSquared = cutOffDistance*cutOffDistance
local closestObject, thisDistSquared
local identifierIsString = type(identifier) == "string"
local actor, cell
for X = minX, maxX do
for Y = minY, maxY do
cell = CELL_LIST[X][Y]
actor = cell.first
if actor then
if identifierIsString then
for __ = 1, cell.numActors do
dx = actor.x[actor.anchor] - x
dy = actor.y[actor.anchor] - y
thisDistSquared = dx*dx + dy*dy
if thisDistSquared < closestDistSquared and actor.identifier[identifier] and (condition == nil or condition(actor.host, ...)) and not actor.isSuspended then
closestDistSquared = thisDistSquared
closestObject = actor.host
end
actor = actor.nextInCell[cell]
end
else
for __ = 1, cell.numActors do
dx = actor.x[actor.anchor] - x
dy = actor.y[actor.anchor] - y
thisDistSquared = dx*dx + dy*dy
if thisDistSquared < closestDistSquared and HasIdentifierFromTable(actor, identifier) and (condition == nil or condition(actor.host, ...)) and not actor.isSuspended then
closestDistSquared = thisDistSquared
closestObject = actor.host
end
actor = actor.nextInCell[cell]
end
end
end
end
end
return closestObject
end
--Debug API
--===========================================================================================================================================================
---Display warnings in the actor tooltips and crash messages of actors interacting with the specified function or list of functions if the listed fields are not present in the host table. requiredOnMale and requiredOnFemale control whether the field is expected to exist in the host table of the initiating (male) or receiving (female) actor of the interaction. Strings or string sequences specify fields that always need to be present. A table entry {optionalField = requiredField} denotes that requiredField must be present only if optionalField is also present in the table. requiredField can be a string or string sequence.
---@param whichFunc function | function[]
---@param requiredOnMale boolean
---@param requiredOnFemale boolean
---@vararg string | string[] | table<string,string|table>
function ALICE_FuncRequireFields(whichFunc, requiredOnMale, requiredOnFemale, ...)
local entry
whichFunc = type(whichFunc) == "function" and {whichFunc} or whichFunc
local actors = {
male = requiredOnMale or nil,
female = requiredOnFemale or nil
}
for actorType, __ in pairs(actors) do
for __, func in pairs(whichFunc) do
functionRequiredFields[func] = functionRequiredFields[func] or {}
if requiredOnMale then
functionRequiredFields[func][actorType] = functionRequiredFields[func][actorType] or {}
for i = 1, select("#", ...) do
entry = select(i, ...)
entry = type(entry) == "string" and {entry} or entry
for key, value in pairs(entry) do
if type(key) == "string" then
if type(value) == "table" then
for __, subvalue in ipairs(value) do
functionRequiredFields[func][actorType][subvalue] = key
end
else
functionRequiredFields[func][actorType][value] = key
end
else
functionRequiredFields[func][actorType][value] = true
end
end
end
end
end
end
end
---Sets the name of a function when displayed in debug mode.
---@param whichFunc function
---@param name string
function ALICE_FuncSetName(whichFunc, name)
debug.functionName[whichFunc] = name
end
---Enable or disable debug mode.
---@param enable? boolean
function ALICE_Debug(enable)
if enable == nil or (not debug.enabled and enable == true) or (debug.enabled and enable == false) then
EnableDebugMode()
end
end
---List all global actors.
function ALICE_ListGlobals()
local message = "List of all global actors:"
for __, actor in ipairs(celllessActorList) do
if actor.isGlobal then
message = message .. "\n" .. Identifier2String(actor.identifier) .. ", Unique: " .. actor.unique
end
end
Warning(message)
end
---Select the actor of the specified object if qualifier is an object, the first actor encountered with the specified identifier if qualifier is a string, or the actor with the specified unique number if qualifier is an integer. Requires debug mode.
---@param qualifier Object | integer | string
function ALICE_Select(qualifier)
if not debug.enabled then
Warning("|cffff0000Error:|r ALICE_Select is only available in debug mode.")
return
end
if type(qualifier) == "number" then
for __, actor in ipairs(actorList) do
if actor.unique == qualifier then
Select(actor)
SetCameraPosition(ALICE_GetCoordinates2D(actor))
return
end
end
Warning("\nNo actor exists with the specified unique number.")
elseif type(qualifier) == "string" then
for __, actor in ipairs(actorList) do
if actor.identifier[qualifier] then
Select(actor)
SetCameraPosition(ALICE_GetCoordinates2D(actor))
return
end
end
Warning("\nNo actor exists with the specified identifier.")
else
local actor = GetActor(qualifier)
if actor then
Select(qualifier)
SetCameraPosition(ALICE_GetCoordinates2D(actor))
else
Warning("\nNo actor exists for the specified object.")
end
end
end
---Returns true if one of the actors in the current pair is selected.
---@return boolean
function ALICE_PairIsSelected()
return debug.selectedActor == currentPair[0x1] or debug.selectedActor == currentPair[0x2]
end
---Create a lightning effect between the objects of the current pair. Optional lightning type argument.
---@param lightningType? string
function ALICE_PairVisualize(lightningType)
VisualizationLightning(currentPair, lightningType or "DRAL")
end
---Pause the entire cycle. Optional pauseGame parameter to pause all units on the map.
---@param pauseGame? boolean
function ALICE_Halt(pauseGame)
cycle.isHalted = true
PauseTimer(timers.MASTER)
if config.INTERPOLATION_INTERVAL then
PauseTimer(timers.INTERPOLATION)
end
if pauseGame then
ALICE_ForAllObjectsDo(PauseUnit, "unit", nil, true)
end
debug.gameIsPaused = pauseGame
end
---Go to the next step in the cycle.
function ALICE_NextStep()
Main(true)
end
---Resume the entire cycle.
function ALICE_Resume()
cycle.isHalted = false
TimerStart(timers.MASTER, config.MIN_INTERVAL, true, Main)
if config.INTERPOLATION_INTERVAL then
TimerStart(timers.INTERPOLATION, config.INTERPOLATION_INTERVAL, true, Interpolate)
end
if debug.gameIsPaused then
ALICE_ForAllObjectsDo(PauseUnit, "unit", nil, false)
debug.gameIsPaused = false
end
end
---Prints out statistics showing which functions are occupying which percentage of the calculations.
function ALICE_Statistics()
local countActivePairs = 0
local functionCount = {}
--Every Step Cycle
local thisPair = firstEveryStepPair
for __ = 1, numEveryStepPairs do
thisPair = thisPair[0x5]
if not thisPair.destructionQueued then
functionCount[thisPair[0x8]] = (functionCount[thisPair[0x8]] or 0) + 1
countActivePairs = countActivePairs + 1
end
end
local currentCounter = cycle.counter + 1
--Variable Step Cycle
local pairsThisStep = whichPairs[currentCounter]
for i = 1, numPairs[currentCounter] do
thisPair = pairsThisStep[i]
if not thisPair.destructionQueued then
functionCount[thisPair[0x8]] = (functionCount[thisPair[0x8]] or 0) + 1
countActivePairs = countActivePairs + 1
end
end
if countActivePairs == 0 then
return "\nThere are no functions currently being evaluated."
end
local statistic = "Here is a breakdown of the functions currently being evaluated:"
local sortedKeys = {}
local count = 0
for key, __ in pairs(functionCount) do
count = count + 1
sortedKeys[count] = key
end
sort(sortedKeys, function(a, b) return functionCount[a] > functionCount[b] end)
for __, functionType in ipairs(sortedKeys) do
if (100*functionCount[functionType]/countActivePairs) > 0.1 then
statistic = statistic .. "\n" .. string.format("\x25.2f", 100*functionCount[functionType]/countActivePairs) .. "\x25 |cffffcc00" .. Function2String(functionType) .. "|r |cffaaaaaa(" .. functionCount[functionType] .. ")|r"
end
end
print(statistic)
end
---Continuously prints the cycle evaluation time and the number of actors, pair interactions, and cell checks until disabled.
function ALICE_Benchmark()
debug.benchmark = not debug.benchmark
end
---Prints the values of _G.whichVar[host], if _G.whichVar exists, as well as host.whichVar, if the host is a table, in the actor tooltips in debug mode. You can list multiple variables.
---@vararg ... string
function ALICE_TrackVariables(...)
for i = 1, select("#", ...) do
debug.trackedVariables[select(i, ...)] = true
end
end
---Attempts to find the pair of the specified objects and prints the state of that pair. Pass integers to select by unique numbers. Possible return values are "active", "outofrange", "paused", "disabled", and "uninitialized". Optional keyword parameters to specify actor with the keyword in its identifier for objects with multiple actors.
---@param objectA Object | integer
---@param objectB Object | integer
---@param keywordA? string
---@param keywordB? string
---@return string
function ALICE_GetPairState(objectA, objectB, keywordA, keywordB)
local actorA, actorB
if type(objectA) == "number" then
for __, actor in ipairs(actorList) do
if actor.unique == objectA then
actorA = actor
break
end
end
if actorA == nil then
Warning("\nNo actor exists with unique number " .. objectA .. ".")
end
else
actorA = GetActor(objectA, keywordA)
if actorA == nil then
Warning("\nNo actor exists for the specified object.")
end
end
if type(objectB) == "number" then
for __, actor in ipairs(actorList) do
if actor.unique == objectB then
actorB = actor
break
end
end
if actorB == nil then
Warning("\nNo actor exists with unique number " .. objectB .. ".")
end
else
actorB = GetActor(objectB, keywordB)
if actorB == nil then
Warning("\nNo actor exists for the specified object.")
end
end
local thisPair = pairList[actorA][actorB] or pairList[actorB][actorA]
if thisPair then
if thisPair.paused then
return "paused"
elseif (thisPair[0x7] and thisPair[0x6]) or (not thisPair[0x7] and thisPair[0x5] ~= DO_NOT_EVALUATE) then
return "active"
else
return "outofrange"
end
elseif pairingExcluded[actorA][actorB] then
return "disabled"
else
return "uninitialized"
end
end
---Create lightning effects around all cells.
function ALICE_VisualizeAllCells()
debug.visualizeAllCells = not debug.visualizeAllCells
if debug.visualizeAllCells then
for X = 1, NUM_CELLS_X do
for Y = 1, NUM_CELLS_Y do
local minx = MAP_MIN_X + (X-1)/NUM_CELLS_X*MAP_SIZE_X
local miny = MAP_MIN_Y + (Y-1)/NUM_CELLS_Y*MAP_SIZE_Y
local maxx = MAP_MIN_X + X/NUM_CELLS_X*MAP_SIZE_X
local maxy = MAP_MIN_Y + Y/NUM_CELLS_Y*MAP_SIZE_Y
CELL_LIST[X][Y].horizontalLightning = AddLightning("DRAM", false, minx, miny, maxx, miny)
SetLightningColor(CELL_LIST[X][Y].horizontalLightning, 1, 1, 1, 0.35)
CELL_LIST[X][Y].verticalLightning = AddLightning("DRAM", false, maxx, miny, maxx, maxy)
SetLightningColor(CELL_LIST[X][Y].verticalLightning, 1, 1, 1, 0.35)
end
end
else
for X = 1, NUM_CELLS_X do
for Y = 1, NUM_CELLS_Y do
DestroyLightning(CELL_LIST[X][Y].horizontalLightning)
DestroyLightning(CELL_LIST[X][Y].verticalLightning)
end
end
end
end
---Creates arrows above all non-global actors.
function ALICE_VisualizeAllActors()
debug.visualizeAllActors = not debug.visualizeAllActors
if debug.visualizeAllActors then
for __, actor in ipairs(actorList) do
if not actor.isGlobal and actor ~= debug.selectedActor then
CreateVisualizer(actor)
end
end
else
for __, actor in ipairs(actorList) do
if not actor.isGlobal and actor ~= debug.selectedActor then
DestroyEffect(actor.visualizer)
end
end
end
end
--Pair Utility API
--===========================================================================================================================================================
---Returns true if the owners of the objects in the current pair are allies.
---@return boolean
function ALICE_PairIsFriend()
local actorA = currentPair[0x1]
local actorB = currentPair[0x2]
local ownerA = actorA.getOwner(actorA.host)
local ownerB = actorB.getOwner(actorB.host)
return IsPlayerAlly(ownerA, ownerB)
end
---Returns true if the owners of the objects in the current pair are enemies.
---@return boolean
function ALICE_PairIsEnemy()
local actorA = currentPair[0x1]
local actorB = currentPair[0x2]
local ownerA = actorA.getOwner(actorA.host)
local ownerB = actorB.getOwner(actorB.host)
return IsPlayerEnemy(ownerA, ownerB)
end
---Changes the interactionFunc of the pair currently being evaluated. You cannot replace a function without a return value with one that has a return value.
---@param whichFunc function
function ALICE_PairSetInteractionFunc(whichFunc)
if currentPair[0x2] == SELF_INTERACTION_ACTOR then
currentPair[0x1].selfInteractions[currentPair[0x8]] = nil
currentPair[0x1].selfInteractions[whichFunc] = currentPair
end
currentPair[0x8] = whichFunc
end
---Disables interactions between the actors of the current pair after this one.
function ALICE_PairDisable()
if not functionIsUnbreakable[currentPair[0x8]] and currentPair[0x2] ~= SELF_INTERACTION_ACTOR then
pairingExcluded[currentPair[0x1]][currentPair[0x2]] = true
pairingExcluded[currentPair[0x2]][currentPair[0x1]] = true
end
if currentPair.destructionQueued then
return
end
if currentPair[0x2] == SELF_INTERACTION_ACTOR then
currentPair[0x1].selfInteractions[currentPair[0x8]] = nil
end
currentPair.destructionQueued = true
AddDelayedCallback(DestroyPair, currentPair)
end
---Modifies the return value of an interactionFunc so that, on average, the interval is the specified value, even if it isn't an integer multiple of the minimum interval.
---@param value number
---@return number
function ALICE_PairPreciseInterval(value)
local ALICE_MIN_INTERVAL = config.MIN_INTERVAL
local data = ALICE_PairLoadData()
local numSteps = (value*INV_MIN_INTERVAL + 1) // 1
local newDelta = (data.returnDelta or 0) + value - ALICE_MIN_INTERVAL*numSteps
if newDelta > 0.5*ALICE_MIN_INTERVAL then
newDelta = newDelta - ALICE_MIN_INTERVAL
numSteps = numSteps + 1
data.returnDelta = newDelta
elseif newDelta < -0.5*ALICE_MIN_INTERVAL then
newDelta = newDelta + ALICE_MIN_INTERVAL
numSteps = numSteps - 1
data.returnDelta = newDelta
if numSteps == 0 and not currentPair.destructionQueued then
currentPair[0x8](currentPair[0x3], currentPair[0x4])
end
else
data.returnDelta = newDelta
end
return ALICE_MIN_INTERVAL*numSteps
end
---Returns false if this function was invoked for another pair that has the same interactionFunc and the same receiving actor. Otherwise, returns true. In other words, only one pair can execute the code within an ALICE_PairIsUnoccupied() block.
function ALICE_PairIsUnoccupied()
if currentPair[0x2][currentPair[0x8]] and currentPair[0x2][currentPair[0x8]] ~= currentPair then
return false
else
--Store for the female actor at the key of the interaction func the current pair as occupying that slot, blocking other pairs.
currentPair[0x2][currentPair[0x8]] = currentPair
return true
end
end
---Returns the remaining cooldown for this pair, then invokes a cooldown of the specified duration. Optional cooldownType parameter to create and differentiate between multiple separate cooldowns.
---@param duration number
---@param cooldownType? string
---@return number
function ALICE_PairCooldown(duration, cooldownType)
currentPair.cooldown = currentPair.cooldown or GetTable()
local key = cooldownType or "default"
local cooldownExpiresStep = currentPair.cooldown[key]
if cooldownExpiresStep == nil or cooldownExpiresStep <= cycle.unboundCounter then
currentPair.cooldown[key] = cycle.unboundCounter + (duration*INV_MIN_INTERVAL + 1) // 1
return 0
else
return (cooldownExpiresStep - cycle.unboundCounter)*config.MIN_INTERVAL
end
end
---Returns a table unique to the pair currently being evaluated, which can be used to read and write data. Optional argument to set a metatable for the data table.
---@param whichMetatable? table
---@return table
function ALICE_PairLoadData(whichMetatable)
if currentPair.userData == nil then
currentPair.userData = GetTable()
setmetatable(currentPair.userData, whichMetatable)
end
return currentPair.userData
end
---Returns true if this is the first time this function was invoked for the current pair, otherwise false. Resets when the objects in the pair leave the interaction range.
---@return boolean
function ALICE_PairIsFirstContact()
if currentPair.hadContact == nil then
currentPair.hadContact = true
return true
else
return false
end
end
---Calls the initFunc with the hosts as arguments whenever a pair is created with the specified interactions.
---@param whichFunc function
---@param initFunc function
function ALICE_FuncSetInit(whichFunc, initFunc)
functionInitializer[whichFunc] = initFunc
end
---Executes the function onDestroyFunc(objectA, objectB, pairData) when a pair using the specified function is destroyed or a callback using that function expires or is disabled. Only one callback per function.
---@param whichFunc function
---@param onDestroyFunc function
function ALICE_FuncSetOnDestroy(whichFunc, onDestroyFunc)
functionOnDestroy[whichFunc] = onDestroyFunc
end
---Executes the function onBreakFunc(objectA, objectB, pairData, wasDestroyed) when a pair using the specified function is destroyed or the actors leave interaction range. Only one callback per function.
---@param whichFunc function
---@param onBreakFunc function
function ALICE_FuncSetOnBreak(whichFunc, onBreakFunc)
functionOnBreak[whichFunc] = onBreakFunc
end
---Executes the function onResetFunc(objectA, objectB, pairData, wasDestroyed) when a pair using the specified function is destroyed, the actors leave interaction range, or the ALICE_PairReset function is called, but only if ALICE_PairIsFirstContact has been called previously. Only one callback per function.
---@param whichFunc function
---@param onResetFunc function
function ALICE_FuncSetOnReset(whichFunc, onResetFunc)
functionOnReset[whichFunc] = onResetFunc
end
---Purge pair data, call onDestroy method and reset ALICE_PairIsFirstContact and ALICE_PairIsUnoccupied functions.
function ALICE_PairReset()
if currentPair.hadContact then
if functionOnReset[currentPair[0x8]] and not cycle.isCrash then
functionOnReset[currentPair[0x8]](currentPair[0x3], currentPair[0x4], currentPair.userData, false)
end
currentPair.hadContact = nil
end
if currentPair[0x2][currentPair[0x8]] == currentPair then
currentPair[0x2][currentPair[0x8]] = nil
end
end
--Repeatedly calls the interaction function of the current pair at a rate of the ALICE_Config.INTERPOLATION_INTERVAL until the next main step. A true is passed into the interaction function as the third parameter if it is called from within the interpolation loop.
function ALICE_PairInterpolate()
if not isInterpolated then
interpolatedPairs[#interpolatedPairs + 1] = currentPair
end
end
--Widget API
--===========================================================================================================================================================
---Widgets with the specified fourCC codes will always receive actors, indepedent of the config.
---@vararg string | integer
function ALICE_IncludeTypes(...)
for i = 1, select("#", ...) do
local whichType = select(i, ...)
if type(whichType) == "string" then
widgets.idInclusions[FourCC(whichType)] = true
else
widgets.idInclusions[whichType] = true
end
end
end
---Widgets with the specified fourCC codes will not receive actors, indepedent of the config.
---@vararg string | integer
function ALICE_ExcludeTypes(...)
for i = 1, select("#", ...) do
local whichType = select(i, ...)
if type(whichType) == "string" then
widgets.idExclusions[FourCC(whichType)] = true
else
widgets.idExclusions[whichType] = true
end
end
end
---Injects the functions listed in the hookTable into the hooks created by ALICE. The hookTable can have the keys: onUnitEnter - The listed function is called for all preplaced units and whenever a unit enters the map or a hero is revived. onUnitDeath - The listed function is called when a unit dies. onUnitRevive - The listed function is called when a nonhero unit is revived. onUnitRemove - The listed function is called when a unit is removed from the game or its corpse decays fully. onUnitChangeOwner - The listed function is called when a unit changes owner. onDestructableEnter - The listed function is called for all preplaced destructables and whenever a destructable is created. onDestructableDestroy - The listed function is called when a destructable dies or is removed. onItemEnter - The listed function is called for all preplaced items and whenever an item is dropped or created. onItemDestroy - The listed function is called when an item is destroyed, removed, or picked up.
---@param hookTable table
function ALICE_OnWidgetEvent(hookTable)
insert(eventHooks.onUnitEnter, hookTable.onUnitEnter)
insert(eventHooks.onUnitDeath, hookTable.onUnitDeath)
insert(eventHooks.onUnitRevive, hookTable.onUnitRevive)
insert(eventHooks.onUnitRemove, hookTable.onUnitRemove)
insert(eventHooks.onUnitChangeOwner, hookTable.onUnitChangeOwner)
insert(eventHooks.onDestructableEnter, hookTable.onDestructableEnter)
insert(eventHooks.onDestructableDestroy, hookTable.onDestructableDestroy)
insert(eventHooks.onItemEnter, hookTable.onItemEnter)
insert(eventHooks.onItemDestroy, hookTable.onItemDestroy)
for key, __ in pairs(hookTable) do
if not eventHooks[key] then
Warning("|cffff0000Warning:|r Unrecognized key " .. key .. " in hookTable passed to ALICE_OnWidgetEvent.")
end
end
if hookTable.onDeath and not config.UNITS_LEAVE_BEHIND_CORPSES then
Warning("|cffff0000Warning:|r Attempted to create onDeath unit event hook, but ALICE_UNITS_LEAVE_BEHIND_CORPSES is not enabled. Use onRemove instead.")
end
end
--Identifier API
--===========================================================================================================================================================
---Add identifier(s) to an object and pair it with all other objects it is now eligible to be paired with. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param object Object
---@param newIdentifier string | string[]
---@param keyword? string
function ALICE_AddIdentifier(object, newIdentifier, keyword)
local actor = GetActor(object, keyword)
if actor == nil or newIdentifier == nil then
return
end
if type(newIdentifier) == "string" then
actor.identifier[newIdentifier] = true
else
for __, word in ipairs(newIdentifier) do
actor.identifier[word] = true
end
end
AssignActorClass(actor, true, false)
DestroyObsoletePairs(actor)
AddDelayedCallback(Flicker, actor)
end
---Remove identifier(s) from an object and remove all pairings with objects it is no longer eligible to be paired with. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param object Object
---@param toRemove string | string[]
---@param keyword? string
function ALICE_RemoveIdentifier(object, toRemove, keyword)
local actor = GetActor(object, keyword)
if actor == nil or toRemove == nil then
return
end
if type(toRemove) == "string" then
if actor.identifier[toRemove] == nil then
return
end
actor.identifier[toRemove] = nil
else
local removedSomething = false
for __, word in ipairs(toRemove) do
if actor.identifier[word] then
removedSomething = true
actor.identifier[word] = nil
end
end
if not removedSomething then
return
end
end
AssignActorClass(actor, true, false)
DestroyObsoletePairs(actor)
AddDelayedCallback(Flicker, actor)
end
---Exchanges one of the object's identifier with another. If the old identifier is not found, the new one won't be added. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param object Object
---@param oldIdentifier string
---@param newIdentifier string
---@param keyword? string
function ALICE_SwapIdentifier(object, oldIdentifier, newIdentifier, keyword)
local actor = GetActor(object, keyword)
if actor == nil or oldIdentifier == nil or newIdentifier == nil then
return
end
if actor.identifier[oldIdentifier] == nil then
return
end
actor.identifier[oldIdentifier] = nil
actor.identifier[newIdentifier] = true
AssignActorClass(actor, true, false)
DestroyObsoletePairs(actor)
AddDelayedCallback(Flicker, actor)
end
---Sets the object's identifier to a string or string sequence.
---@param object Object
---@param newIdentifier string | string[]
---@param keyword? string
function ALICE_SetIdentifier(object, newIdentifier, keyword)
local actor = GetActor(object, keyword)
if actor == nil or newIdentifier == nil then
return
end
for word, __ in pairs(actor.identifier) do
actor.identifier[word] = nil
end
if type(newIdentifier) == "string" then
actor.identifier[newIdentifier] = true
else
for __, word in ipairs(newIdentifier) do
actor.identifier[word] = true
end
end
AssignActorClass(actor, true, false)
DestroyObsoletePairs(actor)
AddDelayedCallback(Flicker, actor)
end
---Checks if the object has the specified identifiers. Identifier can be a string or a table. If it is a table, the last entry must be MATCHING_TYPE_ANY or MATCHING_TYPE_ALL. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param object Object
---@param identifier string | table
---@param keyword? string
---@return boolean
function ALICE_HasIdentifier(object, identifier, keyword)
local actor = GetActor(object, keyword)
if actor == nil or identifier == nil then
return false
end
if type(identifier) == "string" then
return actor.identifier[identifier] == true
else
return HasIdentifierFromTable(actor, identifier)
end
end
---Compiles the identifiers of an object into the provided table or a new table. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param object Object
---@param keyword? string
---@param table? table
function ALICE_GetIdentifier(object, keyword, table)
local actor = GetActor(object, keyword)
if actor == nil then
return
end
local returnTable = table or {}
for key, __ in pairs(actor.identifier) do
insert(returnTable, key)
end
sort(returnTable)
return returnTable
end
---Returns the first entry in the given list of identifiers for which an actor exists for the specified object.
---@param object Object
---@vararg ... string
---@return string | nil
function ALICE_FindIdentifier(object, ...)
local identifier
local actorOf = actorOf[object]
if actorOf == nil then
return nil
end
if actorOf.isActor then
for i = 1, select("#", ...) do
identifier = select(i, ...)
if identifier and actorOf.identifier[identifier] then
return identifier
end
end
else
for __, actor in ipairs(actorOf) do
for i = 1, select("#", ...) do
identifier = select(i, ...)
if actor.identifier[identifier] then
return identifier
end
end
end
end
return nil
end
---If table is a table with identifier keys, returns the field that matches with the specified object's identifier. If no match is found, returns table.other. If table is not a table, returns the variable itself. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param table any
---@param object Object
---@param keyword? string
---@return any
function ALICE_FindField(table, object, keyword)
if type(table) ~= "table" then
return table
end
local actor = GetActor(object, keyword)
if actor == nil then
return nil
end
local identifier = actor.identifier
local entry
local level = 0
local conflict = false
for key, value in pairs(table) do
if type(key) == "string" then
if identifier[key] then
if level < 1 then
entry = value
level = 1
elseif level == 1 then
conflict = true
end
end
else
local match = true
for __, tableKey in ipairs(key) do
if not identifier[tableKey] then
match = false
break
end
end
if match then
if #key > level then
entry = value
level = #key
conflict = false
elseif #key == level then
conflict = true
end
end
end
end
if entry == nil and table.other then
return table.other
end
if conflict then
Warning("Return value ambiguous in ALICE_FindField for " .. Identifier2String(object.identifier) .. ".")
return nil
end
return entry
end
--Interaction API
--===========================================================================================================================================================
---Changes the interaction function of the specified object towards the target identifier to the specified function or removes it. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param object Object
---@param target string | string[]
---@param newFunc function | nil
---@param keyword? string
function ALICE_SetInteractionFunc(object, target, newFunc, keyword)
local actor = GetActor(object, keyword)
if actor == nil then
return
end
local oldFunc = actor.interactions[target]
if oldFunc ~= newFunc then
actor.interactions[target] = newFunc
AssignActorClass(actor, false, true)
if newFunc == nil then
DestroyObsoletePairs(actor)
elseif oldFunc == nil then
AddDelayedCallback(Flicker, actor)
else
local next = actor.nextPair
local pair = next[actor.firstPair]
while pair do
if pair[0x8] == oldFunc then
pair[0x8] = newFunc
end
pair = next[pair]
end
end
end
end
---Adds a self-interaction with the specified function to the object. If a self-interaction with that function already exists, nothing happens. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors. Optional data parameter to initialize a data table that can be accessed with ALICE_PairLoadData.
---@param object Object
---@param whichFunc function
---@param keyword? string
---@param data? table
function ALICE_AddSelfInteraction(object, whichFunc, keyword, data)
local actor = GetActor(object, keyword)
if actor == nil then
return
end
if actor.selfInteractions[whichFunc] then
return
end
actor.selfInteractions[whichFunc] = CreatePair(actor, SELF_INTERACTION_ACTOR, whichFunc)
if data then
local pairData = GetTable()
for key, value in pairs(data) do
pairData[key] = value
end
actor.selfInteractions[whichFunc].userData = pairData
end
end
---Removes the self-interaction with the specified function from the object. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param object Object
---@param whichFunc function
---@param keyword? string
function ALICE_RemoveSelfInteraction(object, whichFunc, keyword)
local actor = GetActor(object, keyword)
if actor == nil then
return
end
if actor.selfInteractions[whichFunc] == nil then
return
end
local pair = actor.selfInteractions[whichFunc]
actor.selfInteractions[whichFunc] = nil
pair.destructionQueued = true
AddDelayedCallback(DestroyPair, pair)
end
---Checks if the object has a self-interaction with the specified function. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param object Object
---@param whichFunc function
---@param keyword? string
---@return boolean
function ALICE_HasSelfInteraction(object, whichFunc, keyword)
local actor = GetActor(object, keyword)
if actor == nil then
return false
end
return actor.selfInteractions[whichFunc] ~= nil
end
--Misc API
--===========================================================================================================================================================
---The first interaction of all pairs using this function will be delayed by the specified number.
---@param whichFunc function
---@param delay number
function ALICE_FuncSetDelay(whichFunc, delay)
if delay > config.MAX_INTERVAL then
Warning("|cffff0000Warning:|r Delay specified in ALICE_FuncSetDelay is greater than ALICE_MAX_INTERVAL.")
end
functionDelay[whichFunc] = min(config.MAX_INTERVAL, delay)
end
---Changes the behavior of pairs using the specified function so that the interactions continue to be evaluated when the two objects leave their interaction range. Also changes the behavior of ALICE_PairDisable to not prevent the two object from pairing again.
---@param whichFunc function
function ALICE_FuncSetUnbreakable(whichFunc)
functionIsUnbreakable[whichFunc] = true
end
---Changes the behavior of the specified function such that pairs using this function will persist if a unit is loaded into a transport or an item is picked up by a unit.
---@param whichFunc function
function ALICE_FuncSetUnsuspendable(whichFunc)
functionIsUnsuspendable[whichFunc] = true
end
---Automatically pauses and unpauses all pairs using the specified function whenever the initiating (male) actor is set to stationary/not stationary.
---@param whichFunc function
function ALICE_FuncPauseOnStationary(whichFunc)
functionPauseOnStationary[whichFunc] = true
end
---Checks if an actor exists with the specified identifier for the specified object. Optional strict flag to exclude actors that are anchored to that object.
---@param object Object
---@param identifier string
---@param strict? boolean
---@return boolean
function ALICE_HasActor(object, identifier, strict)
local actor = GetActor(object, identifier)
if actor == nil then
return false
end
return not strict or actor.host == object
end
---Returns the object the specified object is anchored to or itself if there is no anchor.
---@param object Object
---@return Object | nil
function ALICE_GetAnchor(object)
local actor = GetActor(object)
if actor == nil then
return object
end
return actor.originalAnchor
end
---Accesses all objects anchored to the specified object and returns the one with the specified identifier.
---@param object Object
---@param identifier string
---@return Object | nil
function ALICE_GetAnchoredObject(object, identifier)
local actor = GetActor(object, identifier)
if actor == nil then
return nil
end
return actor.host
end
---Sets the value of a flag for the actor of an object to the specified value. To change the isStationary flag, use ALICE_SetStationary instead. Cannot change hasInfiniteRange flag. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param object Object
---@param whichFlag string
---@param value any
---@param keyword? string
function ALICE_SetFlag(object, whichFlag, value, keyword)
local actor = GetActor(object, keyword)
if actor == nil then
return
end
assert(RECOGNIZED_FLAGS[whichFlag], "Flag " .. whichFlag .. " is not recognized.")
assert(SetFlag[whichFlag], "Flag " .. whichFlag .. " cannot be changed with ALICE_SetFlag.")
SetFlag[whichFlag](actor, value)
end
---Returns the value stored for the specified flag of the specified actor. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param object Object
---@param whichFlag string
---@param keyword? string
---@return any
function ALICE_GetFlag(object, whichFlag, keyword)
local actor = GetActor(object, keyword)
if actor == nil then
return
end
assert(RECOGNIZED_FLAGS[whichFlag], "Flag " .. whichFlag .. " is not recognized.")
if whichFlag == "radius" then
return actor.halfWidth
elseif whichFlag == "width" then
return 2*actor.halfWidth
elseif whichFlag == "height" then
return 2*actor.halfHeight
elseif whichFlag == "cellCheckInterval" then
if actor.cellCheckInterval then
return actor.cellCheckInterval*config.MIN_INTERVAL
else
return nil
end
elseif whichFlag == "anchor" then
return actor.originalAnchor
else
return actor[whichFlag]
end
end
---Returns the owner of the specified object. Faster than GetOwningPlayer. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param object Object
---@param keyword? string
---@return player
function ALICE_GetOwner(object, keyword)
local actor = GetActor(object, keyword)
if actor == nil then
return HandleType[object] == "unit" and GetOwningPlayer(object) or nil
end
return actor.getOwner(actor.host)
end
--Pair Access API
--===========================================================================================================================================================
---Restore a pair that has been previously destroyed with ALICE_PairDisable. Returns two booleans. The first denotes whether a pair now exists and the second if it was just created.
---@param objectA Object
---@param objectB Object
---@param keywordA? string
---@param keywordB? string
---@return boolean, boolean
function ALICE_Enable(objectA, objectB, keywordA, keywordB)
local actorA = GetActor(objectA, keywordA)
local actorB = GetActor(objectB, keywordB)
if actorA == nil or actorB == nil then
return false, false
end
if pairingExcluded[actorA][actorB] == nil then
if pairList[actorA][actorB] or pairList[actorB][actorA] then
return true, false
else
return false, false
end
end
pairingExcluded[actorA][actorB] = nil
pairingExcluded[actorB][actorA] = nil
if (not actorA.usesCells or not actorB.usesCells or SharesCellWith(actorA, actorB)) then
local actorAFunc = GetInteractionFunc(actorA, actorB)
local actorBFunc = GetInteractionFunc(actorB, actorA)
if actorAFunc and actorBFunc then
if actorA.priority < actorB.priority then
CreatePair(actorB, actorA, actorBFunc)
return true, true
else
CreatePair(actorA, actorB, actorAFunc)
return true, true
end
elseif actorAFunc then
CreatePair(actorA, actorB, actorAFunc)
return true, true
elseif actorBFunc then
CreatePair(actorB, actorA, actorBFunc)
return true, true
end
end
return false, false
end
---Access the pair for objects A and B and, if it exists, return the data table stored for that pair. If objectB is a function, returns the data of the self-interaction of objectA using the specified function.
---@param objectA Object
---@param objectB Object | function
---@param keywordA? string
---@param keywordB? string
---@return table | nil
function ALICE_AccessData(objectA, objectB, keywordA, keywordB)
local actorA = GetActor(objectA, keywordA)
local actorB
local whichPair
if type(objectB) == "function" then
whichPair = actorA.selfInteractions[objectB]
else
actorB = GetActor(objectB, keywordB)
if actorA == nil or actorB == nil then
return nil
end
whichPair = pairList[actorA][actorB] or pairList[actorB][actorA]
end
if whichPair then
if whichPair.userData then
return whichPair.userData
else
whichPair.userData = GetTable()
return whichPair.userData
end
end
return nil
end
---Access the pair for objects A and B and, if it is paused, unpause it. If objectB is a function, unpauses the self-interaction of objectA using the specified function.
---@param objectA Object
---@param objectB Object | function
---@param keywordA? string
---@param keywordB? string
function ALICE_UnpausePair(objectA, objectB, keywordA, keywordB)
local actorA = GetActor(objectA, keywordA)
local actorB
local whichPair
if type(objectB) == "function" then
whichPair = actorA.selfInteractions[objectB]
else
actorB = GetActor(objectB, keywordB)
if actorA == nil or actorB == nil then
return nil
end
whichPair = pairList[actorA][actorB] or pairList[actorB][actorA]
end
if whichPair then
AddDelayedCallback(UnpausePair, whichPair)
end
end
---Access the pair for objects A and B and, if it exists, perform the specified action. Returns the return value of the action function. The hosts of the pair as well as any additional parameters are passed into the action function. If objectB is a function, access the pair of the self-inteaction of objectA using the specified function.
---@param action function
---@param objectA Object
---@param objectB Object | function
---@param keywordA? string
---@param keywordB? string
---@vararg any
---@return any
function ALICE_GetPairAndDo(action, objectA, objectB, keywordA, keywordB, ...)
local actorA = GetActor(objectA, keywordA)
local actorB
local whichPair
if type(objectB) == "function" then
whichPair = actorA.selfInteractions[objectB]
else
actorB = GetActor(objectB, keywordB)
if actorA == nil or actorB == nil then
return nil
end
whichPair = pairList[actorA][actorB] or pairList[actorB][actorA]
end
if whichPair then
local tempPair = currentPair
currentPair = whichPair
local returnValue = action(whichPair[0x3], whichPair[0x4], ...)
currentPair = tempPair
return returnValue
end
end
---Access all pairs for the object using the specified interactionFunc and perform the specified action. The hosts of the pairs as well as any additional parameters are passed into the action function. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param action function
---@param object Object
---@param whichFunc function
---@param includeInactive? boolean
---@param keyword? string
---@vararg any
function ALICE_ForAllPairsDo(action, object, whichFunc, includeInactive, keyword, ...)
local actor = GetActor(object, keyword)
if actor == nil then
return
end
local DO_NOT_EVALUATE = DO_NOT_EVALUATE
local next = actor.nextPair
local thisPair = next[actor.firstPair]
local tempPair = currentPair
while thisPair do
if thisPair[0x8] == whichFunc and (includeInactive or (thisPair[0x7] and thisPair[0x6] ~= nil) or (not thisPair[0x7] and thisPair[0x5] ~= DO_NOT_EVALUATE)) then
currentPair = thisPair
action(thisPair[0x3], thisPair[0x4], ...)
end
thisPair = next[thisPair]
end
currentPair = tempPair
end
--Optimization API
--===========================================================================================================================================================
---Pauses interactions of the current pair after this one. Resume with an unpause function.
function ALICE_PairPause()
AddDelayedCallback(PausePair, currentPair)
end
---Unpauses all paused interactions of the object. Optional whichFunctions argument, which can be a function or a function sequence, to limit unpausing to pairs using those functions. Optional keyword parameter to specify actor with the keyword in its identifier for an object with multiple actors.
---@param object Object
---@param whichFunctions? function | table
---@param keyword? string
function ALICE_Unpause(object, whichFunctions, keyword)
local actor = GetActor(object, keyword)
if actor == nil then
return
end
if type(whichFunctions) == "table" then
for key, value in ipairs(whichFunctions) do
whichFunctions[value] = true
whichFunctions[key] = nil
end
elseif whichFunctions then
local functionsTable = GetTable()
functionsTable[whichFunctions] = true
whichFunctions = functionsTable
end
AddDelayedCallback(Unpause, actor, whichFunctions)
end
---Sets an object to stationary/not stationary. Will affect all actors attached to the object.
---@param object Object
---@param enable? boolean
function ALICE_SetStationary(object, enable)
objectIsStationary[object] = enable ~= false
if actorOf[object] == nil then
return
end
if actorOf[object].isActor then
if actorOf[object].usesCells then
SetStationary(actorOf[object], enable ~= false)
end
else
for __, actor in ipairs(actorOf[object]) do
if actor.usesCells then
SetStationary(actor, enable ~= false)
end
end
end
end
---Returns whether the specified object is set to stationary.
---@param object Object
function ALICE_IsStationary(object)
return objectIsStationary[object]
end
---The first interaction of all pairs using this function will be delayed by up to the specified number, distributing individual calls over the interval to prevent computation spikes.
---@param whichFunc function
---@param interval number
function ALICE_FuncDistribute(whichFunc, interval)
if interval > config.MAX_INTERVAL then
Warning("|cffff0000Warning:|r Delay specified in ALICE_FuncDistribute is greater than ALICE_MAX_INTERVAL.")
end
functionDelay[whichFunc] = interval
functionDelayIsDistributed[whichFunc] = true
functionDelayCurrent[whichFunc] = 0
end
--Modular API
--===========================================================================================================================================================
---Executes the specified function before an object with the specified identifier is created. The function is called with the host as the parameter.
---@param matchingIdentifier string
---@param whichFunc function
function ALICE_OnCreation(matchingIdentifier, whichFunc)
onCreation.funcs[matchingIdentifier] = onCreation.funcs[matchingIdentifier] or {}
insert(onCreation.funcs[matchingIdentifier], whichFunc)
end
---Add a flag with the specified value to objects with matchingIdentifier when they are created. If a function is provided for value, the returned value of the function will be added.
---@param matchingIdentifier string
---@param flag string
---@param value any
function ALICE_OnCreationAddFlag(matchingIdentifier, flag, value)
if not OVERWRITEABLE_FLAGS[flag] then
error("Flag " .. flag .. " cannot be overwritten with ALICE_OnCreationAddFlag.")
end
onCreation.flags[matchingIdentifier] = onCreation.flags[matchingIdentifier] or {}
onCreation.flags[matchingIdentifier][flag] = value
end
---Adds an additional identifier to objects with matchingIdentifier when they are created. If a function is provided for value, the returned string of the function will be added.
---@param matchingIdentifier string
---@param value string | function
function ALICE_OnCreationAddIdentifier(matchingIdentifier, value)
onCreation.identifiers[matchingIdentifier] = onCreation.identifiers[matchingIdentifier] or {}
insert(onCreation.identifiers[matchingIdentifier], value)
end
---Adds an interaction to all objects with matchingIdentifier when they are created towards objects with the specified keyword in their identifier. To add a self-interaction, use ALICE_OnCreationAddSelfInteraction instead.
---@param matchingIdentifier string
---@param keyword string | string[]
---@param interactionFunc function
function ALICE_OnCreationAddInteraction(matchingIdentifier, keyword, interactionFunc)
onCreation.interactions[matchingIdentifier] = onCreation.interactions[matchingIdentifier] or {}
if onCreation.interactions[matchingIdentifier][keyword] then
Warning("|cffff0000Warning:|r Multiple interactionsFuncs added on creation to " .. matchingIdentifier .. " and " .. keyword .. ". Previous entry was overwritten.")
end
onCreation.interactions[matchingIdentifier][keyword] = interactionFunc
end
---Adds a self-interaction to all objects with matchingIdentifier when they are created.
---@param matchingIdentifier string
---@param selfinteractions function
function ALICE_OnCreationAddSelfInteraction(matchingIdentifier, selfinteractions)
onCreation.selfInteractions[matchingIdentifier] = onCreation.selfInteractions[matchingIdentifier] or {}
insert(onCreation.selfInteractions[matchingIdentifier], selfinteractions)
end
--#endregion
end
if Debug then Debug.beginFile "Hook" end
--ββββββββββββββββββββββββββββββββββββββ
-- Hook version 7.1.0.1
-- Created by: Bribe
-- Contributors: Eikonium, Jampion, MyPad, Wrda
--βββββββββββββββββββββββββββββββββββββββββββββ
---@class Hook.property
---@field next function|Hook.property --Call the next/native function. Also works with any given name (old/native/original/etc.). The args and return values align with the original function.
---@field remove fun(all?: boolean) --Remove the hook. Pass the boolean "true" to remove all hooks.
---@field package tree HookTree --Reference to the tree storing each hook on that particular key in that particular host.
---@field package priority number
---@field package index integer
---@field package hookAsBasicFn? function
----@field package debugId? string
----@field package debugNext? string
---@class Hook: {[integer]: Hook.property, [string]: function}
Hook = {}
do
local looseValuesMT = { __mode = "v" }
local hostKeyTreeMatrix = ---@type table<table, table<any, HookTree>>
setmetatable({
--Already add a hook matrix for _G right away.
[_G] = setmetatable({}, looseValuesMT)
}, looseValuesMT)
---@class HookTree: { [number]: Hook.property }
---@field host table
---@field key unknown --What the function was indexed to (_G items are typically indexed via strings)
---@field hasHookAsBasicFn boolean
---Reindexes a HookTree, inserting or removing a hook and updating the properties of each hook.
---@param tree HookTree
---@param index integer
---@param newHook? table
local function reindexTree(tree, index, newHook)
if newHook then
table.insert(tree, index, newHook)
else
table.remove(tree, index)
end
local top = #tree
local prevHook = tree[index - 1]
-- `table.insert` and `table.remove` shift the elements upwards or downwards,
-- so this loop manually aligns the tree elements with this shift.
for i = index, top do
local currentHook = tree[i]
currentHook.index = i
currentHook.next = (i > 1) and
rawget(prevHook, 'hookAsBasicFn') or
prevHook
--currentHook.debugNext = tostring(currentHook.next)
prevHook = currentHook
end
local topHookBasicFn = rawget(tree[top], 'hookAsBasicFn')
if topHookBasicFn then
if not tree.hasHookAsBasicFn or rawget(tree.host, tree.key) ~= topHookBasicFn then
tree.hasHookAsBasicFn = true
--a different basic function should be called for this hook
--instead of the one that was previously there.
tree.host[tree.key] = topHookBasicFn
end
else
--The below comparison rules out 'nil' and 'true'.
--Q: Why rule out nil?
--A: There is no need to reassign a host hook handler if there is already one in place.
if tree.hasHookAsBasicFn ~= false then
tree.host[tree.key] = function(...)
return tree[#tree](...)
end
end
tree.hasHookAsBasicFn = false
end
end
---@param hookProperty Hook.property
---@param deleteAllHooks? boolean
function Hook.delete(hookProperty, deleteAllHooks)
local tree = hookProperty.tree
hookProperty.tree = nil
if deleteAllHooks or #tree == 1 then
--Reset the host table's native behavior for the hooked key.
tree.host[tree.key] =
(tree[0] ~= DoNothing) and
tree[0] or
nil
hostKeyTreeMatrix[tree.host][tree.key] = nil
else
reindexTree(tree, hookProperty.index)
end
end
---@param hostTableToHook? table
---@param defaultNativeBehavior? function
---@param hookedTableIsMetaTable? boolean
local function setupHostTable(hostTableToHook, defaultNativeBehavior, hookedTableIsMetaTable)
hostTableToHook = hostTableToHook or _G
if hookedTableIsMetaTable or
(defaultNativeBehavior and hookedTableIsMetaTable == nil)
then
hostTableToHook = getmetatable(hostTableToHook) or
getmetatable(setmetatable(hostTableToHook, {}))
end
return hostTableToHook
end
---@param tree HookTree
---@param priority number
local function huntThroughPriorityList(tree, priority)
local index = 1
local topIndex = #tree
repeat
if priority <= tree[index].priority then
break
end
index = index + 1
until index > topIndex
return index
end
---@param hostTableToHook table
---@param key unknown
---@param defaultNativeBehavior? function
---@return HookTree | nil
local function createHookTree(hostTableToHook, key, defaultNativeBehavior)
local nativeFn = rawget(hostTableToHook, key) or
defaultNativeBehavior or
((hostTableToHook ~= _G or type(key) ~= "string") and
DoNothing)
if not nativeFn then
--Logging is used here instead of directly throwing an error, because
--no one can be sure that we're running within a debug-friendly thread.
(Debug and Debug.throwError or print)("Hook Error: No value found for key: " .. tostring(key))
return
end
---@class HookTree
local tree = {
host = hostTableToHook,
key = key,
[0] = nativeFn,
--debugNativeId = tostring(nativeFn)
}
hostKeyTreeMatrix[hostTableToHook][key] = tree
return tree
end
---@param self Hook.property
local function __index(self)
return self.next
end
---@param key unknown Usually `string` (the name of the native you wish to hook)
---@param callbackFn fun(Hook, ...):any The function you want to run when the native is called. The first parameter is type "Hook", and the remaining parameters (and return value(s)) align with the original function.
---@param priority? number Defaults to 0. Hooks are called in order of highest priority down to lowest priority. The native itself has the lowest priority.
---@param hostTableToHook? table Defaults to _G (the table that stores all global variables).
---@param defaultNativeBehavior? function If the native does not exist in the host table, use this default instead.
---@param hookedTableIsMetaTable? boolean Whether to store into the host's metatable instead. Defaults to true if the "default" parameter is given.
---@param hookAsBasicFn? boolean When adding a hook instance, the default behavior is to use the __call metamethod in metatables to govern callbacks. If this is `true`, it will instead use normal function callbacks.
---@return Hook.property
function Hook.add(
key,
callbackFn,
priority,
hostTableToHook,
defaultNativeBehavior,
hookedTableIsMetaTable,
hookAsBasicFn
)
priority = priority or 0
hostTableToHook = setupHostTable(hostTableToHook, defaultNativeBehavior, hookedTableIsMetaTable)
hostKeyTreeMatrix[hostTableToHook] =
hostKeyTreeMatrix[hostTableToHook] or
setmetatable({}, looseValuesMT)
local index = 1
local tree = hostKeyTreeMatrix[hostTableToHook][key]
if tree then
index = huntThroughPriorityList(tree, priority)
else
---@diagnostic disable-next-line: cast-local-type
tree = createHookTree(hostTableToHook, key, defaultNativeBehavior)
if not tree then
return ---@diagnostic disable-line: missing-return-value
end
end
local new = {
priority = priority,
tree = tree
}
function new.remove(deleteAllHooks)
Hook.delete(new, deleteAllHooks)
end
--new.debugId = tostring(callbackFn) .. ' and ' .. tostring(new)
if hookAsBasicFn then
new.hookAsBasicFn = callbackFn
else
setmetatable(new, {
__call = callbackFn,
__index = __index
})
end
reindexTree(tree, index, new)
return new
end
end
---Hook.basic avoids creating a metatable for the hook.
---This is necessary for adding hooks to metatable methods such as __index.
---The main difference versus Hook.add is in the parameters passed to callbackFn;
---Hook.add has a 'self' argument which points to the hook, whereas Hook.basic does not.
---@param key unknown
---@param callbackFn fun(Hook, ...):any
---@param priority? number
---@param hostTableToHook? table
---@param defaultNativeBehavior? function
---@param hookedTableIsMetaTable? boolean
function Hook.basic(key, callbackFn, priority, hostTableToHook, defaultNativeBehavior, hookedTableIsMetaTable)
return Hook.add(key, callbackFn, priority, hostTableToHook, defaultNativeBehavior, hookedTableIsMetaTable, true)
end
---@deprecated
---@see Hook.add for args
function AddHook(...)
local new = Hook.basic(...)
return function(...)
return new.next(...)
end, new.remove
end
setmetatable(Hook, {
__newindex = function(_, key, callback)
Hook.add(key, callback)
end
})
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "TripleShockwave" end
---@debug
do
local SPAWN_OFFSET = 50
local WAVE_SPEED = 800
local LIFETIME_SECONDS = 2.5
local function PropagateWave(wave)
wave.x = wave.x + math.cos(wave.angle)*WAVE_SPEED*ALICE_Config.MIN_INTERVAL
wave.y = wave.y + math.sin(wave.angle)*WAVE_SPEED*ALICE_Config.MIN_INTERVAL
BlzSetSpecialEffectPosition(wave.visual, wave.x, wave.y, 0)
wave.time = wave.time + ALICE_Config.MIN_INTERVAL
if wave.time >= LIFETIME_SECONDS then
ALICE_Kill(wave)
end
end
local function CreateWaves()
local caster = GetSpellAbilityUnit()
local casterX = GetUnitX(caster)
local casterY = GetUnitY(caster)
local targetX = GetSpellTargetX()
local targetY = GetSpellTargetX()
local angle = math.atan(targetY - casterY, targetX - casterX)
local casterHalf = caster/2
for i = -1, 1 do
local waveAngle = angle + 30*bj_RADTODEG*i
local spawnX = casterX + SPAWN_OFFSET*math.cos(waveAngle)
local spawnY = casterY + SPAWN_OFFSET*math.sin(waveAngle)
local wave = {
x = spawnX,
y = spawnY,
identifier = "wave",
selfInteractions = PropagateWave,
visual = AddSpecialEffect("Abilities\\Spells\\Orc\\Shockwave\\ShockwaveMissile.mdl", spawnX, spawnY),
angle = waveAngle,
time = 0
}
BlzSetSpecialEffectYaw(wave.visual, angle)
ALICE_Create(wave)
end
end
OnInit.final(function()
local trig = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
TriggerAddAction(trig, function()
CreateWaves()
end)
end)
end
if Debug then Debug.beginFile "Hello" end
do
function PrintMessage()
print("Hello, I am the second tab! You might have heard of me.")
end
end
if Debug then Debug.endFile() end
print([[Type |cffffcc00-wscode|r to open the Code Editor. To debug the CreateWaves function, put a stop into the function (but only after all event response functions are called), then cast the Triple Shockwave spell.
Investigate the bugs, edit the code, then press Execute to recompile it. You win the game if you find all the bugs in the code!
You can add the GUI trigger to the editor by clicking the Pull button and typing the trigger name.]])
]===])