Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
Warcraft Studio Code is an in-game code editor, compiler, and debugger, designed to be a complement to your other coding tools by allowing you to rapidly iterate between different versions of your functions and locate those extremely elusive bugs. WS Code allows you to effortlessly import code snippets, functions, or even entire libraries and debug them in-game.
Your code can be easily added to the editor by wrapping it in your map script with a WSCode function:
Lua:
WSCode.Transpile([[
function MyFunction(a, b, c)
return a + b + c
end
]])
Adding a library to the editor for debugging this way will not interfere with your map initialization, and your libraries will be able to initialize as usual.
Features
Import code to be automatically loaded in the Code Editor on map launch.
Syntax highlighting and variable and function preview.
Halt function execution with breakpoints and execute functions line-by-line.
View the values of local variables and function parameters at various steps within the function.
View the content of tables in the variable viewer.
Recompile a library and alter function behavior in-game.
Export code as a text file and reimport it in a later session.
Limitations
Warcraft Studio Code is not meant to be a replacement for an external IDE, but a complement to it. The editor has many basic features, but it is limited by the Warcraft 3 API and, of course, cannot compete with the plethora of features and improvements that have been added to modern IDEs over the years. However, its real strength lies in giving you the ability to rapidly iterate your code and to debug it much more efficiently. I recommend that you write the majority of your code in your IDE of choice, then import it into WS Code and get your script to the finish line completely in-game.
WS Code uses an editbox frame to capture your text inputs, which is similar to the frame that pops up when you chat to other players. This frame captures all key inputs, which means that they cannot be registered by the game. Therefore, hotkeys such as Ctrl + Z do not work. There are some workarounds used to achieve these features, but it still stands as the biggest limitation of the editor.
The editor will not work if the mouse cursor is not over terrain. This will eventually be fixed when the full version of the Mouse Tracker is released (sometime after the release of Winds of Winter).
To-Do List
Variable viewer slider for when too many variables are shown.
Auto-sync export files with IDE folder.
Installation
Copy the Warcraft Studio Code script file into your map. Next, make sure you have all the requirements imported. These are:
.fdf files and textures required for the Code Editor can be found in the test map or in the appended zip-file. Importing the 350kB Consolas.ttf file is optional.
I recommend that you save your map in folder mode and then copy&paste all assets from the zip file into its root directory.
After importing all required files, launch your map and test if the editor is working correctly before doing anything else. Type -wscode to open it. Once it has been initialized, you can hide or show it by pressing F1 (customizeable).
Using the Editor
Parsing the Map Script
Initialization
In-Game Code Editing
Using the Editor
Open the editor by typing "-wscode" or by pressing the enable key (if auto-initialize is enabled). You can start typing code and test it execute it by pressing the Execute button at the bottom.
Smaller snippets of code can be directly copied into the code editor with Copy & Paste. The editbox has a limit to how many characters it can hold, so anything about four or five lines will not be processed.
You can only enter text while the hidden editbox of the editor is focused. While it is focused, hotkeys do not work. To defocus the editor, left-click anywhere on the map.
Breakpoints
To the right, next to the line numbers are hidden buttons that you can click (left-click or right-click) to insert breakpoints or spyglasses. When your script encounters a breakpoint, the code execution will halt and you can view all currently visible variables in the variable viewer. When your script encounters a spyglass, the code execution will continue, but the variable viewer will be updated with a snapshot of the variables currently visible at that line of code. For breakpoints and spyglasses to work, your code must have been transpiled into debug form.
Once the code execution has been halted, you can resume it by pressing F3 or F4. F3 will execute the next line; F4 will advance to the next breakpoint. Note that hotkeys don't work as long as a Code Editor chat input box is focused, so always click somewhere outside of the editor to defocus it first.
All currently visible variables and their values are displayed in the variable viewer. You can view the full value of variables and the content of tables by clicking on them.
You have to be careful that a periodic callback function is not invoked repeatedly by a periodic event as you're trying to debug it. This might lead to unforeseen consequences. The best solution here is to write your code using ALICE, as the cycle is automatically halted on a breakpoint.
Do not put breakpoints before all event response functions such as GetTriggerUnit have been called as those functions will return nil otherwise.
Save & Load
You can save your code to a text file, then reload it on a later session with the Save & Load buttons at the bottom. The filenames are based on the name of the current tab. To use this feature, FileIO needs to be imported.
You can also use this feature to export your code if you've done substantial edits to it in-game. The code will be written to a file in the CustomMapData. To extract your code from the file, simply replace all occurences of call Preload( " and " ).
Context Menu
You can right-click anywhere in your code to open the context menu. This menu gives you the following options:
Cut, Copy & Paste: Works just like you would expect in any other program. Note, however, that this feature does not add text to the clipboard, as that's unfortunately not possible. Instead, it uses a fake clipboard only used internally, so you cannot use this feature to export your code.
Evaluate: Evaluate takes an expression and prints out its result. So, selecting the text 5*7 and pressing Evaluate will print out 35, myString1 .. myString2 will print out the concatenation of those two strings.
Execute: Execute will take the selected code and execute it in a new thread. The compiled function does not have access to any local variables defined in the current tab outside of the executed code chunk. This feature is especially useful for fast prototyping.
Assign: Assigns a new value to the selected table field or upvalue. The allowed inputs are numbers, strings (delimited by " or '), global names, and true, false, or nil.
Expose: Stores the selected variable in a global with the entered name.
The Parse Function
The Parse function is the main way to import your code, work on it, and debug it. It works by wrapping your entire map script, storing it as a string, and then executing it in chunks. This enables you to import code much more conveniently, adds function previews to your custom functions, and enables various options to protect you from game crashes or main menu yeets.
Create a new script directly below your essential libraries, which should be arranged in this order:
Then, at the very end of your map script, close the long string and function call by creating another script with the content:
Lua:
]===])
Now, your entire map script is wrapped and gets parsed before being executed.
To enable the Parse function to correctly separate your map script into individual files, you need to begin each script file with a Debug.beginFile call:
Lua:
if Debug then Debug.beginFile "ScriptName" end
or alternatively with
Lua:
---@beginFile ScriptName
WSCode will execute your map script in protected mode in chunks separated by these delimiting tokens. If the initialization of one script file fails, the rest will still execute and once the map initialization completes, the editor will pop up and show you the encountered error.
When installing WS Code in your map, I recommend doing this wrap as the last step in the process after you have made sure that everything else is working. You can also do it in steps — first a single script, then one folder, then the entire map script. Check each time whether you experience any bugs and whether you can load the wrapped script files into the editor.
Parser Instructions
To direct how the parser treats an individual script file, you can use various parser instructions. These are added as comments anywhere in the code and preceded by an @-sign.
@debug
Transpiles the current script file into debug form and adds it to the editor upon map launch, allowing you to do further edits in game, look at its local variables, and halt its execution with breakpoints. If you're writing a new spell or system, simply add this instruction, so that, if the code doesn't behave as expected, you can immediately investigate it in-game.
@transpile
Transpiles the current script file into debug form, but does not add it to the debugger. This instruction could be useful for investigating a rare bug that cannot be reproduced.
@store
Skips executing the current script file at map initialization and adds it to the editor. Useful for testing code snippets.
@persistent
To edit your code in-game, the code has to be executed again. This will often overwrite existing variables. Some of these variables might be set externally and will not be set to the correct value when the code is recompiled. By adding the @persistent command, you declare that the variable in this line should be copied over from the previous iteration of a library when it is recompiled. Only has an effect if the library is compiled in debug form.
@persistents
@endpersistents
Declares all variables between these two statements as persistent variables.
@ephemeral
Sets the variable declared in the current line as explicitly not persistent when the "Smart Persist" flag is enabled.
You can also load any script that was parsed with the Pull Script button at the bottom of the editor.
Side Effects
Using the Parse function to wrap your map script has some minor side effects:
You do not get a compile error in World Editor when you have a syntax error in one of your scripts. However, if you launch your map from an external tool, you will not be yeeted into the main menu. Instead, you will get an error showing you the exact location of the problem.
All individual scripts must be able to able to be compiled on their own. Having another wrapper similar to the Parse wrapper inside the wrapped script will lead to syntax errors as the Parse function executes the map script in chunks.
Your map takes slightly longer to load. This increase in loading time is quite small, though, as the parser is well optimized. In my map with 50k+ lines of code, the increase is ~0.3 seconds.
Initialization
WSCode can help you debug your map initialization process by identifying the causes of script errors or map crashes. It also offers its own initialization methods, similar to Total Initialization, but with a few advantages when it comes to debugging.
To debug your map initialization, your map script must be wrapped by the WSCode.Parse function, and to be effective, your script files must be delimited with either Debug.beginFile calls or ---@beginFile tokens.
We cannot set breakpoints via the editor during map initialization, so instead, we use a WSCode.BreakHere() call to halt the map script. Once the map initialization completes, the editor will initialize and pull up the halted script file. The script must be compiled in debug mode by adding ---@debug token.
The behavior of your map script now depends on the initialization method used:
WSCode Initialization: The entire map script will be halted. Blizzard functions will be executed as normal. The map initialization will resume once the halted script reaches the end.
Any other initialization: The script file will be halted, but all other scripts will initialize as normal. If another script depends on the script being halted, it will most likely throw an error.
WSCode initializers offer additional diagnostic options, such as the INIT_DUMP, which will write the last map script being initialized into a text file in the CustomMapData folder, potentially preserving your sanity when trying to find the cause of a game crash. Once you have identified the map script responsible, add a breakpoint with WSCode.BreakHere() and go through line by line until the game crahses. If it fails to crash, this probably means that you are calling a Blizzard native before it is safe to do so.
How to use WSCode Initializers
To use a WSCode initializer, wrap your function in a WSCode.Init call, similar to TotalInitialization:
Lua:
WSCode.InitGlobal(function()
--Do stuff
end)
The initializers and their order of execution are:
WSCode.InitMain
WSCode.InitGlobal
WSCode.InitTrig
WSCode.InitMap
WSCode.InitFinal
Unlike TotalInitialization, WSCode initializers do not use a Require function. Instead, the order of execution is solely determined by the order in which the initializers are called. Therefore, we must control the order of execution of the root of our script files. This is done with the ---@require token. This command tells the Parse function to wait with executing the current map script until all requirements have been executed. The requirements are listed after the require keyword, separated by commas. A ? denotes an optional requirement.
Using WSCode, a script file may look like this:
Lua:
---@beginFile Cataclysm
---@require SpellLibrary, TriggerUtils?
do
local MAX_RANGE = 500*SpellLibrary.RangeModifier --Guaranteed to execute after SpellLibrary, so the SpellLibrary table exists.
WSCode.InitGlobal(function()
SpellLibary.CreateSpell("Cataclysm") --Any InitGlobal function inside SpellLibrary executes first, so the CreateSpell function is guaranteed to work properly.
if TriggerUtils then
TriggerUtils.CreateTrigger("Cataclysm")
end
end)
end
In the unlikely case that you need even more control over the order of initialization, you can set the optional onWhichPass parameter.
Using WSCode initializers has the downside that the system can no longer be removed from the release version. However, with all debugging options disabled, the loading time increase incured from the WSCode.Parse function is negligible. In addition, WSCode is able to write the compiled map script into a text file, which you could import and replace the Parse wrapper with. It even opens up the possibility of a script optimizer in the future.
In-Game Code Editing
WSCode is an excellent tool to prototype code snippets and adjust numbers quickly. Tabs you create and code you write into those tabs are saved between game sessions, so you don't have to worry about losing your progress by not writing in your external editor. You can also pull files from your map script or from Preload files with the respective buttons at the bottom. If your changes require a map restart, you can use the Quick Restart button at the bottom to save the altered map script, then restart the map.
Adjusting numbers
Variables stored in tables or as upvalues can be adjusted directly with the Assign button in the context menu. You can also recompile your library with the numbers adjusted. However, this may break things if you're not careful.
Manipulating game objects
If you want to, for example, fine-tune a special effect or fix the position a frame, you can use the Expose button in the context menu. This will store the selected variable in a global, which you can then manipulate in a second tab.
Fixing functions
Redefining buggy functions requires a complete recompilation of the library. Adjust the faulty logic inside the function, then press Execute to recompile the library. This will have, in many situations, unintended side effects and cause errors unless the compiler setting that protect against those errors are correctly set:
The Smart Persist flag ensures that upvalues inside the library are not overwritten with uninitialized values. For example, you could have a data table that is initialized as an empty table and filled by external function calls. If the Smart Persist flag is not set, it would overwrite that table and delete all stored data when you recompile the library.
WRAP_IN_CALLER_FUNCTIONS must be enabled in the config and the library must have already been compiled in debug mode the first time. Otherwise, existing function references will not be correctly updated to the newly compiled function.
The Run Init flag must be disabled in most cases to make sure handles are not created a second time.
Change Log
Version 3.0
Added the WSCode.Init functions. These functions provide a way to initialize scripts and handle dependencies using the Parse function with the ---@require parser instruction. Initializations using WSCode.Init functions are fully yieldable, and can be analyzed and debugged, before being resumed.
Incorporated the "-exec" command from IngameConsole.
Added the Quick Restart button. When pressed, WSCode exports the map script to a text file, respecting all changes made within the editor. The map script will be loaded from that file on the next map launch.
(Experimental) Added the ENABLE_MAP_SCRIPT_SHARING setting. With this setting, you can write and read the map script to and from a save file. This allows you to update your map script for playtests without compiling a new version of the map if only the map script changes. (This feature may also be the starting point for a map optimizer later down the road).
Added the INIT_DUMP config option. When enabled, the script name of the last script being executed during initialization will be written to the file WSCodeInitDump.txt. Useful for investigating map crashes. This feature works with WSCode.Init functions, but not TotalInit functions.
Added the View Global button, which adds a global variable to the variable viewer.
Added the Show Traceback button, which prints the traceback of the currently halted thread.
Added the Expose button to the context menu, which stores the value of a local variable to a global with the specified name.
Added the Assign button to the context menu, which allows you to easily set a new value for any upvalue or table field.
Removed the Export Script button and changed the Load Script feature to work with auto-saves instead.
Tabs that were not created by the parser on a previous session are now automatically reloaded on a map restart.
Added the RELEASE_VERSION config option. When set to a release version, all code alterations and all features other than the Parser are disabled.
Added the ON_ERROR callback function.
Added the Hide Functions flag, which will hide all functions from the variable viewer while in the root.
The line numbers in the editor now light up slightly if a line in a script has been executed at least once.
The flags which are enabled by default are no longer set in the config, but instead saved and retrieved from the previous session.
The Execute function in the context menu can now be activated without having text selected and will execute the current line of the cursor when doing so.
(Untested) WS Code should now be multiplayer-safe. For use in multiplayer, importing World2ScreenSynced is required.
Removed the Compile, Transpile, Store, and Execute functions.
Removed the STORE_HANDLES_IN_NO_DEBUG_MODE setting.
Removed the Go to Next Step (ALICE) button.
WRAP_ALL_LUA_ENTRY_POINTS "editoropen" option removed. It is now either true or false.
The variable viewer will now show the number of hidden variables in the header.
The variable viewer now correctly updates when switching to a different tab.
Fixed a bug where the variable viewer would not automatically open on an error.
Fixed a bug that caused local functions to not be callable with Evaluate.
Fixed an issue where special characters in the search bar were not being escaped.
Fixed a bug where the Last Function Call would pop up in the variable viewer even if no function was executed yet in the current tab.
Fixed a bug that caused line numbers in tracebacks for scripts compiled in debug mode to be inaccurate.
Fixed a bug that caused text to sometimes reappear after deleting it with Undo.
Fixed a bug that caused crashes when a triggeraction was added to a trigger with nil as the callback argument.
Version 2.4
You can now set an execution limit to protect against accidental infinite loops.
Fixed the Evaluate feature.
When a breakpoint is added to the map initialization with BreakHere, it will now wait with initializing the editor until it is safe to do so.
Executing code with the Execute feature will no longer interrupt currently halted threads.
Fixed a faulty pattern that would sometimes result in wrong file names in error messages.
Fixed an error that would occur when TimerStart is called with nil as the timer argument.
HALT_ALICE_ON_BREAKPOINT is no longer a flag. ALICE is now always halted if the code execution stops within the ALICE main thread.[/CODE]
Version 2.3.2
Fixed the missing ON_EXPAND and ON_COLLAPSE callback functions.
Fixed a bug that caused thread crashes when WRAP_ALL_LUA_ENTRY_POINTS was not enabled.
Fixed an issue where the editor would add an additional string delimiter when entering one to close a string if AUTO_CLOSE_BRACKETS_AND_QUOTES is enabled.
Version 2.2 & 2.3
All of the bug fixes. ALL OF THEM! That's right, no more bugs from here on out!
The Persist Upvalues feature has been replaced by the Smart Persist feature. Smart Persist attempts to keep data tables and variables that are initialized as nil intact when recompiling a library while keeping all other variables unaffected. It now also affects globals.
The Clear Tab button has been replaced with the Clear Handles button.
Version 2.1
Significantly reduced text input lag. Thanks to @moddiemads for the suggestion.
Tabs can now be closed.
Reduced the amount of memory leaks caused by the coroutine wraps.
Version 2.0
Added the Parse function to make importing code super easy, barely an inconvenience. Also enables a multitude of other goodies.
Added a search bar.
Added the option to wrap all Blizzard API points in coroutines, so that the main thread can be yielded on a breakpoint.
Added a context menu that is enabled by right-clicking on the text field.
Thank you to @Eikonium for help with error handling and for updating DebugUtils to be compatible.
The full version is up. Here are the main new features added:
Completed parser: The parser should now be able to take any code chunk and transform it into its debug form. This means that you should be able to import an entire library, debug it, and recompile it in-game. There might be one or two weird syntax combinations where it fails, so if you encounter them, please report them so I can address the issue.
Transpile function: The transpile function takes a code chunk and transpiles it into its debug form, adds it to the Code Editor, and then executes it. If wrapped around a library in the Lua root, the library initialization will be unimpaired, and all other libraries that depend on it will also be able to initialize. Adding a library to the debugger this way only requires two extra lines of code.
Run Init / Clean Handles: New compilation options allow you to properly recompile your library by automatically running OnInit functions and cleaning up triggers and other handles created in previous compilations.
Textmacros: You can now define textmacros in the config, which can be used, for example, to insert the current camera or mouse coordinates into your code.
Make sure you're using the newest version of DebugUtils if you're using WS Code. Thank you to @Eikonium for implementing the new features.
Version 1.3
The table viewer has been removed. Clicking on a variable now shows it in the variable viewer.
Added the ability to view nested tables.
Added the Load button, which loads the code previously saved by the Export button. You can use this to quickly store the code you wrote in-game, restart the game, and add the code back into the editor. Requires FileIO.
Added function previews for natives and BJ functions. Requires the FunctionPreviews script.
Moved the hide and expand buttons to the top bar with new icons.
The parser can now parse multiline comments opened by [=[ correctly.
Fixed a bug with the parser introduced in previous version.
You can now add additional tabs to the editor.
Added the "spyglass" feature. By clicking on a breakpoint button twice, you put a spyglass on that line. The variable viewer will display a snapshot of the variables on that line, even when the code continues executing.
In this update for Version 1.5, I rewrote the editor and changed the individual lines from editboxes to regular text frames with only one hidden editbox to capture user input. This reduced the jankiness by 73.6% and opened up many new possibilities such as syntax highlighting and text selection.
Other noteable features added include the last function call display, which displays the arguments with which a function was called.
All of the bug fixes. ALL OF THEM! That's right, no more bugs from here on out!
The Persist Upvalues feature has been replaced by the Smart Persist feature. Smart Persist attempts to keep data tables and variables that are initialized as nil intact when recompiling a library while keeping all other variables unaffected. It now also affects globals.
The Clear Tab button has been replaced with the Clear Handles button.
Fixed the missing ON_EXPAND and ON_COLLAPSE callback functions.
Fixed a bug that caused thread crashes when WRAP_ALL_LUA_ENTRY_POINTS was not enabled.
Fixed an issue where the editor would add an additional string delimiter when entering one to close a string if AUTO_CLOSE_BRACKETS_AND_QUOTES is enabled.
I come back to this community after a decade and this is the first thing I see lmao, absolutely insane
(I'm inspired to keep learning how to code spells xD)
I come back to this community after a decade and this is the first thing I see lmao, absolutely insane
(I'm inspired to keep learning how to code spells xD)
There was some talk about making a proper JASS2Lua transpiler, and someone wanted to commission me, but that came to nothing unfortunately. I think that's the route to go; not manually translating everything. It would be a considerable amount of work, but something a lot of people would be interested in, so you could maybe find other people who would be willing to throw some bucks in for a commission.
There was some talk about making a proper JASS2Lua transpiler, and someone wanted to commission me, but that came to nothing unfortunately. I think that's the route to go; not manually translating everything. It would be a considerable amount of work, but something a lot of people would be interested in, so you could maybe find other people who would be willing to throw some bucks in for a commission.
We can discuss it. For the time being though, I think you should try vJass2Lua and see how far you can get with it. I don't know exactly what the limitations are.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.