This is the API reference manual for version 1.0 of Bolt Launcher’s API for Lua plugins. All of the available functions and types are listed along with a simple usage example.
This document is for developers. If you’re not looking to develop plugins, you don’t need to read this document.
Bolt Launcher comes with a built-in plugin loader. A plugin loader is not the same thing as plugins - the loader is what enables plugins to be able to work, but it doesn’t have any plugins built into it in itself. Plugins can be made by anyone and must be obtained separately from Bolt. This document details how to make your own Bolt plugins.
Plugins are written in Lua, but the plugin loader itself is a dynamic library written in C. The launcher launches the game and loads the library into the process using a manual map injector after the process loads but before allowing it to run. When the library starts up, it modifies the import table of the process to insert function hooks. If that’s all too technical for you, don’t worry: you don’t need to understand any of that to be able to write plugins, but just know that your Lua code runs directly inside the game process.
The word "inject" sounds scary, but Bolt is totally harmless: the technique described above is the same one used by lots of software you might already be using, such as OBS, Alt11, steam overlay, Discord screencapture, or obs-vkcapture. Like those, Bolt only hooks external graphics-related functions and will never attempt to read or modify the game code, therefore it is entirely allowed under the terms of service and will not trip any known anti-cheating measures2. It does, however, mean that it’s less powerful than a tool like Runelite, since the only information it has access to is what’s available in the OpenGL shaders; for example, it can see the vertex data and texture data of an item being rendered, but cannot know the item ID.
(1 Yes, really.)
(2 That is NOT to claim that Bolt is undetectable. It’s not - nothing is.)
The primary file in a plugin is called "bolt.json". It needs to have that exact name, in lower-case. Here’s how your bolt.json might look:
{ "name": "Goblin Helper", "version": "1.0", "description": "A plugin designed to help you kill goblins more effectively and display a graph of your GSE (goblin-slaughter efficiency)", "main": "main.lua" }
The directory that contains bolt.json is called the "plugin directory". As a general rule, the plugin only has access to files inside the plugin directory, and any file paths will be considered relative to the plugin directory.
There are no limitations on the text contents or length of the name, version, or description, and the version and description are optional. That said, it’s suggested to use a short name (2-3 words), a longer and helpful description, and a sensible system of version numbers.
The "main" file, in this case "main.lua", is the file that will be loaded and run when a user starts your plugin. This file will be loaded from the root of the plugin directory, next to bolt.json. If your main file is in a subdirectory then you need to write the relative path using ’/’ as directory separators, e.g. "app/src/main.lua".
Here’s an example of a simple main.lua file. Lines beginning with
-- are comments in Lua.
-- load the Bolt API local bolt = require("bolt") -- make sure this plugin is running on a Bolt version compatible with -- the one it was written for (1.0) bolt.checkversion(1, 0) -- some variables we'll refer to later local interval = 1000 * 1000 local clickcount = 0 local nextprinttime = bolt.time() + interval -- set a function that will run every time the user clicks the game -- window (excluding the title bar or any decorations) with the left, -- middle, or right mouse button bolt.onmousebutton(function (event) -- check if this is a left click using the event's button() function, -- and if it is, count it if event:button() == 1 then clickcount = clickcount + 1 end end) -- set a function that will run every time SwapBuffers is called - in -- other words, this runs at the end of every frame, so around 100 times -- per second depending on the user's FPS bolt.onswapbuffers(function (event) -- check if the time interval (1 second) has passed yet if bolt.time() >= nextprinttime then -- print a message print(string.format("clicks-per-second: %s", tostring(clickcount))) -- reset the click count and update the interval timer clickcount = 0 nextprinttime = nextprinttime + interval end end)
As you can see, this is a simple plugin which keeps track of the user’s left-clicks per second and prints that information using Lua’s built-in "print" function. This prints to the terminal. If you don’t have a terminal open, you won’t see anything, but the plugin is still running. All of the functions used here are documented below.
Before we move on, there’s a tiny but important detail of this code to
be aware of. Notice how functions like bolt.checkversion use a
’.’, but event:button() uses a ’:’. This is because bolt
is a simple list of functions, whereas event is a userdata object
which needs to be passed as the first argument to its functions - that’s
true for all types of object in the Bolt API. In Lua, ’:’ is shorthand
for doing exactly that.
There are two ways to install a plugin via the plugin menu: the first is a file picker which needs you to navigate to the "bolt.json" file on your computer, assuming you’ve already downloaded or created a plugin. The second way is to enter an updater URL and allow the plugin menu to download the plugin onto your computer for you. For most users this is far simpler as it only needs them to copy-paste a hyperlink into the menu and press enter. Even more convenient is that this same URL will be queried each time the user wants to check for a newer version of your plugin, whereas there’s no way to update-check a plugin that was selected from the file picker.
Updater URLs return JSON which should look like this:
{ "url": "https://bolt.adamcake.com/my-plugin-v1.0.1.zip", "version": "1.0.1", "sha256": "ec3791c5fcbd9b7f077ac292a2ea319f428e7023919d01e33ff118dcbbb29d16" }
When initially installing a plugin from an updater URL, the file at "url" will be downloaded. Next, if the optional "sha256" is present, the downloaded file is hashed and will be rejected if the checksums don’t match. The file contents will be extracted to a new directory in bolt’s data location, and "bolt.json" will then be selected from that location.
When checking for updates, the updater URL will be queried. If "sha256" is present in the response and does not match the hash that was previously installed, the plugin will be re-downloaded from "url", hashed, and like before it will be rejected if the hash doesn’t match the sha256 string. If "sha256" is not present, it will instead compare the "version" in the response to the version in the previously-installed "bolt.json", and, if they’re not an exact match, the plugin will be re-downloaded on that basis. To clarify, "version" is completely ignored if "sha256" is present - you only need one or the other. Either way, after downloading the new version, it will be extracted to the same location as it was installed previously, after deleting all previous contents of that directory.
In the above example, the downloaded file is a .zip, but Bolt supports every type of archive format that can be auto-detected by libarchive (which is practically every format ever created.) If possible, consider using a format like .tar.xz, which is slow to create but can be extracted very quickly and has an extremely high compression ratio (i.e. small download size), for the fastest possible user experience. Generally speaking, .zip and .rar are the worst in both of these aspects, but you can use them if you really want to.
Note that updater URLs and the "url" contained in them must respond with a status of "200 OK", otherwise the plugin menu will not continue with the process of installing/updating the plugin.
This is a list of all functions in bolt, the table returned by
require("bolt").
Returns the Bolt API major version and minor version, in that order. Plugins should call this function on startup and, if the major version is one it doesn’t recognise, it should exit by calling ‘error()‘. The minor version however does not need to be checked, as minor versions will never contain breaking changes; they may add features, though, and the minor version can be used to check for the existence of those features.
For compatibility reasons, there will never be a breaking change to this function.
local major, minor = bolt.apiversion()
A simple alternative to apiversion which calls error() if either of these conditions is true:
This function will never return on failure, since that’s the implementation of Lua’s error() function. Calling error() in a Bolt plugin will always be handled by stopping the plugin.
For compatibility reasons, there will never be a breaking change to this function.
bolt.checkversion(1, 0)
Stops this instance of this plugin. This is essentially the same as the plugin being stopped via the plugin menu in the launcher.
bolt.close()
Returns a monotonic time as an integer, in microseconds.
This function can be used for timing. The number it returns is arbitrary - that is, it’s the number of microseconds that have elapsed since an arbitrary point in time - therefore it’s not useful for anything other than to call this function multiple times and compare the results.
Note that on a 32-bit CPU this number will overflow back to 0 every ~4296 seconds, which is slightly more than an hour. On a 64-bit CPU, it will overflow every ~18 trillion seconds, or around 585 millennia. Playing on a 32-bit CPU is therefore not advisable, but if a plugin wishes to support 32-bit CPUs while using this function, it must handle the possibility of time() resetting to 0.
local t1 = bolt.time() -- something time-consuming... local t2 = bolt.time() print(string.format("execution took %s μs", tostring(t2 - t1)))
Returns six integers: the current calendar year, month (1-12), day (1-31), hour (0-23), minute (0-59), and second (0-601), in game-time (i.e. UTC). The time is based on the user’s system clock but the result will be converted to game-time. There is no way to get the user’s timezone information via Bolt.
Don’t try to use this function for precision timing. Use time() instead.
local year, month, day, hour, minute, second = bolt.datetime() print(string.format( "the time is %s:%s:%s on %s-%s-%s", tostring(hour), tostring(minute), tostring(second), tostring(year), tostring(month), tostring(day) ))
(1 seconds value can be 60 in the case of a leap-second)
Returns an integer representing the current weekday in game-time (i.e. UTC). A value of 1 represents Sunday, 2 represents Monday, 3 represents Tuesday, and so on.
This function is based on the user’s system clock but the result will be converted to game-time. For example, if the user’s system time is 22:00 EST on a monday, this function will return 3 (tuesday), because the game-time is 03:00 on a tuesday. There is no way to get the user’s timezone information via Bolt.
local function isweekend() local day = bolt.weekday() return day == 1 or day == 7 end
Returns a Point representing the last known position of the player, in world coordinates. This point relates to point (0,0,0) on the player model, which is usually on the ground between the model’s feet during a normal standing animation. Also see Appendix: note on world coordinates.
local worldpoint = bolt.playerposition()
Returns the last known width and height of the game window, in pixels. Specifically this refers to the inner "graphical" area of the window, excluding any OS-level decorations like borders or title bars.
local width, height = bolt.gamewindowsize()
Returns the last known x, y, width and height of the game view inside the game window, in pixels. The x and y are the distance from the left and top of the window respectively.
The game view is the area where 3D rendering of the game world takes place, which by default takes up the entire window, but its position can be configured by the user in the game’s layout settings.
local x, y, width, height = bolt.gameviewxywh()
Makes the game window "flash" to grab the user’s attention until the window is next brought into focus. If the window is already focused, this function does nothing.
The exact behaviour of this function will depend on the user’s platform and window manager, but the most common result is for the taskbar icon to flash orange.
bolt.flashwindow()
Returns a boolean value indicating whether the game window is the currently-focused window on the user’s desktop.
if bolt.isfocused() then -- ... end
Returns a string which uniquely identifies the game character currently being played. The string will contain only alphanumeric characters. The same character will always have the same ID, even if the display name is changed. It’s also not possible to change characters without restarting the game client, so there’s no need to call this function more than once per run of a plugin.
The value returned by this function is a hash, so it’s not considered sensitive in terms of account security, although users will still appreciate their privacy being respected.
local id = bolt.characterid()
Loads the file into a Lua string and returns it. The file will be located relative to the plugin directory. Either ’/’ or ’\’ may be used as file separators, regardless of OS, and it makes no difference if the path does or doesn’t start with a file separator. In the case of an error, this function will return nil. The most likely cause of failure is that the file doesn’t exist.
The plugin directory is read-only. For writeable files, use saveconfig and loadconfig.
local filecontents = bolt.loadfile("data.bin")
Loads the file into a Lua string and returns it. The file will be located relative to the plugin’s config directory, the exact location of which depends on the user’s OS. Either ’/’ or ’\’ may be used as file separators, regardless of OS, and it makes no difference if the path does or doesn’t start with a file separator. In the case of an error, this function will return nil. The most likely cause of failure is that the file doesn’t exist.
local filecontents = bolt.loadconfig("position.cfg")
Saves the Lua string in the second parameter into a file identified by the first parameter. The file will be located relative to the plugin’s config directory, the exact location of which depends on the user’s OS. Either ’/’ or ’\’ may be used as file separators, regardless of OS, and it makes no difference if the path does or doesn’t start with a file separator.
This function returns a boolean: if the file is saved successfully this function will return true. If not, it will return false. The most likely cause of failure is that the file already exists and is locked for writing, such as by the user having it open in a text editor.
bolt.saveconfig("info.bin", "Some example file contents")
Creates and returns a new Surface with the given width and height. The surface will initally be fully transparent.
local mysurface = bolt.createsurface(800, 608)
Creates and returns a new Surface with the given width, height, and RGBA data. The data can be a string or a Buffer.
There are four bytes in an RGBA pixel, so the number of bytes in the
data is expected to be 4 * width * height. If fewer bytes than
that are provided, the data will be padded with zeroes. If too many
bytes are provided, the excess data will be unused. The data will be
interpreted in row-major order with the first pixel being in the
top-left.
The following example creates a 2x2-pixel surface with a diagonally-opposite pattern of black and white pixels.
local blackpixel = "\x00\x00\x00\xFF" local whitepixel = "\xFF\xFF\xFF\xFF" local rgba = blackpixel .. whitepixel .. whitepixel .. blackpixel local mysurface = bolt.createsurfacefromrgba(2, 2, rgba)
Creates and returns a new Surface from the PNG file at the given path. The first returned value is the surface object and the second and third values are the width and height respectively.
The path will be interpreted similarly to require(), i.e. relative to the plugin directory, using ’.’ as file separators, and must not include the ".png" extension (this is appended automatically). This function will return nil if the file does not exist, is inaccessible or unreadable, or is not a valid PNG file. A detailed error message will be printed to stdout.
The surface will have the same width and height as the image. As with createsurface, the width and height of your PNG file should be integral powers of 2.
The following example creates a surface from "images/icon.png" relative to the plugin directory.
local mysurface, width, height = bolt.createsurfacefrompng("images.icon")
Creates and returns a new Window with the given initial values for x, y, width and height. The x and y relate to the top-left corner of the game. Embedded windows can capture mouse and keyboard events, and can be drawn onto like a Surface.
The following example creates a window with its top-left corner 50px from the left and 60px from the top of the game, with a width of 800 and a height of 608. clear is used to make the window entirely red and opaque, otherwise it would be transparent.
local mywindow = bolt.createwindow(50, 60, 800, 608) mywindow:clear(1, 0, 0)
Creates and returns a new Browser with the given initial values for width, height, and URL. If the URL begins with "plugin://", it will be interpreted as a file path relative to the plugin directory, and must use ’/’ as file separators (if any). Otherwise, it will be treated as a URL of an internet website. The same rules go for any navigations or fetch requests made by the browser during its lifetime.
The optional 6th parameter is some javascript code which will be run in
an OnContextCreated callback. This is immediately after the V8 context
has been created, so the JavaScipt window object is available but
the DOM content is not yet loaded.
The following example shows an invocation without some custom JS followed by one with some custom JS, to demonstrate that the parameter is optional.
local mybrowser = bolt.createwindow(800, 608, "https://bolt.adamcake.com") local mybrowser = bolt.createwindow(800, 608, "https://bolt.adamcake.com", "window.myCustomFunction = (a, b) => a + b;")
Creates and returns a new Embedded Browser with the given initial values for x, y, width, height and URL. If the URL begins with "plugin://", it will be interpreted as a file path relative to the plugin directory, and must use ’/’ as file separators (if any). Otherwise, it will be treated as a URL of an internet website. The same rules go for any navigations or fetch requests made by the browser during its lifetime.
The optional 6th parameter is some javascript code which will be run in
an OnContextCreated callback. This is immediately after the V8 context
has been created, so the JavaScipt window object is available but
the DOM content is not yet loaded.
The following example shows an invocation without some custom JS followed by one with some custom JS, to demonstrate that the parameter is optional.
local mybrowser = bolt.createembeddedbrowser(50, 60, 800, 608, "https://bolt.adamcake.com") local mybrowser = bolt.createembeddedbrowser(50, 60, 800, 608, "https://bolt.adamcake.com", "window.myCustomFunction = (a, b) => a + b;")
Creates and returns a new Point from x, y and z values. Point objects represent a point in 3D space, and have functions which are useful for 3D space calculations.
local mypoint = bolt.point(0, 0, 0)
Creates and returns a new Buffer with the given size.
Buffer objects are fixed-size and useful for passing large amounts of
binary data to functions. They’re mostly analogous to JavaScript’s
DataView. The initial contents will be 0.
local mybuffer = bolt.createbuffer(65536)
Compiles and returns a vertex shader from a string containing GLSL. If
the code fails to compile, this function will call error().
The shader code must not contain a #version header. The string
#version 330 core will be prepended automatically to it.
This function is part of the Appendix: custom shader API, which requires some understanding of shader programming to be able to use.
local myvertshader = bolt.createvertexshader("void main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }")
Compiles and returns a fragment shader from a string containing GLSL. If
the code fails to compile, this function will call error().
The shader code must not contain a #version header. The string
#version 330 core will be prepended automatically to it.
This function is part of the Appendix: custom shader API, which requires some understanding of shader programming to be able to use.
local myfragshader = bolt.createfragmentshader("out vec4 c; void main() { c = vec4(1.0); }")
Links and returns a Shader Program using a vertex shader
and a fragment shader. If linking fails, this function will call
error().
This function is part of the Appendix: custom shader API, which requires some understanding of shader programming to be able to use.
local myprogram = bolt.createshaderprogram(myvertshader, myfragshader)
Creates and returns a Shader Buffer with the given contents. The data can be either a string or a Buffer.
This function is not to be confused with createbuffer. The difference is that Buffer objects exist in RAM and can be read from or written to at any time. Shader Buffer objects exist in VRAM, cannot be read from, and cannot be written to after creation, but can be used to run custom shader programs.
This function is part of the Appendix: custom shader API, which requires some understanding of shader programming to be able to use.
local mybuf = bolt.createshaderbuffer("\x00\x00\x00\x00\x00\x00\x00\x00")
Sets an event handler for 2D rendering events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Batch2D Event for documentation on this event object.
bolt.onrender2d(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for 3D rendering events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Render3D Event for documentation on this event object.
bolt.onrender3d(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for particle rendering events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Render Particles Event for documentation on this event object.
bolt.onrenderparticles(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for billboard rendering events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Render Billboard Event for documentation on this event object.
bolt.onrenderbillboard(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for icon rendering events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Render Icon Event for documentation on this event object.
Unlike "big icons", the pre-rendered icons given by this callback are stored for a long time, often indefinitely. This only seems to be used for item icons. Icons are rendered at 64x64 resolution.
bolt.onrendericon(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for icon rendering events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Render Icon Event for documentation on this event object.
"big icons" are larger than normal icons, and, despite being pre-rendered, each one seems to be used only once. Big icons are rendered at 512x512 resolution. They’re used for NPC chat-heads and some specific UI elements.
bolt.onrenderbigicon(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for minimap terrain events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Minimap Terrain Event for documentation on this event object.
bolt.onminimapterrain(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for minimap render2d events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Batch2D Event for documentation on this event object.
bolt.onminimaprender2d(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for minimap-rendering events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Render Minimap Event for documentation on this event object.
bolt.onrenderminimap(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for game view rendering events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Render Game View for documentation on this event object.
bolt.onrendergameview(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for swapbuffers events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See SwapBuffers event for documentation on this event object.
This event is named after the "SwapBuffers" function in OpenGL, and as such, it runs every time a new frame is about to be shown on the player’s computer screen. How often the event fires will therefore depend on the user’s current FPS. The event is very useful as a "sync point", i.e. to ensure that some Lua code will run at regular intervals. The event object itself can be used to refer to the finished screen contents as it will appear to the user, such as by reading pixel data from the screen.
bolt.onswapbuffers(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for mouse motion events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Mouse Motion Event for documentation on this event object.
This will fire only for mouse motion events that are received by the game. Events received by a Window or Embedded Browser will not be received by the game, so will not fire this event.
bolt.onmousemotion(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for mouse button events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Mouse Button Event for documentation on this event object.
This handler runs immediately after a button is pressed down. When the button is released, onmousebuttonup will run.
This will fire only for mouse button events that are received by the game. Events received by a Window or Embedded Browser will not be received by the game, so will not fire this event.
bolt.onmousebutton(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for mouse button-up events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Mouse Button Event for documentation on this event object.
This handler runs immediately after a button is released. It shares the same type of event object as onmousebutton.
This will fire only for mouse button events that are received by the game. Events received by a Window or Embedded Browser will not be received by the game, so will not fire this event.
bolt.onmousebuttonup(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for mouse scroll events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Scroll Event for documentation on this event object.
This will fire only for mouse scroll events that are received by the game. Events received by Window or Embedded Browser will not be received by the game, so will not fire this event.
bolt.onscroll(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Reads a single byte from a buffer object or string, at the given offset, and returns it as a signed integer. See Buffer for more on the buffer API.
local num = bolt.buffergetint8(mybuffer, myoffset)
Reads a two-byte value from the buffer object or string, at the given offset, and returns it as a signed integer. See Buffer for more on the buffer API.
local num = bolt.buffergetint16(mybuffer, myoffset)
Reads a four-byte value from the buffer object or string, at the given offset, and returns it as a signed integer. See Buffer for more on the buffer API.
local num = bolt.buffergetint32(mybuffer, myoffset)
Reads an eight-byte value from the buffer object or string, at the given offset, and returns it as a signed integer. See Buffer for more on the buffer API.
local num = bolt.buffergetint64(mybuffer, myoffset)
Reads a single byte from the buffer object or string, at the given offset and returns it, as an unsigned integer. See Buffer for more on the buffer API.
local num = bolt.buffergetuint8(mybuffer, myoffset)
Reads a two-byte value from the buffer object or string, at the given offset, and returns it as an unsigned integer. See Buffer for more on the buffer API.
local num = bolt.buffergetuint16(mybuffer, myoffset)
Reads a four-byte value from the buffer object or string, at the given offset, and returns it as an unsigned integer. See Buffer for more on the buffer API.
local num = bolt.buffergetuint32(mybuffer, myoffset)
Reads an eight-byte value from the buffer object or string, at the given offset, and returns it as an unsigned integer. See Buffer for more on the buffer API.
local num = bolt.buffergetuint64(mybuffer, myoffset)
This is a list of all types of userdata object used by the Bolt API. The
objects have various functions associated with them, all of which need
the userdata object as the first parameter. Lua has a shorthand for
this: for example, instead of window.onresize(window, myhandler),
you can write window:onresize(myhandler).
A surface is a graphical canvas which can be drawn on with various rendering functions, and can then be drawn to the user’s screen by calling drawtoscreen. The contents will never be cleared, other than by explicitly calling clear.
Surface widths and heights should always be integral powers of 2. GPUs often can’t handle other values correctly which will result in unexpected behaviour.
Deletes any previous contents of the surface and sets it to contain a single colour and alpha.
Takes up to four parameters in the range 0.0 - 1.0. All parameters to this function are optional (apart from the surface itself). If none are provided, the surface will be fully transparent. Otherwise, if alpha is not provided, it will be assumed to be 1.0.
Note that if an alpha value less than 1 is provided, the partially-transparent colour will still fully replace the old contents, rather than blending with them.
mysurface:clear() -- transparent mysurface:clear(1, 0, 0) -- opaque red mysurface:clear(1, 0, 0, 0.5) -- partially transparent red
Updates a rectangular section of this surface with the given RGBA pixel data. The parameters are X, Y, width and height, in pixels, followed by the RGBA data. The data can be either a string or a Buffer.
There are four bytes in an RGBA pixel, so the number of bytes in the
data is expected to be 4 * width * height. If fewer bytes than
that are provided, the data will be padded with zeroes. If too many
bytes are provided, the excess data will be unused. The data will be
interpreted in row-major order with the first pixel being in the
top-left.
Note that non-opqaue pixels will fully replace the old contents of the surface, rather than blending with them.
The following example sets the top-left pixel of a surface to a fully opaque green colour:
mysurface:subimage(0, 0, 1, 1, "\x00\x00\xFF\xFF")
Draws a section of the surface directly onto the screen. Parameters are source X,Y,W,H followed by destination X,Y,W,H, all in pixels. Any transparency will be blended additively and, if the size of the source and destination rectangles is different, the image will be scaled smoothly.
mysurface:drawtoscreen(0, 0, 800, 608, 0, 0, 800, 608)
Draws a section of the surface onto a section of another surface. Parameters are target surface, then source X,Y,W,H, then destination X,Y,W,H, all in pixels. Any transparency will be blended additively and, if the size of the source and destination rectangles is different, the image will be scaled smoothly.
mysurface:drawtosurface(myothersurface, 0, 0, 100, 100, 0, 0, 1920, 1080)
Draws a section of the surface onto a section of the game view. Can only be called during a rendergameview event, as the event object needs to be passed to this function. Parameters are target surface, then source X,Y,W,H, then destination X,Y,W,H, all in pixels. Any transparency will be blended additively and, if the size of the source and destination rectangles is different, the image will be scaled smoothly.
bolt.onrendergameview(function (event) mysurface:drawtogameview(event, 0, 0, 100, 100, 0, 0, 100, 100) end)
Draws a section of the surface onto a section of a Window. Parameters are target window, then source X,Y,W,H, then destination X,Y,W,H, all in pixels. Any transparency will be blended additively and, if the size of the source and destination rectangles is different, the image will be scaled smoothly.
mysurface:drawtowindow(mywindow, 0, 0, 100, 100, 0, 0, 1920, 1080)
Sets the "tint" this surface will be drawn with when drawing it to the screen or to another surface. These values are in the range [0.0 - 1.0] and are multiplied with the red, green and blue channels of the image, so values less than 1.0 will darken the image. The defaults are all 1.0.
mysurface:settint(red, green, blue)
Sets the alpha transparency this surface will be drawn with when drawing it to the screen or to another surface. The value is in the range [0.0 - 1.0] where 0.0 is invisible and 1.0 is fully opaque. The default is 1.0.
mysurface:setalpha(0.5)
A buffer is a fixed-size, pre-allocated block of memory containing
binary data. Functions in Bolt which need binary data as an input can
take either a Lua string or a Buffer object. Functions that return
binary data return strings. The buffer API is designed to be mostly the
same as the DataView API in JavaScript, and exists to make it
easier to read and receive large amounts of raw binary data, such as
file contents, RGBA pixel data, or messages to and from browsers.
local mybuffer = bolt.createbuffer(12) mybuffer:setuint32(0, 12345) mybuffer:setfloat64(4, -3.14159) local num1 = mybuffer:getuint32(0) --returns 12345 local num2 = mybuffer:getfloat64(4) --returns -3.14159
The "get" functions of buffers can be found on the base object returned
from require("bolt") and can be used on either strings or
buffers. For example, buffer:getuint32 is the
same as bolt.buffergetuint32, except
that in the latter case, the buffer or string needs to be passed
explicitly as the first param. The buffer’s "set" functions aren’t
available by that method because strings are considered read-only.
local mybuffer = bolt.createbuffer(4) mybuffer:setuint32(0, 123) local num1 = mybuffer:getuint32(0) --returns 123 local num2 = bolt.buffergetuint32(mybuffer, 0) --same function as above (except mybuffer could also have been a string)
All functions in the buffer API are bounds-checked and will call
error() if the buffer size is exceeded, or if the given offset is
negative. However there are no limitations on memory alignment.
local mybuffer = bolt.createbuffer(16) mybuffer:setuin64(3, 123) --fine mybuffer:setuin64(17, 0) --error (writing out of bounds)
Endianness is always native. Values will be truncated as necessary using C’s type casts.
Reads a single byte from the buffer at the given offset and returns it as a signed integer. See Buffer for more on the buffer API.
local num = mybuffer:getint8(myoffset)
Reads a two-byte value from the buffer at the given offset and returns it as a signed integer. See Buffer for more on the buffer API.
local num = mybuffer:getint16(myoffset)
Reads a four-byte value from the buffer at the given offset and returns it as a signed integer. See Buffer for more on the buffer API.
local num = mybuffer:getint32(myoffset)
Reads an eight-byte value from the buffer at the given offset and returns it as a signed integer. See Buffer for more on the buffer API.
local num = mybuffer:getint64(myoffset)
Reads a single byte from the buffer at the given offset and returns it as an unsigned integer. See Buffer for more on the buffer API.
local num = mybuffer:getuint8(myoffset)
Reads a two-byte value from the buffer at the given offset and returns it as an unsigned integer. See Buffer for more on the buffer API.
local num = mybuffer:getuint16(myoffset)
Reads a four-byte value from the buffer at the given offset and returns it as an unsigned integer. See Buffer for more on the buffer API.
local num = mybuffer:getuint32(myoffset)
Reads an eight-byte value from the buffer at the given offset and returns it as an unsigned integer. See Buffer for more on the buffer API.
local num = mybuffer:getuint64(myoffset)
Reads a four-byte IEEE floating point value from the given offset and returns it as a number. See Buffer for more on the buffer API.
local num = mybuffer:getfloat32(myoffset)
Reads an eight-byte IEEE floating point value from the given offset and returns it as a number. See Buffer for more on the buffer API.
local num = mybuffer:getfloat64(myoffset)
Writes a single byte into the buffer at the given offset. See Buffer for more on the buffer API.
mybuffer:setint8(myoffset, myvalue)
Writes a two-byte value into the buffer at the given offset. See Buffer for more on the buffer API.
mybuffer:setint16(myoffset, myvalue)
Writes a four-byte value into the buffer at the given offset. See Buffer for more on the buffer API.
mybuffer:setint32(myoffset, myvalue)
Writes an eight-byte value into the buffer at the given offset. See Buffer for more on the buffer API.
mybuffer:setint64(myoffset, myvalue)
Writes a single unsigned byte into the buffer at the given offset. See Buffer for more on the buffer API.
mybuffer:setuint8(myoffset, myvalue)
Writes a two-byte unsigned value into the buffer at the given offset. See Buffer for more on the buffer API.
mybuffer:setuint16(myoffset, myvalue)
Writes a four-byte unsigned value into the buffer at the given offset. See Buffer for more on the buffer API.
mybuffer:setuint32(myoffset, myvalue)
Writes an eight-byte unsigned value into the buffer at the given offset. See Buffer for more on the buffer API.
mybuffer:setuint64(myoffset, myvalue)
Writes a number into the buffer at the given offset, as a four-byte IEEE floating-point value. See Buffer for more on the buffer API.
mybuffer:setfloat32(myoffset, myvalue)
Writes a number into the buffer at the given offset, as an eight-byte IEEE floating-point value. See Buffer for more on the buffer API.
mybuffer:setfloat64(myoffset, myvalue)
A window is a rectangle overlaid onto the game window. It has an internal Surface which can be rendered to and will be automatically shown at the window’s position. Windows also capture the user’s mouse and keyboard inputs and have callbacks which can be set to handle those inputs.
The window will initially be transparent, but will still capture mouse inputs, which will be visually confusing. Make sure to draw something to the window after creating it when testing this function.
The window contents will be cleared when the window changes size, which may happen for several reasons. Make sure to redraw in response to a onreposition if didresize is true, otherwise your window will turn invisible.
Closes and destroys the window. This is the only way for a window to be destroyed, other than the plugin stopping, which will destroy the window automatically.
Do not use the window again after calling this function on it. No more events will be received (although it is safe to call this from inside an event handler.)
mywindow:close()
Returns an integer which uniquely identifies this window. No other object will ever have this ID during this instance of the game process, even if this plugin is stopped and started again.
local id = mywindow:id()
Deletes any previous contents of the window and sets it to contain a single colour and alpha. See surface:clear.
Updates a rectangular subsection of this window with the given RGBA pixel data. See surface:subimage.
Starts repositioning for this window. This function changes how the user’s "drag" action is processed, and would usually be called from the onmousebutton callback for the left mouse button. Repositioning will occur until the user releases the left mouse button or until the repositioning is cancelled. In the first case, an onreposition event will be fired (even if the size and position din’t actually change.) During repositioning, all mouse events will be handled internally by the window, without calling its event handlers. The window will not actually change size or position until repositioning ends by the user releasing the mouse button.
This function takes two integer parameters. The first should be negative if the window’s left edge is being dragged, positive if the right edge is being dragged, or zero if neither. The second should be negative if the window’s top edge is being dragged, positive if the bottom edge is being dragged, or zero if neither. If both parameterss are zero then the window will be moved instead of resized.
The following example would cause the window to be dragged by its top-right edge:
mywindow:startreposition(1, -1)
Cancels repositioning for this window. The window’s position and size will not change and an onreposition event will not be sent.
mywindow:cancelreposition()
Sets an event handler for this window for reposition events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Reposition Event for documentation on this event object.
mywindow:onreposition(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for this window for mouse motion events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Mouse Motion Event for documentation on this event object.
mywindow:onmousemotion(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for this window for mouse button events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Mouse Button Event for documentation on this event object.
This handler runs immediately after a button is pressed down. When the button is released, onmousebuttonup will run.
mywindow:onmousebutton(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for this window for mouse button-up events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Mouse Button Event for documentation on this event object.
This handler runs immediately after a button is released. It shares the same type of event object as onmousebutton. If the user clicks the window and drags their mouse outside the window, the mouse motion events and button-up will still usually be captured by the window.
mywindow:onmousebuttonup(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for this window for mouse scroll events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Scroll Event for documentation on this event object.
mywindow:onscroll(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for this window for mouse leave events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Mouse Leave Event for documentation on this event object.
mywindow:onmouseleave(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
A browser object is a webview in an external OS-level window. It’s a
chromium-based browser backed by CEF. The browser takes up the whole
window - there are no decorations like tabs or a URL bar or back button,
etc. The browser can navigate or fetch normal internet URLs via
https://, or files in the plugin directory with plugin://.
It can also request certain URLs at https://bolt-api/ to
communicate with the Lua code running in the game process. Incoming
messages will be sent via the JavaScript window.postMessage
function.
Closes and destroys the browser. This is the only way for a browser to be destroyed, other than the plugin stopping, which will destroy the browser automatically.
Do not use the browser again after calling this function on it. No more events will be received (although it is safe to call this from inside an event handler.)
mybrowser:close()
Sends a message to the browser. The data can be either a string or a Buffer. It will be sent to the browser using the postMessage function, so to handle it in your browser application, just add an event listener for "message" to the window object. The event’s data will be an object with "type": "pluginMessage", and "content" will be an ArrayBuffer containing the Lua string that was passed to this function. Note that the binary data will be transferred exactly as it appeared in Lua, byte-for-byte - it will not be decoded or re-encoded in any way.
If this function is called immediately after creating the browser, it’s possible the message may arrive at the JavaScript process before the page has actually loaded. In this case, Bolt will queue the message until after the loading is complete. It’s therefore safe to assume plugin messages will never arrive before the ’DOMContentLoaded’ event.
mybrowser:sendmessage("some message contents")
Enables screen capture for this browser. The screen contents will be
sent to the browser using the postMessage function. The event’s data
will be an object with "type": "screenCapture", "width" and "height"
will be integers indicating the size of the captured area, and "content"
will be an ArrayBuffer of length width * height * 3. The contents
will be three bytes per pixel, in RGB format, in row-major order,
starting with the bottom-left pixel.
The data will be sent using a shared memory mapping, so the overhead is much lower than it would be to send all the data using sendmessage. However, downloading screen contents from the GPU will still slow the game down (takes around 2 to 5 milliseconds depending on window size), so Bolt will limit itself to capturing 4 frames per second via this function.
mybrowser:enablecapture()
Disables screen capture for this browser. See enablecapture.
mybrowser:disablecapture()
Opens a chromium devtools window for this browser. If one is already open, it will be focused instead of opening a new one. The devtools window can’t be closed through code, it can only be closed by a user action (e.g. clicking the ’X’) or by the browser itself closing.
Sets an event handler for this browser for close-request events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. Unlike most event handlers, this one is called with no parameters.
Bolt takes no default action other than calling this function, which
means nothing will happen by default when the user tries to close the
browser. To enable normal closing behaviour, add a closerequest handler
which calls mybrowser:close():
mybrowser:oncloserequest(function () mybrowser:close() end)
Sets an event handler for this browser for message events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. The function will be called with a string as the only parameter. The string will contain the binary data that was sent in the browser’s POST message.
mybrowser:onmessage(function (message) print(string.format("message received: %s", message)) end)
Note that this example assumes that the message is safely formattable text. This may or may not be the case, depending on what your browser’s web page is programmed to send.
An embedded browser object is a webview embedded into the game window. It behaves similarly to a Window, but with more state, and most of the normal events of a Window object are handled internally by the browser.
The browser is chromium-based, backed by CEF. The webview takes up the
whole rectangle - there are no decorations like tabs or a URL bar or
back button, etc. The browser can navigate or fetch normal internet URLs
via https://, or files in the plugin directory with
plugin://. It can also request certain URLs at
https://bolt-api/ to communicate with the Lua code running in the
game process. Incoming messages will be sent via the JavaScript
window.postMessage function.
Closes and destroys the browser. This is the only way for a browser to be destroyed, other than the plugin stopping, which will destroy the browser automatically.
Do not use the browser again after calling this function on it. No more events will be received (although it is safe to call this from inside an event handler.)
mybrowser:close()
Opens a chromium devtools window for this browser. If one is already open, it will be focused instead of opening a new one. The devtools window can’t be closed through code, it can only be closed by a user action (e.g. clicking the ’X’) or by the browser itself closing.
Sets an event handler for this browser for close-request events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. Unlike most event handlers, this one is called with no parameters.
Bolt takes no default action other than calling this function, which
means nothing will happen by default when the user tries to close the
browser. To enable normal closing behaviour, add a closerequest handler
which calls myembeddedbrowser:close():
myembeddedbrowser:oncloserequest(function () myembeddedbrowser:close() end)
Sets an event handler for this browser for message events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. The function will be called with a string as the only parameter. The string will contain the binary data that was sent in the browser’s POST message.
myembeddedbrowser:onmessage(function (message) print(string.format("message received: %s", message)) end)
Note that this example assumes that the message is safely formattable text. This may or may not be the case, depending on what your browser’s web page is programmed to send.
Sets an event handler for this browser for reposition events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Reposition Event for documentation on this event object.
The appropriate JavaScript events will also be sent internally to the browser; plugins don’t need to handle this event from Lua in order to implement any browser functionality.
mybrowser:onreposition(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for this embeddedbrowser for mouse motion events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Mouse Motion Event for documentation on this event object.
The appropriate JavaScript events will also be sent internally to the browser; plugins don’t need to handle this event from Lua in order to implement any browser functionality.
mybrowser:onmousemotion(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for this browser for mouse button events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Mouse Button Event for documentation on this event object.
The appropriate JavaScript events will also be sent internally to the browser; plugins don’t need to handle this event from Lua in order to implement any browser functionality.
This handler runs immediately after a button is pressed down. When the button is released, onmousebuttonup will run.
mybrowser:onmousebutton(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for this browser for mouse button-up events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Mouse Button Event for documentation on this event object.
The appropriate JavaScript events will also be sent internally to the browser; plugins don’t need to handle this event from Lua in order to implement any browser functionality.
This handler runs immediately after a button is released. It shares the same type of event object as onmousebutton. If the user clicks the window and drags their mouse outside the window, the mouse motion events and button-up will still usually be captured by the window.
mybrowser:onmousebuttonup(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for this browser for mouse scroll events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Scroll Event for documentation on this event object.
The appropriate JavaScript events will also be sent internally to the browser; plugins don’t need to handle this event from Lua in order to implement any browser functionality.
mybrowser:onscroll(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
Sets an event handler for this browser for mouse leave events. The function will overwrite any previous event handler, or, if it’s not a function, then any previous event handler will be deleted. See Mouse Leave Event for documentation on this event object.
The appropriate JavaScript events will also be sent internally to the browser; plugins don’t need to handle this event from Lua in order to implement any browser functionality.
mybrowser:onmouseleave(function (event) -- ... end)
It’s not safe to use an event object after the event handler has returned.
A Point object represents a point in 3D space. It’s mainly useful for doing transformation calculations with Transforms, to find, for example, the pixel coordinates on the user’s screen where a certain vertex is currently being rendered.
Transforms this Point by a Transform object and returns a new Point. The original Point object is not modified.
Transforms the X component of this point from the range [-1.0, 1.0] to the range [0, screen_width], and the Y component from the range [-1.0, 1.0] to the range [0, screen_height]. This will give the actual pixel position of a point in the game view, taking the relative position of the game view itself into account. The Z component, depth, is returned unmodified.
This is useful for a point that has been transformed from world space using a viewproj matrix (or a separate view and projection matrix). For any other point, the results will likely not be meaningful.
Points whose depth values are outside the range [-1.0, 1.0] are probably behind the camera and should be ignored.
local xpixel, ypixel, depth = mypoint:toscreen() if depth >= -1.0 and depth <= 1.0 then -- pixel coordinates are valid, do something with them end
Functionally, the only difference between this function and
togameview is that this adds game_view_x to X and
game_view_y to Y before returning them. In other words,
togameview() is relative to the game view whereas
toscreen() is relative to the overall window, bearing in mind
that the game view won’t necessarily fill the whole window depending on
the player’s HUD layout.
Transforms the X component of this point from the range [-1.0, 1.0] to the range [0, gameview_width], and the Y component from the range [-1.0, 1.0] to the range [0, gameview_height]. This will give the actual pixel position of a point in the game view. The Z component, depth, is returned unmodified.
This is useful for a point that has been transformed from world space using a viewproj matrix (or a separate view and projection matrix). For any other point, the results will likely not be meaningful.
Points whose depth values are outside the range [-1.0, 1.0] are probably behind the camera and should be ignored.
local xpixel, ypixel, depth = mypoint:togameview() if depth >= -1.0 and depth <= 1.0 then -- pixel coordinates are valid, do something with them end
Functionally, the only difference between this function and
toscreen is that toscreen() adds gameview_x to X and
gameview_y to Y before returning them, while this function doesn’t. In
other words, togameview() is relative to the game view whereas
toscreen() is relative to the overall window, bearing in mind
that the game view won’t necessarily fill the whole window depending on
the player’s HUD layout.
A transform represents a 4x4 transformation matrix. There’s no way to create a transform directly, they can only be obtained by querying Render3D Events. They’re useful for transforming points from model coordinates into world coordinates or screen pixel coordinates.
Decomposes the transform into the following nine constituent values, in this order: X, Y, Z, in model coordinates; scale factor X, Y and Z; yaw, pitch, roll, in radians.
Matrix decomposition is an experimental feature. It assumes the right-most column of the matrix to be (0, 0, 0, 1). That will always be the case in transforms returned by vertexanimation, which is the primary intended use of this function.
local x, y, z, xscale, yscale, zscale, yaw, pitch, roll = mytransform:decompose()
Returns the 16 floating-point values that make up this transformation matrix, in row-major order.
local m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16 = mytransform:get()
A Shader object comes from createvertexshader or createfragmentshader. Its only use is to pass to the createshaderprogram function. The same shader may be used to create any number of shader programs, but does not necessarily need to be kept after creating the program(s). It’s recommended to delete unneeded shader objects to save memory.
This object is part of the Appendix: custom shader API, which requires some understanding of shader programming to be able to use.
A Shader Program object comes from createshaderprogram. It can be used to draw to surfaces using custom vertex and fragment shaders.
This object is part of the Appendix: custom shader API, which requires some understanding of shader programming to be able to use.
Sets a shader attribute. This function is somewhat analogous to
glVertexAttribPointer, but not exactly the same. Firstly, this function
permanently enables attribute n for the given program,
and it’s an error to call this more than once for the same attribute
index for the same program. Secondly, the actual vertex data isn’t
needed for this call, and is instead passed as a
Shader Buffer to the draw function. This means that, unlike
glVertexAttribPointer, this API requires all vertex data to be contained
in a single shader buffer.
This function requires seven parameters, as follows:
Valid byte sizes for floats are 2, 4 and 8, and valid byte sizes for
integers are 1, 2, or 4. An invalid byte size will cause this function
to call error(). It will also call error() if is_signed is
false and is_float is true.
-- enable attribute 0 to read float32 values, three per attribute, -- beginning at byte 0 of the buffer and stepping forward by 12 bytes per attribute myprogram:setattribute(0, 4, true, false, 3, 0, 12)
Sets one integer as a uniform value. The first param is the "location" specified in GLSL and the second is the value. This value will be permanently associated with this location for this program, until it’s overwritten by another setuniform call.
myprogram:setuniform1i(location, myinteger)
Sets two integers as a uniform value. The first param is the "location" specified in GLSL and the rest are the values. These values will be permanently associated with this location for this program, until it’s overwritten by another setuniform call.
myprogram:setuniform2i(location, myint1, myint2)
Sets three integers as a uniform value. The first param is the "location" specified in GLSL and the rest are the values. These values will be permanently associated with this location for this program, until it’s overwritten by another setuniform call.
myprogram:setuniform3i(location, myint1, myint2, myint3)
Sets four integers as a uniform value. The first param is the "location" specified in GLSL and the rest are the values. These values will be permanently associated with this location for this program, until it’s overwritten by another setuniform call.
myprogram:setuniform4i(location, myint1, myint2, myint3, myint4)
Sets one float as a uniform value. The first param is the "location" specified in GLSL and the second is the value. This value will be permanently associated with this location for this program, until it’s overwritten by another setuniform call.
myprogram:setuniform1f(location, mynumber)
Sets two floats as a uniform value. The first param is the "location" specified in GLSL and the rest are the values. These values will be permanently associated with this location for this program, until it’s overwritten by another setuniform call.
myprogram:setuniform2f(location, mynum1, mynum2)
Sets three floats as a uniform value. The first param is the "location" specified in GLSL and the rest are the values. These values will be permanently associated with this location for this program, until it’s overwritten by another setuniform call.
myprogram:setuniform3f(location, mynum1, mynum2, mynum3)
Sets four floats as a uniform value. The first param is the "location" specified in GLSL and the rest are the values. These values will be permanently associated with this location for this program, until it’s overwritten by another setuniform call.
myprogram:setuniform4f(location, mynum1, mynum2, mynum3, mynum4)
Sets four floats as a uniform mat2x2 value. The first param is the "location" specified in GLSL, the second is the "transpose" boolean, and the rest are the values. Transposing a matrix turns it from row-major to column-major or vice versa. These values will be permanently associated with this location for this program, until it’s overwritten by another setuniform call.
myprogram:setuniformmatrix2f(location, false, n11, n21, n12, n22)
Sets nine floats as a uniform mat3x3 value. The first param is the "location" specified in GLSL, the second is the "transpose" boolean, and the rest are the values. Transposing a matrix turns it from row-major to column-major or vice versa. These values will be permanently associated with this location for this program, until it’s overwritten by another setuniform call.
myprogram:setuniformmatrix3f(location, false, n11, n21, n31, n12, n22, n32, n13, n23, n33)
Sets 16 floats as a uniform mat4x4 value. The first param is the "location" specified in GLSL, the second is the "transpose" boolean, and the rest are the values. Transposing a matrix turns it from row-major to column-major or vice versa. These values will be permanently associated with this location for this program, until it’s overwritten by another setuniform call.
In the following example, the 16 values are returned from get, taking advantage of the Lua feature where multiple return values can be used implicitly as multiple function arguments:
myprogram:setuniformmatrix4f(location, false, mytransform:get())
Sets a Surface as a uniform value. This will usually be used for uniform sampler2D variables. This value will be permanently associated with this location for this program, until it’s overwritten by another setuniform call. Note that if the surface is destroyed before being used for a render, the results will be undefined, so make sure to keep your surface object in scope for as long as it remains bound to any uniforms.
myprogram:setuniformsurface(location, mysurface)
Sets the depth buffer as a uniform value. This will usually be used for sampler2D variables. This function works like all other setuniform functions, but requiring a rendergameview event as the second argument, and, as such, it may only be called during an onrendergameview event.
bolt.onrendergameview(function (event) myprogram:setuniformdepthbuffer(location, event) end)
During rendering of the game view, the game engine maintains an explicit depth buffer. The depth buffer is like a normal surface, but not exactly the same: a normal surface has internal contents of format GL_RGBA, whereas the depth buffer has contents of format GL_DEPTH_COMPONENT32F. This means that while it can be sampled in a sampler2D like any other surface, the R component will contain the depth value for that pixel, the G and B components will be undefined, and the A component will usually be 1.0.
The depth component of any given pixel represents the Z-value of whatever was drawn on that pixel, and is used by the game to ensure that something far from the camera won’t be drawn in front of something nearer, regardless of what order they’re drawn in. The primary reason why a Bolt plugin may want to sample this data would be to create a pseudo-3D effect, where each pixel in an overlay may be drawn or not drawn depending on the user’s perspective. For example, if a fragment shader was set up to draw an overlay where each pixel is either opaque or transparent depending on the depth of that pixel, it would appear that objects in the game view were obscuring the overlay by being in front of it.
Z-values are logarithmic in the range [-1.0, +1.0]. -1.0 is immediately in front of the camera, 0.0 is a very short distance away from the camera, and 1.0 is the maximum view distance away from the camera.
The width and height of the depth buffer surface, in pixels, can be
retrieved by calling size() on the rendergameview event object,
since the size of the game view and the size of the depth buffer will
always match.
Uniform bindings in the Bolt API are permanent until overwritten, but event objects cannot be used after the even handler has returned. So, after calling this function, be careful not to use this program again after the rendergameview event handler has returned, until the uniform binding has been changed to something else.
Invokes this shader program to draw to a surface. Parameters are the target surface, a shader buffer, and an integer indicating the number of vertices to draw. Each 3 vertices will make a triangle. If the number of vertices isn’t a multiple of 3, then the last 1 or 2 will usually be ignored.
Invokes this shader program to draw to the game view. This can only be called during a rendergameview event, as the event object needs to be passed to this function. Parameters are a rendergameview event object, a shader buffer, and an integer indicating the number of vertices to draw. Each 3 vertices will make a triangle. If the number of vertices isn’t a multiple of 3, then the last 1 or 2 will usually be ignored.
This is not to be confused with Buffer. The difference is that Buffer objects exist in RAM and can be read from or written to at any time. Shader Buffer objects exist in VRAM, cannot be read from, and cannot be written to after creation, but can be used to run custom shader programs.
This object is part of the Appendix: custom shader API, which requires some understanding of shader programming to be able to use.
A Batch2D event comes from onrender2d. or onminimaprender2d. It occurs when the game draws a batch of 2D icons to the screen, or to the minimap image. 2D icon renders are uploaded to the GPU in batches rather than one-at-a-time, so to look for an individual image, you’ll need to iterate through each individual image in the batch, by iterating through the vertices. The number of vertices per image is usually 6 (i.e. two polygons) but it’s recommended to call verticesperimage to get this number instead of hard-coding it into your plugin.
A Batch2D will only ever have one texture associated with it - a large surface, usually 8192x8192, with a lot of rectangular images on it. This is commonly called a "texture atlas". Each vertex has associated data which points to a rectangular section of that texture. This can be queried to find the exact pixel contents of the image being drawn.
When querying vertex data, keep in mind that Lua indices start at 1, so the valid range is 1 to vertexcount, inclusive:
for i = 1, event:vertexcount() do -- ...
It’s not safe to use an event object after the event handler has returned.
Returns the number of vertices in a 2D batch object. Divide by verticesperimage to get the number of icons being rendered.
Returns the number of vertices per individual image in this batch. At time of writing, this will always return 6 (i.e. enough to draw two separate triangles). To future-proof against possible engine updates, it’s recommended to use this function instead of hard-coding the number 6 into your plugin.
Returns the width and height of the target area of this render, in pixels.
For a miminap render event, this will be the size of the minimap image, usually 256x256. For a normal render2d event, this will be proportional to the size of the inner area of the game window, given by gamewindowsize - that is, if the user has an interface scaling other than 100%, it will be bigger or smaller than that area, proportionally.
Returns the interface scaling being applied to this render. For example, if the interface scaling is set to 135%, this function will return 1.35. For minimap2d handlers this function will always return 1.
Given an index of a vertex in a batch, returns its X and Y position on the target surface, in pixel coordinates. As with all screen coordinates in the Bolt API, this is relative to the top-left pixel of the screen.
As explained in targetsize, the size of the target surface won’t necessarily match the screen size. If interface scaling is in use, the target surface will be proportionally bigger or smaller than the actual game window. This function provides precise pixel locations independent of interface scaling, which are useful for distance comparisons. If a plugin needs to convert to actual screen coordinates to support interface scaling, it should use vertexscaledxy instead.
For a minimap2d handler, vertextargetxy and vertexscaledxy are the same function.
Given an index of a vertex in a batch, returns its X and Y position on the screen, in pixel coordinates. As with all screen coordinates in the Bolt API, this is relative to the top-left pixel of the screen.
This is the same as vertextargetxy except the results are multiplied by the player’s interface scaling setting (given by targetscale) to give actual screen coordinates. As a result, the values are less precise and are not necessarily whole numbers, but if a plugin wants to draw an overlay above a vertex, this is the correct function to use.
For a minimap2d handler, vertextargetxy and vertexscaledxy are the same function.
Given an index of a vertex in a batch, returns six values describing the vertex’s image in the texture atlas. The first four values are the x, y, width and height, in pixels. The fifth and six values are booleans which relate to U and V coordinates respectively: if the value is true, U or V coordinates which fall outside the sub-image should wrap around within it. If the value is false, values should instead be clamped to the edges of the sub-image.
Given an index of a vertex in a batch, returns the vertex’s associated "UV" coordinates. The values will either be floating-point numbers in the range 0.0 - 1.0, or nil.
If either of the returned values is nil, then the texture data for this vertex should be ignored and RGBA values of 1.0 should be used instead. Otherwise, the UVs are relative to the entire texture atlas, but may be outside of the sub-image rectangle. See See vertexatlasdetails to see how to handle those cases.
Given an index of a vertex in a batch, returns the red, green, blue and alpha values for that vertex, in that order. All four values will be floating-point numbers in the range 0.0 - 1.0.
These values are multiplied with the texture, so values less than 1.0 will darken the texture. A common use for this is to draw coloured text.
Returns the unique ID of the texture associated with this render. There will always be one (and only one) texture atlas associated with a 2D render batch.
The plugin API does not have a way to get a texture by its ID; this is intentional, as it wouldn’t always be safe to do so. The purpose of this function is to be able to compare texture IDs together to check if the current texture is the same one that was used in a previous render.
Compares a section of the texture atlas for this batch to some RGBA pixel data. The data can be a string or a Buffer. The function will return true only if the bytes are an exact match. It internally uses memcmp from the C library. This means comparing each set of contiguous data is very fast, but, since the data needs to be contiguous, it can only be checked one row at a time.
Internally, Bolt compares against data taken from the CPU just before being uploaded to the GPU, so there will never be any imprecision issues caused by different GPUs/drivers/etc.
The example below checks if the pixels at x,y and (x+1),y are fully opaque and red.
mybatch:texturecompare(x, y, "\xFF\x00\x00\xFF\xFF\x00\x00\xFF")
Normally the X and Y coordinates should be calculated from vertexatlasdetails.
Gets the RGBA data starting at a given coordinate of the texture atlas, and returns it as a Lua string.
The following example would return an 8-bytes-long string, containing the RGBA data for two pixels.
mybatch:texturedata(x, y, 8)
Using this function to search for specific textures isn’t recommended as it would be noticeably slower than using texturecompare.
A Render3D event comes from onrender3d. It occurs when a 3D model is rendered to the game view. All of the model’s vertex coordinates and texture data can be queried, along with its animation data if the model is animated.
Bolt has a complex set of functions for doing calculations with 3D space data. Render3D’s vertexpoint function returns static model data which will always be the same for two instances of the same model even if they’re placed differently, scaled or rotated differently, doing different animations, etc. To get the model’s position in world space, use modelmatrix:
local modelpoint = myevent:vertexpoint(1) local worldpoint = modelpoint:transform(myevent:modelmatrix())
This gives an X, Y and Z in world coordinates, which can be used to find its position on the world map. From there, you can use its viewprojmatrix to find where it will appear on the user’s screen, in pixel coordinates:
local pixelx, pixely = worldpoint:transform(myevent:viewprojmatrix()):toscreen()
This will give the pixel coordinates at which a static vertex would be rendered. If the model is animated, you might like to apply its animation data to the transform to get the vertex’s true position. Each vertex in an animated model has an animation transform associated with it. An animation transform is from model-space to model-space, so it must be applied before any of the other transforms.
local modelpoint = myevent:vertexpoint(1) local animatedpoint = modelpoint:transform(myevent:vertexanimation(1)) local worldpoint = animatedpoint:transform(myevent:modelmatrix())
Like Batch2D events, Render3D events always have exactly one texture atlas associated with them, but each vertex may be textured with a different section of that atlas. Unlike Batch2D however, there’s an extra step involved in getting the pixel data associated with a vertex. The vertex’s meta-id must be queried with vertexmeta, then pass the meta-id to atlasxywh to get the correct section of the texture atlas:
local mymeta = event:vertexmeta(1) local x, y, w, h = event:atlasxywh(mymeta)
When querying vertex data, keep in mind that Lua indices start at 1, so the valid range is 1 to vertexcount, inclusive:
for i = 1, event:vertexcount() do -- ...
It’s not safe to use an event object after the event handler has returned.
Given a vertex number, returns a Point representing the vertex’s model coordinates, according to the static model data.
local modelpoint = event:vertexpoint(1)
Returns the Transform representing the model matrix of the model being rendered. This can be used to transform points from model coordinates into world coordinates.
local modelmatrix = event:modelmatrix() local modelpoint = event:vertexpoint(1) local worldpoint = modelpoint:transform(modelmatrix)
Returns the Transform representing the view matrix of the model being rendered. Unless you understand the difference between a view matrix and a projection matrix, this probably isn’t useful to you; viewprojmatrix can be used to more efficiently get a transform from world coordinates to pixel coordinates.
Returns the Transform representing the projection matrix of the model being rendered. Unless you understand the difference between a view matrix and a projection matrix, this probably isn’t useful to you; viewprojmatrix can be used to more efficiently get a transform from world coordinates to pixel coordinates.
Returns the Transform representing the combined view and projection matrices, commonly called the "viewproj matrix", of the model being rendered. This can be used to transform points from world coordinates into screen coordinates.
Returns a Transform representing the inverse of the view matrix (a.k.a. the camera matrix) for this render. This can be used to transform points from eye-space to world-space.
Given a vertex number, returns a meta-ID (an integer) relating to its associated texture image. This can then be passed to atlasxywh.
local meta = event:vertexmeta(1) local x, y, w, h = event:atlasxywh(meta)
Given a vertex number, returns the X, Y, width and height of its associated image in the texture atlas, in pixel coordinates.
local meta = event:vertexmeta(1) local x, y, w, h = event:atlasxywh(meta)
Given a vertex number, returns the vertex’s associated "UV" coordinates.
The values will be floating-point numbers, usually in the range 0.0 - 1.0. They are relative to image in the texture atlas. They may fall outside the image (and therefore outside the range 0.0 - 1.0), in which case they’re expected to wrap around within the image.
Given a vertex number, returns the red, green, blue and alpha values for that vertex, in that order. All four values will be floating-point numbers in the range 0.0 - 1.0.
These values are multiplied with the texture, so values less than 1.0 will darken the texture (or make it more transparent in the case of the alpha channel). A common use for this is to draw differently "tinted" versions of the same model using the same texture.
Returns the unique ID of the texture associated with this render. There will always be one (and only one) texture atlas associated with a 3D render.
The plugin API does not have a way to get a texture by its ID; this is intentional, as it wouldn’t always be safe to do so. The purpose of this function is to be able to compare texture IDs together to check if the current texture is the same one that was used in a previous render.
Compares a section of the texture atlas for this batch to some RGBA pixel data. The data can be a string or a Buffer. The function will return true only if the bytes are an exact match. It internally uses memcmp from the C library. This means comparing each set of contiguous data is very fast, but, since the data needs to be contiguous, it can only be checked one row at a time.
Internally, Bolt compares against data taken from the CPU just before being uploaded to the GPU, so there will never be any imprecision issues caused by different GPUs/drivers/etc.
The example below checks if the pixels at x,y and (x+1),y are fully opaque and red.
event:texturecompare(x, y, "\xFF\x00\x00\xFF\xFF\x00\x00\xFF")
Normally the X and Y coordinates should be calculated from atlasxywh.
Gets the RGBA data starting at a given coordinate of the texture atlas, and returns it as a Lua string.
The following example would return an 8-bytes-long string, containing the RGBA data for two pixels.
mybatch:texturedata(x, y, 8)
Using this function to search for specific textures isn’t recommended as it would be noticeably slower than using texturecompare.
Given a vertex number, returns the Transform object that would be applied to its static model, in model coordinates, to transform it into its current animated position.
It’s a fatal error to call this function on a render event for a non-animated model, since non-animated models have no bone transforms that could be queried. To check if the model is animated, use animated.
Returns a boolean value indicating whether this model is animated. Animated models can have multiple bones which can move independently of each other. If this function returns false, then the model has no animation data, and calling vertexanimation will result in an error.
if event:animated() then -- it's safe to call event:vertexanimation end
Returns the X, Y and Z of the camera position, in world coordinates, during this render.
local x, y, z = event:cameraposition()
A render-particles event comes from onrenderparticles. It occurs when a set of particles from one or more particle emitters are rendered to the 3D game view. Render-particles events are fairly similar to render3d events in the way textures and transforms work, with one big difference: the world position of a particle vertex cannot be queried directly. Instead, plugins can query the world position the particle a vertex belongs to, using the vertexparticleorigin function. If a plugin really needs to know the positioning of each individual vertex, it will need to apply two offsets to that position: the 3D offset given by vertexworldoffset, which is added directly to the world position, and the 2D offset given by vertexeyeoffset, which is added in eye-space, i.e. relative to the direction the camera is facing, for billboarding.
bolt.onrenderparticles(function (event) for i = 1, event:vertexcount() do -- view and projection matrix local viewmatrix = event:viewmatrix() local projmatrix = event:projmatrix() -- the world position of this particle local x, y, z = event:vertexparticleorigin(i):get() -- world offset of this individual vertex of the particle local wx, wy, wz = event:vertexworldoffset(i) -- eye offset of this individual vertex of the particle local ex, ey = event:vertexeyeoffset(i) -- apply transforms and offsets in the correct order: local eyepoint = bolt.point(x + wx, y + wy, z + wz):transform(viewmatrix) local eyex, eyey, eyez = eyepoint:get() local screenpoint = bolt.point(eyex + ex, eyey + ey, eyez):transform(projmatrix) local xpixel, ypixel, depth = screenpoint:toscreen() end end)
Given a vertex number, returns a Point representing the position of the particle that the vertex belongs to, in world coordinates. All vertices of a particle will have the same origin, since they all belong to the same particle.
local worldpoint = event:vertexparticleorigin(i)
Given a vertex number, returns three numbers representing the X, Y and Z offset of that vertex from the particle’s origin, in world space. See Render Particles Event for more info on offsets.
local xoffset, yoffset, zoffset = event:vertexworldoffset(i)
Given a vertex number, returns two numbers representing the X and Y offset of that vertex from the particle’s origin, in eye-space (i.e. relative to the direction the camera is facing, for billboarding.) See Render Particles Event for more info on offsets.
local xoffset, yoffset = event:vertexeyeoffset(i)
Given a vertex number, returns the vertex’s associated "UV" coordinates.
The values will be floating-point numbers, usually in the range 0.0 - 1.0. They are relative to image in the texture atlas. They may fall outside the image (and therefore outside the range 0.0 - 1.0), in which case they’re expected to wrap around within the image.
Given a vertex number, returns the red, green, blue and alpha values for that vertex, in that order. All four values will be floating-point numbers in the range 0.0 - 1.0.
These values are multiplied with the texture, so values less than 1.0 will darken the texture (or make it more transparent in the case of the alpha channel). A common use for this is to draw differently "tinted" versions of the same type of particle using the same texture.
Given a vertex number, returns a meta-ID (an integer) relating to its associated texture image. This can then be passed to atlasxywh.
local meta = event:vertexmeta(1) local x, y, w, h = event:atlasxywh(meta)
Given a vertex number, returns the X, Y, width and height of its associated image in the texture atlas, in pixel coordinates.
local meta = event:vertexmeta(1) local x, y, w, h = event:atlasxywh(meta)
Returns the unique ID of the texture associated with this render. There will always be one (and only one) texture atlas associated with a particle render.
The plugin API does not have a way to get a texture by its ID; this is intentional, as it wouldn’t always be safe to do so. The purpose of this function is to be able to compare texture IDs together to check if the current texture is the same one that was used in a previous render.
Compares a section of the texture atlas for this batch to some RGBA pixel data. The data can be a string or a Buffer. The function will return true only if the bytes are an exact match. It internally uses memcmp from the C library. This means comparing each set of contiguous data is very fast, but, since the data needs to be contiguous, it can only be checked one row at a time.
Internally, Bolt compares against data taken from the CPU just before being uploaded to the GPU, so there will never be any imprecision issues caused by different GPUs/drivers/etc.
The example below checks if the pixels at x,y and (x+1),y are fully opaque and red.
event:texturecompare(x, y, "\xFF\x00\x00\xFF\xFF\x00\x00\xFF")
Gets the RGBA data starting at a given coordinate of the texture atlas, and returns it as a Lua string.
The following example would return an 8-bytes-long string, containing the RGBA data for two pixels.
mybatch:texturedata(x, y, 8)
Using this function to search for specific textures isn’t recommended as it would be noticeably slower than using texturecompare.
Returns a Transform representing the projection matrix being used for this particle render.
Returns the Transform representing the combined view and projection matrices, commonly called the "viewproj matrix", for this particle render. This can be used to transform points from world coordinates into screen coordinates.
Returns a Transform representing the inverse of the view matrix (a.k.a. the camera matrix) for this particle render. This can be used to transform points from eye-space to world-space.
Returns the X, Y and Z of the camera position, in world coordinates, during this render.
local x, y, z = event:cameraposition()
A render-billboard event comes from onrenderbillboard. It occurs when a billboard is drawn to the game view. A billboard is a two-dimensional shape in 3D space by having it directly face the camera.
Billboarding is achieved in a similar way to a Render3D event, except that in a Render3D event each vertex would have different model coordinates before being transformed. In a RenderBillboard event, each set of 6 vertices usually has exactly the same model coordinate, but has an offset that must be applied to it in "eye space", which is between the view and projection matrices.
The following example shows step-by-step how to translate each vertex to its pixel coordinates on the user’s screen (the "screenx" and "screeny" local variables):
bolt.onrenderbillboard(function (event) local modelmatrix = event:modelmatrix() local viewmatrix = event:viewmatrix() local projmatrix = event:projmatrix() for i = 1, event:vertexcount() do local modelpoint = event:vertexpoint(i) local offsetx, offsety = event:vertexeyeoffset(i) local eyepointx, eyepointy, eyepointz = modelpoint:transform(modelmatrix):transform(viewmatrix):get() local movedpoint = bolt.point(eyepointx + offsetx, eyepointy + offsety, eyepointz) local screenx, screeny, _ = movedpoint:transform(projmatrix):toscreen() end end)
It’s not safe to use an event object after the event handler has returned.
Returns the number of vertices per individual image in this batch. At time of writing, this will always return 6 (i.e. enough to draw two separate triangles). To future-proof against possible engine updates, it’s recommended to use this function instead of hard-coding the number 6 into your plugin.
Given a vertex number, returns a Point representing the vertex’s model coordinates.
local modelpoint = event:vertexpoint(1)
Given a vertex number, returns two numbers representing the X and Y offset of that vertex from the particle’s origin, in eye-space (i.e. between the view and projection transforms, as described in Render Billboard Event).
local xoffset, yoffset = event:vertexeyeoffset(i)
Given a vertex number, returns the vertex’s associated "UV" coordinates.
The values will be floating-point numbers, usually in the range 0.0 - 1.0. They are relative to image in the texture atlas.
At time of writing, UV coordinates for renderbillboard are stored as integers, meaning each one can only be 0 or 1.
Given a vertex number, returns the red, green, blue and alpha values for that vertex, in that order. All four values will be floating-point numbers in the range 0.0 - 1.0.
These values are multiplied with the texture, so values less than 1.0 will darken the texture (or make it more transparent in the case of the alpha channel). A common use for this is to draw differently "tinted" versions of the same image using the same texture.
Given a vertex number, returns a meta-ID (an integer) relating to its associated texture image. This can then be passed to atlasxywh.
local meta = event:vertexmeta(1) local x, y, w, h = event:atlasxywh(meta)
Given a vertex number, returns the X, Y, width and height of its associated image in the texture atlas, in pixel coordinates.
local meta = event:vertexmeta(1) local x, y, w, h = event:atlasxywh(meta)
Returns the unique ID of the texture associated with this render. There will always be one (and only one) texture atlas associated with a billboard render.
The plugin API does not have a way to get a texture by its ID; this is intentional, as it wouldn’t always be safe to do so. The purpose of this function is to be able to compare texture IDs together to check if the current texture is the same one that was used in a previous render.
Compares a section of the texture atlas for this batch to some RGBA pixel data. The data can be a string or a Buffer. The function will return true only if the bytes are an exact match. It internally uses memcmp from the C library. This means comparing each set of contiguous data is very fast, but, since the data needs to be contiguous, it can only be checked one row at a time.
Internally, Bolt compares against data taken from the CPU just before being uploaded to the GPU, so there will never be any imprecision issues caused by different GPUs/drivers/etc.
The example below checks if the pixels at x,y and (x+1),y are fully opaque and red.
event:texturecompare(x, y, "\xFF\x00\x00\xFF\xFF\x00\x00\xFF")
Gets the RGBA data starting at a given coordinate of the texture atlas, and returns it as a Lua string.
The following example would return an 8-bytes-long string, containing the RGBA data for two pixels.
mybatch:texturedata(x, y, 8)
Using this function to search for specific textures isn’t recommended as it would be noticeably slower than using texturecompare.
Returns a Transform representing the projection matrix being used for this billboard render.
Returns the Transform representing the combined view and projection matrices, commonly called the "viewproj matrix", for this particle render. This can be used to transform points from world coordinates into screen coordinates.
Returns a Transform representing the inverse of the view matrix (a.k.a. the camera matrix) for this particle render. This can be used to transform points from eye-space to world-space.
Returns the X, Y and Z of the camera position, in world coordinates, during this render.
local x, y, z = event:cameraposition()
A render-icon event comes from onrendericon or onrenderbigicon. It occurs when a pre-rendered icon is drawn to the game view. Icons refer to items appearing in the UI, such as the player’s inventory, equipment screen, or action bar slots. These appear to be 2D images but in reality they’re pre-rendered "snapshots" of the item’s 3D models, which can make them very difficult to work with compared to a normal 2D image. Bolt attempts to simplify this by storing some of the details of the models rendered at the time when the icon is created, and allows plugins to query this stored data. However this means that some data is missing compared to a Render3D event; most notably, the texture data is currently not saved.
An icon may contain any number of models, and each vertex in each model may be queried using the functions of this event.
bolt.onrendericon(function (event) for model = 1, event:modelcount() do for vertex = 1, event:modelvertexcount(model) do let point = event:modelvertexpoint(model, vertex) -- ... end end end)
The view and projection matrix used when pre-rendering the item can also be queried for each model. There’s no model matrix for this process, so the model coordinates are treated as if they were the world coordinates. Some items, especially most weapons’ off-hand variants, use the same item model viewed from a different camera angle, which means the only way to differentiate between them is to decompose the view matrix.
It’s not safe to use an event object after the event handler has returned.
Returns the x, y, width and height of where this icon is being drawn on the screen, in pixel coordinates. The X and Y are relative to the top-left pixel of the inner area of the window.
local x, y, width, height = event:xywh()
Returns the number of 3D models that were rendered to this icon.
for model = 1, event:modelcount() do -- ... end
Given a model index, returns the number of vertices in that model.
for model = 1, event:modelcount() do for vertex = 1, event:modelvertexcount(model) do -- ... end end
Given a model number and vertex number, returns a Point representing the vertex’s model coordinates.
local point = event:modelvertexpoint(model, vertex)
Given a model number and a vertex number, returns the red, green, blue and alpha values for that vertex, in that order. All four values will be floating-point numbers in the range 0.0 - 1.0.
These values are multiplied with the texture, so values less than 1.0 will darken the texture (or make it more transparent in the case of the alpha channel). A common use for this is to draw differently "tinted" versions of the same model using the same texture.
local red, green, blue, alpha = event:modelvertexcolour(model, vertex)
Returns the width and height of the target area of this render, in pixels.
This will be proportional to the size of the inner area of the game window, given by gamewindowsize - that is, if the user has an interface scaling other than 100%, it will be bigger or smaller than that area, proportionally.
local width, height = event:targetsize()
Given a model number, returns a Transform representing the view matrix that was used to render it.
local viewmatrix = event:modelviewmatrix(model)
Given a model number, returns a Transform representing the projection matrix that was used to render it.
local projmatrix = event:modelprojectionmatrix(model)
Given a model number, returns a Transform representing the combined view and projection matrices, commonly called the "viewproj matrix", that was used to render that model. These are pre-multiplied in the shader, so using the viewproj matrix is more efficient than using the view and projection matrices separately.
local viewprojmatrix = event:modelviewprojmatrix(model)
Returns the red, green, blue and alpha values that this icon is being rendered with. These values are multiplied with the texture, so values less than 1.0 will darken the icon (or make it more transparent in the case of the alpha channel). This is usually used to draw transparent or "tinted" versions of the same pre-rendered icon. For example, a placeholder item in your bank will be drawn slightly transparent, even though the icon itself is non-transparent, to indicate that the item is a placeholder and not an actual item.
local red, green, blue, alpha = event:colour()
A Minimap Terrain event comes from onminimapterrain. It occurs when the background terrain gets drawn to the minimap image, which usually happens once per frame, unless the minimap isn’t visible. The terrain can’t be inspected directly, but this event can be queried for some useful information, like the angle the minimap is rotated to and the world position it’s centered on.
It’s not safe to use an event object after the event handler has returned.
Returns the angle at which the minimap background image is being rendered, in radians. The angle is 0 when upright (facing directly north), and increases counter-clockwise (note that turning the camera clockwise rotates the minimap counter-clockwise and vice versa).
Returns the scale at which the minimap background image is being rendered. This indicates how far in or out the minimap is zoomed. It appears to be capped between roughly 0.5 and 3.5.
Returns an estimate of the X and Y position the minimap is centered on, in world coordinates. This is only a rough estimate and can move around a lot even while standing still. It usually doesn’t vary by more than half a tile.
A render minimap event comes from onrenderminimap. It occurs when the minimap image gets drawn to the game window, which usually happens once per frame, unless the minimap isn’t visible. This event can be used to find out the location of the minimap on the user’s screen. To get the minimap contents, see Minimap Terrain Event and onminimaprender2d.
It’s not safe to use an event object after the event handler has returned.
A rendergameview event comes from onrendergameview. It occurs when the game view is finalised and drawn to the screen, which will happen once per frame unless the game view is not visible (e.g. player is in the lobby or viewing the map.)
During this event, it’s possible to render surfaces to the game view using drawtogameview. This will mean the surface will be drawn as an overlay onto the 3D game view, but below any UI elements. When using the custom shader API, it’s also possible to draw directly to the game view using a shader program during this event, using drawtogameview, which has the added bonus of being able to bind the game’s depth buffer as a sampler2D. Sampling the game’s depth buffer can be used to achieve a pseudo-3D effect in an overlay. See that function’s documentation for more info.
It’s not safe to use an event object after the event handler has returned.
Returns the width and height of the game view being rendered, in pixels. This will also be the width and height of the depth buffer.
local width, height = event:size()
Returns the x, y, width and height of the section of the minimap image being drawn, in pixels.
local x, y, width, height = event:sourcexywh()
Returns the x, y, width and height of where the minimap is being drawn on the screen, in pixel coordinates. The X and Y are relative to the top-left pixel of the inner area of the window.
local x, y, width, height = event:targetxywh()
Returns the width and height of the target area of this render, in pixels.
This will be proportional to the size of the inner area of the game window, given by gamewindowsize - that is, if the user has an interface scaling other than 100%, it will be bigger or smaller than that area, proportionally.
local width, height = event:targetsize()
A SwapBuffers event comes from onswapbuffers. This event is named after the "SwapBuffers" function in OpenGL, and as such, it runs every time a new frame is about to be shown on the player’s computer screen. How often the event fires will therefore depend on the user’s current FPS. The event is very useful as a "sync point", i.e. to ensure that some Lua code will run at regular intervals. The event object itself can be used to refer to the finished screen contents as it will appear to the user, such as by reading pixel data from the screen.
Unlike other events in the Bolt API, which fire immediately after the event in question has happened, SwapBuffers events fire immediately before the buffers are actually swapped. This means any rendering done in a SwapBuffers event will be immediately visible on the frame that the player is about to see.
It’s not safe to use an event object after the event handler has returned.
Reads pixel data from a portion of the screen, defined by the given X, Y, width and height. The bounds are not allowed to exceed the size of the game window (given by gamewindowsize) and width and height are not allowed to be negative.
The function will return a Buffer with size (width * height * 3) containing RGB pixel data. The first row of data will be the one that’s nearest the bottom of the user’s screen, then continuing upwards.
The length of time this function takes will be proportional to the number of pixels being read, but an expected time to read a 1920x1080 rectangle would be around 5 milliseconds, so use with care.
Any windows, embedded browsers, or overlay contents drawn by any plugins will not be included in the image data.
local gamewidth, gameheight = bolt.gamewindowsize() local buffer = event:readpixels(0, 0, gamewidth, gameheight)
Copies some data from the user’s screen to a surface. Parameters are the X, Y, width and height of the region of the user’s screen, followed by the X, Y, width and height of the target area of the surface. Any previous contents of the surface in the destination area will be overwritten with a fully opaque image.
This function is preferable over readpixels as the contents of the image never need to be retrieved from the GPU, which is slow.
Any windows, embedded browsers, or overlay contents drawn by any plugins will not be included in the image.
local gamewidth, gameheight = bolt.gamewindowsize() local mysurface = bolt.createsurface(gamewidth, gameheight) local buffer = event:copytosurface(mysurface, 0, 0, gamewidth, gameheight, 0, 0, gamewidth, gameheight)
A Reposition event comes from onreposition. It indicates that the size or position of a Window has changed. This could happen for several reasons, such as the game window being resized, so never assume that it won’t happen.
didresize can be used to check if the window size changed. If it did, the window contents have been cleared to transparent and must be redrawn.
It’s not safe to use an event object after the event handler has returned.
Returns the new x, y, width and height that the window was repositioned to.
local newx, newy, newwidth, newheight = event:xywh()
Returns a boolean value indicating whether the window changed size. If true, the contents of the window were cleared and need to be redrawn.
if event:didresize() then -- re-draw something to the window end
A Mouse Motion event comes from bolt.onmousemotion or window:onmousemotion. It occurs when the mouse moves while inside the game window. Each window or embedded browser can only receive one mouse motion event per frame - the latest position will always be sent, but some position updates in between will be dropped. The same goes for bolt.onmousemotion.
It’s not safe to use an event object after the event handler has returned.
Returns the x and y for this mouse event in pixels, relative to the top-left of the window that it relates to.
local x, y = event:xy()
Returns a boolean value indicating whether ctrl was held when
this event was fired.
local ctrl = event:ctrl()
Returns a boolean value indicating whether shift was held when
this event was fired.
local shift = event:shift()
Returns a boolean value indicating whether the meta key (also known as super, command, or the "windows key") was held when this event was fired.
local meta = event:meta()
Returns a boolean value indicating whether alt was held when this
event was fired.
local alt = event:alt()
Returns a boolean value indicating whether caps lock was on when this event was fired.
local capslock = event:capslock()
Returns a boolean value indicating whether numlock was on when this event was fired.
local numlock = event:numlock()
Returns three boolean values indicating whether each primary mouse button was held when this event fired, in the order: left, right, middle.
local lmb, rmb, mmb = event:mousebuttons()
A Mouse Button event comes from bolt.onmousebutton, bolt.onmousebuttonup, window:onmousebutton, or window:onmousebuttonup. It occurs when one of the three primary mouse buttons is pressed or released. The action of pressing a mouse button will be captured by the window or embedded browser under the mouse at the time. In the case of the left mouse button, the button-up event will usually be captured by the window that was clicked on, as well as any mouse motion events that happen between the press and release.
It’s not safe to use an event object after the event handler has returned.
Returns the x and y for this mouse event in pixels, relative to the top-left of the window that it relates to.
local x, y = event:xy()
Returns a boolean value indicating whether ctrl was held when
this event was fired.
local ctrl = event:ctrl()
Returns a boolean value indicating whether shift was held when
this event was fired.
local shift = event:shift()
Returns a boolean value indicating whether the meta key (also known as super, command, or the "windows key") was held when this event was fired.
local meta = event:meta()
Returns a boolean value indicating whether alt was held when this
event was fired.
local alt = event:alt()
Returns a boolean value indicating whether caps lock was on when this event was fired.
local capslock = event:capslock()
Returns a boolean value indicating whether numlock was on when this event was fired.
local numlock = event:numlock()
Returns three boolean values indicating whether each primary mouse button was held when this event fired, in the order: left, right, middle.
local lmb, rmb, mmb = event:mousebuttons()
Returns an integer representing the mouse button that was pressed or released. Possible values are 1 for the left mouse button, 2 for the right mouse button, and 3 for the middle mouse button (clicking the mouse wheel).
A Scroll event comes from bolt.onscroll or window:onscroll. It occurs when the user scrolls their mouse wheel while the mouse is inside the game window.
It’s not safe to use an event object after the event handler has returned.
Returns the x and y for this mouse event in pixels, relative to the top-left of the window that it relates to.
local x, y = event:xy()
Returns a boolean value indicating whether ctrl was held when
this event was fired.
local ctrl = event:ctrl()
Returns a boolean value indicating whether shift was held when
this event was fired.
local shift = event:shift()
Returns a boolean value indicating whether the meta key (also known as super, command, or the "windows key") was held when this event was fired.
local meta = event:meta()
Returns a boolean value indicating whether alt was held when this
event was fired.
local alt = event:alt()
Returns a boolean value indicating whether caps lock was on when this event was fired.
local capslock = event:capslock()
Returns a boolean value indicating whether numlock was on when this event was fired.
local numlock = event:numlock()
Returns three boolean values indicating whether each primary mouse button was held when this event fired, in the order: left, right, middle.
local lmb, rmb, mmb = event:mousebuttons()
Returns a boolean value representing the scroll direction. False means scrolling down, toward the user, and true means scrolling up, away from the user.
local direction = event:direction()
A Mouse Leave event comes from window:onmouseleave. It occurs when the user moves their mouse such that it’s no longer inside the window or embedded browser.
It’s not safe to use an event object after the event handler has returned.
Returns the x and y for this mouse event in pixels, relative to the top-left of the window that it relates to.
local x, y = event:xy()
Returns a boolean value indicating whether ctrl was held when
this event was fired.
local ctrl = event:ctrl()
Returns a boolean value indicating whether shift was held when
this event was fired.
local shift = event:shift()
Returns a boolean value indicating whether the meta key (also known as super, command, or the "windows key") was held when this event was fired.
local meta = event:meta()
Returns a boolean value indicating whether alt was held when this
event was fired.
local alt = event:alt()
Returns a boolean value indicating whether caps lock was on when this event was fired.
local capslock = event:capslock()
Returns a boolean value indicating whether numlock was on when this event was fired.
local numlock = event:numlock()
Returns three boolean values indicating whether each primary mouse button was held when this event fired, in the order: left, right, middle.
local lmb, rmb, mmb = event:mousebuttons()
Browser objects and embedded browser objects have access to a few APIs
which are primarily used for communicating with the Lua code running in
the game process. Incoming messages will be via the JavaScript
window.postMessage function, so can be handled like so:
window.addEventListener('message', async (event) => { if (typeof(event.data) !== "object") return; // check event.data.type... });
APIs invoked by the web page itself are accessed by making a web request
to https://bolt-api/, as shown in the examples for the relevant
API endpoints.
Browsers and embedded browsers have no window.close() function,
as one would usually find in JavaScript in a web page. This function
doesn’t work correctly in CEF so it has been disabled. Pages that want
to self-close must use the /close-request API instead.
A message has been received from the Lua sendmessage function being
called on this Browser or Embedded Browser object.
event.data.content is an ArrayBuffer containing the binary data
that was passed to the function. This type of message will never arrive
before the initial ’DOMContentLoaded’ event for the page, as Bolt will
queue them until after the page has completely loaded.
window.addEventListener('message', async (event) => { if (typeof(event.data) !== "object") return; if (event.data.type === "pluginMessage") { const message = (new TextDecoder()).decode(event.data.content); console.log(message); } });
Note that this example assumes that the message is safely formattable text. This may or may not be the case, depending on what your plugin is programmed to send.
Screen capture is currently enabled for this browser, and a new screen
capture has been received. event.data.width and
event.data.height are the width and height of the image in
pixels, and event.data.content is an ArrayBuffer of length
width * height * 3, containing the RGB pixel data.
window.addEventListener('message', async (event) => { if (typeof(event.data) !== "object") return; if (event.data.type === "screenCapture") { console.log(`got screen capture ${event.data.width}x${event.data.height}`); // do something with event.data.content... } });
A POST to this URL will result in an onmessage event being fired for the relevant browser object or embedded browser object in Lua. The POST body will be sent as-is, byte-for-byte, without being decoded or re-encoded in any way.
fetch("https://bolt-api/send-message", {method: 'POST', body: 'some message contents'});
A request to this URL will result in an oncloserequest event being fired for the browser object in Lua, as if the user had clicked the ’X’ button on the window. This doesn’t actually close the window; if the Lua object has no event handler for close requests then nothing will happen.
fetch("https://bolt-api/close-request");
See startreposition. The query should contain two params, "h" and "v", for the horizontal and vertical components, respectively. This should usually be done in response to a JavaScript onmousedown event for the left mouse button. This has no effect on Browser objects, which are not embedded into the game window.
The following example would cause the window to be dragged by its top-right edge:
fetch("https://bolt-api/start-reposition?h=1&v=-1");
See cancelreposition. This has no effect on Browser objects, which are not embedded into the game window.
fetch("https://bolt-api/cancel-reposition");
Opens a chromium devtools window for this browser. If one is already open, it will be focused instead of opening a new one. The devtools window can’t be closed through code, it can only be closed by a user action (e.g. clicking the ’X’) or by the browser itself closing.
The custom shader API allows rendering to surfaces using custom vertex and fragment shaders and custom vertex data. It’s very powerful, but requires some prior understanding of shader programming to be able to use it.
Custom shaders must be written in GLSL. Here’s how to compile a vertex shader object and fragment shader object using GLSL code:
local vs = bolt.createvertexshader( "layout(location=0) in highp vec2 pos;".. "out highp vec2 vpos;".. "void main() {".. "vpos = pos;".. "gl_Position = vec4(pos, 0.0, 1.0);".. "}" ) local fs = bolt.createfragmentshader( "in highp vec2 vpos;".. "out highp vec4 col;".. "void main() {".. "col = vec4((vpos + 1.0) / 2.0, 1.0, 1.0);".. "}" )
Of course, these strings may come from anywhere, you don’t necessarily
have to write them inline if you don’t want to. Note the absence of a
#version header in these shaders. The Bolt API automatically
prepends #version 330 core to your code, so you mustn’t include
a version header yourself. Lastly be aware that the plugin will be
terminated by a call to error() if shader compilation fails, so
you’ll need to read stdout to find out the reason.
Next, create a shader program by linking your shaders:
local program = bolt.createshaderprogram(vs, fs)
Again, this function will call error() if linking fails, such as
if you provided two vertex shaders or two fragment shaders.
Since we no longer need the shader objects, we should delete them to save memory. As with all userdata objects, custom deletion logic (in this case, glDeleteShader) is handled by Lua’s garbage collector.
vs = nil fs = nil
Next, we need to set up our program’s attributes by calling
setattribute on it. This function permanently
associates values with the attribute in question, so it only needs to be
called once, usually right after creation. The vertex shader defined
above has only one input attribute at location=0, so we’ll
activate attribute 0 and leave the others inactive.
program:setattribute(0, 1, true, false, 2, 0, 2)
There are a lot of parameters here, so let’s take them one at a time:
layout(location=0).
By the way, there’s no way to query attribute locations by name, so
using layout(location=...) in your GLSL code is required to be
able to pass data in. The same goes for uniform variables. The behaviour
of attributes or uniforms with overlapping memory regions is undefined
in Bolt, so don’t do it.
You’ll notice we defined the format of our data, but we don’t actually have any data yet. The format of each attribute is defined in the shader program, but the data itself doesn’t need to exist yet - it’s passed in when calling a draw function. Here we’ll create a shader buffer that contains six signed 1-byte integers - that’s three vertices with one vec2 per vertex.
local buf = bolt.createbuffer(6) -- vec2 of the first vertex buf:setint8(0, -1) buf:setint8(1, -1) -- vec2 of the second vertex buf:setint8(2, 1) buf:setint8(3, -1) --vec2 of the third vertex buf:setint8(4, 1) buf:setint8(5, 1) local myshaderbuffer = bolt.createshaderbuffer(buf)
Once again, keep in mind that earlier we told our program to expect vertex data with a 1-byte signed integer per value, two values per vertex, the first set being at byte 0, and increasing by two bytes for each vertex. Now we’ve formatted our input data in a way that matches that description.
The points (-1, -1), (1, -1), and (1, 1) make a right-angled triangle with the right angle at the bottom-right, so it’ll be easy to see if it’s worked correctly.
The createshaderbuffer function creates a shader buffer by uploading the given data to the GPU, after which it can’t be read or written anymore. Here we used a Buffer to write the data before sending it. Don’t be misled here: a buffer object is not the same thing as a shader buffer object. See Shader Buffer for an explanation.
Now we are ready to run our shader. Let’s create a surface and draw to it using our program and buffer.
local mysurface = bolt.createsurface(100, 100) program:drawtosurface(mysurface, myshaderbuffer, 3)
Finally, to be able to see our surface, let’s draw it to the screen in a swapbuffers callback.
bolt.onswapbuffers(function (event) mysurface:drawtoscreen(0, 0, 100, 100, 100, 100, 200, 200) end)
If you did everything right, here’s what you’ll see on your screen:
That’s almost right. But didn’t we say that the right-angle should be at
the bottom-right, not the top-right? Why is our triangle upside-down?
Well, this is because we drew to a surface, and the surface is
correcting this for us. GPUs use -1.0 to refer to the bottom of the
screen and +1.0 to refer to the top, but the Bolt API wants to work the
other way round, with 0 being the top and screen_height being the
bottom. drawtoscreen is correcting for this by drawing the image
upside-down when we specify y=100 and height=200. This is a notable
"gotcha" to be aware of when working with custom shaders.
Some of the Render3D and Minimap functions refer to "world coordinates". These refer to an actual position in the world, at a scale of 512 units per tile.
Coordinate (X=0, Z=0) is the southwest corner of a tile. A model that’s
at the center of a tile, such as an idle player or NPC, will have X and
Z coordinates ending in .5.