Baldur's Gate 3 Script Extender

repository·main·Indexed 21 days ago

https://github.com/norbyte/bg3se

A tool by Norbyte that injects Lua and Osiris scripting support into Baldur's Gate 3 to enable advanced modding. It provides a SE Console for executing Lua code, separate Lua states for server and client contexts, and an API for interacting with engine objects, events, enumerations, and bitfields.

Tokens
16.6K
Snippets
31
Records
84
Agent score
75%

What's inside bg3se

  1. Work with Enumerations

    main

    Enum values are returned as userdata (lightcppobject) rather than strings. They provide metadata about the enumeration they belong to.

    Properties:

    • Label: The textual name (e.g., "Blood").
    • Value: The numeric value (e.g., 16).
    • EnumName: The name of the enumeration (e.g., "SurfaceType").

    Usage Patterns:

    • Comparison: You can compare an enum value against a string label, a numeric value, or another enum object.
    • Assignment: You can assign values using a string label, a numeric value, or an enum object.
    • Compatibility: tostring() and JSON serialization convert enums to their string labels. Using an enum as a table key converts it to a string.
    local bt = _C().CurrentTemplate.BloodSurfaceType
    
    -- Querying metadata
    _D(bt.Label)    -- "Blood"
    _D(bt.Value)    -- 16
    _D(bt.EnumName) -- "SurfaceType"
    
    -- Comparisons
    _D(bt == "Blood") -- true
    _D(bt == 16)       -- true
    _D(bt == Ext.Enums.SurfaceType.Blood) -- true
    
    -- Assignment
    _C().CurrentTemplate.BloodSurfaceType = "Blood"
    _C().CurrentTemplate.BloodSurfaceType = 16
    _C().CurrentTemplate.BloodSurfaceType = Ext.Enums.SurfaceType.Blood
  2. Understand Client and Server Lua states

    main

    The Script Extender maintains separate Lua states for the Server and each Client (including the single-player 'fake client'). These states are isolated; they cannot access each other's globals, functions, or variables.

    • Server (S): Osiris and behavior scripts (gamescripts) always run here. Many Osiris-related functions are only available in the server context.
    • Client (C): Used for features requiring client-side code, such as UI modification, level scaling formulas, status chances, and skill damage calculations.
    • Restricted (R): Functions that can only be called in specific, designated contexts.

    When developing, ensure you are calling functions in the correct state (e.g., UI logic on the client, Osiris logic on the server).

  3. How to wrap and extend an existing UI DataContext

    main

    To intercept or extend existing game UI functionality (like the Main Menu), you can create a wrapper type that targets the existing DataContext name.

    1. Register a wrapper type: Use Ext.UI.RegisterType and provide the name of the existing in-game DataContext as the third argument (wrappedTypeName).
    2. Instantiate with the original context: Use Ext.UI.Instantiate passing the original DataContext as the second argument.
    3. Set Handlers: Attach Lua functions to commands within the new wrapper.
    4. Replace the DataContext: Assign the new wrapper instance back to the widget's DataContext property.
    -- 1. Register a wrapper type for the main menu DataContext
    Ext.UI.RegisterType("SAMPLE_MainMenuCtx", {
        StartGameCommand = {Type = "Command"} -- builtin command to start game
    }, "gui::DCMainMenu") -- gui::DCMainMenu is the name of the ingame main menu DataContext
    
    -- (Note: Getting the widget via GetRoot is for demonstration only)
    local mainMenu = Ext.UI.GetRoot():Find("ContentRoot"):VisualChild(1)
    
    -- 2. Create a wrapper around the original main menu DataContext
    local ctx = Ext.UI.Instantiate("se::SAMPLE_MainMenuCtx", mainMenu.DataContext)
    
    -- 3. Set a handler for the command
    ctx.StartGameCommand:SetHandler(function ()
        print("do stuff")
    end)
    
    -- 4. Overwrite datacontext with our wrapper
    mainMenu.DataContext = ctx
  4. Understand the Ext.Entity class

    main

    In Baldur's Gate 3, game objects (characters, items, triggers, etc.) are called entities. The Lua Ext.Entity class is the representation of these in-game objects.

    Entities are built using an Entity-Component-System (ECS) architecture, where an entity consists of multiple components that describe its specific properties and behaviors.

    To interact with an entity, you typically retrieve it using Ext.Entity.Get(handle) and then access its components to read or modify its state.

  5. Virtual Texture Glossary

    main

    Understanding the core components of the Virtual Texture system:

    • Tile Set: A large texture containing multiple sub-textures merged into one.
    • .GTS file (Graphine Tile Set): A file containing a description of the tile set and the layout/position of all tiles and page files.
    • .GTP file (Graphine Tile Page): A file containing the textures subdivided into equally sized chunks (tiles).
    • GTex: A specific slice of the tile set containing only one specific source texture.
  6. Understand Script Extender updates and version compatibility

    main

    Auto-Updates

    The Script Extender auto-updates. When a game update occurs, the game will automatically download and apply the necessary Script Extender update upon launching. A new release on GitHub typically indicates a change to the updater itself, not necessarily a new version of the Script Extender.

    Version Compatibility

    • Minimum Version: The extender does not support game versions older than patch 5.
    • Error "No extender version found for game version v4.xx.xx.xx.": This occurs if you are running a game version that is too old for the current extender releases. New extender versions are primarily released for the latest game version.
  7. Apply and manage materials (v30+)

    main

    Since version 30, the Script Extender supports advanced material manipulation:

    • Applying base and overlay materials.
    • Reading current instance parameter values.
    • Setting parameters on queued materials.
    • Setting parameters for virtual texture and texture2D materials.
  8. Understand Object Scopes and Lifetimes

    main

    In BG3SE, most userdata types (game objects like Character, Status, etc.) are bound to their enclosing extender scope. Because the engine deletes game objects at the end of the game loop, objects are only guaranteed to be valid during the current Lua call.

    Crucial Rule: You cannot "smuggle" objects outside of listeners or into asynchronous callbacks like Ext.OnNextTick. If you store a reference to an object in a variable and try to access it in a later tick, the extender will throw an error because the object's lifetime has expired.

    Subproperties inherit the lifetime of their parent. For example, a reference to a specific spell in a SpellBook will expire when the character owning that book is destroyed.

    -- INCORRECT: This will crash or throw an error
    local spellbook = Ext.Entity.Get(...).SpellBook
    Ext.OnNextTick(function (...)
        -- Throws "Attempted to read object of type 'SpellBookEntry' whose lifetime has expired"
        local uuid = spellbook.Spells[2].SpellUUID
    end)
  9. Use the NetChannel API for server/client communication

    main

    The NetChannel API is the recommended way for mods to exchange data between the server and client(s). It provides structured request/response semantics and message broadcasting.

    Key Concepts:

    • Channel: A named string identifier for a communication stream.
    • Request / Reply: An asynchronous pattern where you send a request and provide a callback to handle the response.
    • Message: A one-way, fire-and-forget transmission.
    • Handlers: Functions registered to a specific channel that execute when a message or request arrives.

    Note: The Script Extender does not support external networking; communication is strictly limited to the local server and connected clients.

    ---@param callback fun(data:table, user:any)
    function NetChannel:SetHandler(callback) end
    
    ---@param callback fun(data:table, user:any):table
    function NetChannel:SetRequestHandler(callback) end
    
    ---@param data table
    ---@param replyCallback fun(data:table)
    function NetChannel:RequestToServer(data, replyCallback) end
    
    ---@param data table
    ---@param user integer|Guid
    ---@param replyCallback fun(data:table)
    function NetChannel:RequestToClient(data, user, replyCallback) end
  10. Use Genome for event queuing and diverse value types (v30+)

    main

    Version 30 expanded Genome support to include:

    • Queuing Genome events.
    • Writing Genome variants.
    • Support for various Genome value types: Bool, Float, FloatSet, IntSet, ShortNameSet, StringSet, and FixedStringSet.
  11. Interact with Engine Objects (Properties, Iteration, and Equality)

    main

    Engine objects in BG3SE behave like Lua tables with specific metamethod support:

    • Properties: Accessing non-existent properties on an object class will throw a Lua error.
    • Iteration: You can use pairs() to iterate over all properties and methods of an engine object.
    • Stringification: tostring(obj) returns the class name and instance ID (e.g., "SpellBookEntry (00000209C32D16F0)").
    • Equality: The == operator checks if two references point to the exact same engine object.
    • Arrays: Array-like objects support # for length and ipairs() for iteration.
    -- Iterating properties
    local spell = Ext.Entity.Get(...).SpellBook.Spells[1]
    for property, value in pairs(spell) do
        _P(property, value)
    end
    
    -- Checking equality
    _P(Ext.Entity.Get(GetHostCharacter()) == Ext.Entity.Get(GetHostCharacter()))
    
    -- Array-like iteration
    local tags = _C().Tag.Tags
    for i, tag in ipairs(tags) do
        _P(i, tag)
    end
  12. Work with Bitfields

    main

    Bitfields are returned as userdata (lightcppobject). They represent a collection of flags.

    Properties:

    • __Labels: A table containing all textual names of the flags.
    • __Value: The numeric value representing the combined flags.
    • __EnumName: The name of the enumeration.

    Operations:

    • Querying: You can check if a specific flag is set by accessing it as a property: af.DrunkImmunity returns true or false.
    • Bitwise Operators: Supports ~ (NOT), | (OR), & (AND), and ~ (XOR). Operands can be bitfields, string labels, tables of labels, or numbers.
    • Comparison: Supports comparison against other bitfields, tables of labels, or numeric values.
    • Assignment: Supports assignment via labels, tables of labels, enum objects, or numeric values.
    • Iteration: Supports pairs() and ipairs() to iterate over the active flags.

    Note: Bitfields are passed by value. You cannot use table.insert or direct assignment to change a single flag (e.g., af.Flag = false will fail). Use bitwise operators to create a new bitfield value instead.