Script Extender Lua Setup - LaughingLeader/BG3ModdingTools GitHub Wiki

Table of Contents

Prerequisites

This tutorial assumes you already have a mod project created / working in-game.

I recommend setting up the mod as loose folders in the data folder (Data\Mods\ModFolder, Data\Public\ModFolder etc), so when you change scripts and reload them via the reset command, you can see these changes immediately, rather than having to create another pak and restart the game.

Script Extender Setup

Lua scripting requires the Script Extender, a project that makes Osiris/lua scripting possible in Baldur's Gate 3. Out of the box it also enables achievements, and provides a console window for running scripting functions, among many other features.

Updater

Grab the latest Script Extender Updater if you haven't already. Put DWrite.dll in your Baldur's Gate 3\bin folder. Launch the game once so the updater will download the latest extender release.

Config.json

This file tells the script extender which features to enable for your mod project. You can either create the file manually, or grab the sample.

The file should be located here:

Mods\ModFolder\ScriptExtender\Config.json

What we want to enable lua scripting are the following settings:

{
	"RequiredVersion": 20,
	"ModTable": "REPLACE_ME_WITH_YOUR_MOD_ID",
	"FeatureFlags": ["Lua"]
}

Replace the ModTable value with a unique name for your mod. This will be the global table key the extender stores all of your mod's global variables (your mod's 'environment'), so it's very important to set this to a unique name.

With this file set, the extender will enable lua scripting for your mod, and look for the related bootstrap scripts to load.

Property Description
RequiredVersion This is the required extender version, and is used to enable some additional features, so generally this is always the latest.
ModTable The table key name used for your mod's scripting environment. Must be unique, and can be accessed in scripting within the global Mods table (Mods.LeaderLib for instance).
FeatureFlags An array of specific flags to enable for the mod.

Feature Flags

ID Description
Lua Simply enables Lua scripting. Must have a ModTable set.

ScriptExtenderSettings.json

ScriptExtenderSettings.json is a config file from the user side of the script extender. We want to set this up to enable the console window and developer mode.

Create a new text file and rename it to ScriptExtenderSettings.json in the game's bin folder (the same folder you placed DWrite.dll).

Place the following text inside:

{
  "CreateConsole": true,
  "DeveloperMode": true,
  "EnableLuaDebugger": true,
  "EnableLogging": true,
  "LogCompile": true,
  "LogFailedCompile": true,
  "LogRuntime": true
}

These are some basic settings that will allow better development with the extender. Logging is enabled so we have files we can check when debugging issues or looking at the various rules that can happen.

Property Description
CreateConsole Enables the script extender console window, which will output console text and allow running lua code directly in the console.
DeveloperMode Can be checked in scripts to enable developer features (Ext.IsDeveloperMode()).
EnableLuaDebugger Allows hooking the lua debugger to the game with vscode.
EnableLogging Enables logging the various aspects of the game. Keep in mind logs can get fairly big, so keep an eye on your logs folder. Default directory is %LOCALAPPDATA%\Larian Studios\Baldur's Gate 3\Extender Logs
LogCompile Enables logging for the Osiris compiler when it's merging story scripts. Useful to see issues that cause working story errors.
LogFailedCompile Enables logging for the Osiris compiler when compiling fails (may be redundant if LogCompile is enabled).
LogRuntime Enables logging for the console window / lua scripting.
LogDirectory For changing the directory logs are stored in. Make sure to escape \ in json (\\).

Visual Studio Code Setup

Visual Studio Code is the preferred editor for Divinity scripting. Install the following:

Workspace Setup

Create a new text file somewhere appropriate (wherever you want to store your workspace files outside of your mod folders, such as C:\Modding\BG3\Workspaces), then rename it to MyModName.code-workspace, replacing MyModName with whatever your mod's name is.

Edit it with your preferred text editor (Notepad, Notepad++, etc. VSCode may try and open it as a workspace) and enter the following:

{
	"folders": [
		{
			"path": "C:\\Games\\Steam\\steamapps\\common\\Baldur's Gate 3\\Data\\Mods\\ModFolder",
			"name": "Mods"
		},
		{
			"path": "C:\\Games\\Steam\\steamapps\\common\\Baldur's Gate 3\\Data\\Public\\ModFolder",
			"name": "Public"
		},
		{
			"path": "C:\\Modding\\BG3\\ReferenceScripts",
			"name": "ReferenceScripts"
		}
	],
	"settings": {
		"search.exclude": {
			"**/*.raw": true,
			"**/Story/*.div": true,
			"**/*.osi": true,
			"**/*.dat": true
		},
		"Lua.workspace.library": [
			"C:\\Modding\\BG3\\ReferenceScripts"
		],
		"Lua.diagnostics.libraryFiles": "Disable",
		"Lua.diagnostics.workspaceRate": 25,
		"Lua.diagnostics.workspaceDelay": 5000,
		"Lua.workspace.preloadFileSize": 10000,
		"Lua.diagnostics.globals": [
			"Osi",
			"Ext"
		]
	}
}

This is a basic workspace file that includes two folders - Your mod's Mods\ and Public\ folders. Be sure to replace the path values with the actual path to your mod's folders, replacing \ with \\.

The last folder, "ReferenceScripts", should be a folder you create external to your actual mod. We'll use that folder to store lua reference scripts, such as Osi.lua. Including this folder in the workspace is mainly to make checking references easier, as this isn't required to make auto-completion work.

The ReferenceScripts folder must be included in the Lua.workspace.library setting, so the global variables in the Osi automatically show up in every lua environment. Without this setting, you will get warnings of an "undefined global" when trying to use the Ext table, and you won't have auto-completion.

Lua.workspace.library

Each individual folder in a workspace is treated as its own isolated lua environment. The Lua.workspace.library setting is what allows Lua globals, from the included folders, to show up these isolated environments. Therefore, if you want a mod or the extender's globals to show up when working on a mod, folders need to be added to Lua.workspace.library, as adding a folder to the workspace itself won't work for this purpose.

Other Settings

The search.exclude setting lets us ignore the various osi compilation files from searching, so if you look for an Osiris function, it should either show your code or story_header.div.

The Lua.diagnostics.workspaceRate and Lua.diagnostics.workspaceDelay settings reduce the extension's CPU usage, for better overall performance, though this can be tweaked or removed as desired.

Lua.workspace.preloadFileSize is needed if your references folder includes the various definitions for Osiris functions as well (all usable in lua).

Lua.diagnostics.globals is an array of variables to consider as global, even without definition from a script. Including "Osi" here allows you to use it without being warned of an "Undefined global", even if you choose to not include the annotations for the Osi table.

Scripting Setup

Finally with our workspace and the script extender all set up, we can begin to set up actual lua scripting.

References

First grab copies of the various extender scripts here, and place them in a folder external to your mod (such as C\\Modding\BG3\\ExtenderScripts). We don't want to include these in our mod, just use them in vscode for referencing and auto-completion.

  • Osi.lua
    This is a generated lua file that provides annotations for all the Osiris functions (Osi.ApplyStatus etc).
  • ExtIdeHelpers.lua This is a generated lua file for the Script Extender itself, that contains the various functions in Ext, and all the related types.

Bootstrap Scripts

To finally begin lua scripting, we need to create the two script files the extender looks for. These are our entry points into lua scripting, and from there we can load other scripts.

Create the Lua script folder:

Mods\ModFolder\ScriptExtender\Lua\

Then create two text files, and rename them to BootstrapServer.lua and BootstrapClient.lua:

Mods\ModFolder\ScriptExtender\Lua\BootstrapServer.lua
Mods\ModFolder\ScriptExtender\Lua\BootstrapClient.lua

The extender will attempt to load BootstrapServer.lua on the server side, and BootstrapClient.lua on the client side. In a multiplayer context, the host is both the server and a client, and all players connecting are clients. In singleplayer, both the server and client sides co-exist.

Required Scripts

Name State
BootstrapServer.lua Server Side
BootstrapClient.lua Client Side

From here, these scripts can load other scripts with Ext.Require. The path to scripts are relative to the Lua folder, so if you had a file setup like this:

Mods\ModFolder\ScriptExtender\Lua\BootstrapClient.lua
Mods\ModFolder\ScriptExtender\Lua\BootstrapServer.lua

Mods\ModFolder\ScriptExtender\Lua\Server\SkillMechanics.lua

BootstrapServer would load SkillMechanics.lua with Ext.Require("Server/SkillMechanics.lua"). Script loading only needs to happen once.

Tips

Resetting Scripts

  1. Go to the extender console window, and hit enter to enable text input.
  2. Next, type silence off and hit enter to send the command. This will allow print messages to appear in the window while in input mode.
  3. The extender command, reset, and then hit the enter key.

All lua scripts will be reloaded again.

Note that your mod will need to be loosely in the Data folder for script changes to be reloaded, since pak data itself isn't reloaded.