Skip to content

Latest commit

 

History

History
2359 lines (1809 loc) · 91.3 KB

API.md

File metadata and controls

2359 lines (1809 loc) · 91.3 KB

BG3SE Lua API v1 Documentation

Table of Contents

Getting Started

To start using the Script Extender in your mod, a configuration file must be created that describes what features are utilized by your mod.

Create a file at Mods\YourMod_11111111-2222-...\ScriptExtender\Config.json with the following contents, then tweak the values as desired:

{
    "RequiredVersion": 1,
    "ModTable": "YOUR_MOD_NAME_HERE",
    "FeatureFlags": [
        "Osiris",
        "Lua"
    ]
}

Meaning of configuration keys:

Key Meaning
RequiredVersion Osiris Extender version required to run the mod. It is recommended to use the version number of the Script Extender you used for developing the mod since the behavior of new features and backwards compatibility functions depends on this version number.
ModTable Name of the mod in the global mod table (Mods) when using Lua.
FeatureFlags A list of features that the mod is using. For performance reasons it is recommended to only list features that are actually in use.

The following features are accepted in FeatureFlags:

Value Meaning
Lua Enables Lua scripting
Osiris Enables Osiris (Story) extension functions (see Osiris Functions)
Preprocessor Enables the use of preprocessor definitions in Story scripts. (See Osiris Preprocessor)

Early Access Notes

The Script Extender itself is Early Access! Many of the features are still in development and are either unavailable or not completely functional. The APIs used to access may change due to redesign or due to changes in the game itself.

Story Scripting: Osiris scripting is currently not functional in BG3 because the original story scripts are not available, so it is not possible to compile story code. The Script Extender allows calling Osiris functions without the need to write story scripts, however mods cannot write story code. The game will most likely include raw story files when the full game is released and story scripting will become possible.

Behavior Scripting: Behavior scripts seem to be deprecated in BG3 in favor of Lua scripting; therefore no support will be provided for interacting with behavior scripts.

Editing object properties note!

Differences between the DOS2 and BG3 Extenders

WIP

TODO add bg3 general differences:

  • ECS and raw component data exposed
  • Component editing rules / replication triggers
  • Userdata get/set/iteration support and considerations
  • Lifetime/Scope binding for by-ptr userdata; entity/comp handle persistence
  • ...

Client and Server Roles

The game is split into two components, a client and a server. When a game is started/loaded, the in-game server is started and the internal client connects to this server. In multiplayer games server component is only present on the host; client components are created on both the host and all players connecting to the host. Because of this, the game technically always runs in multiplayer. Single player is just a special form of multiplayer where only one local player is connected to the server.

Osiris, behavior scripts (gamescripts/itemscripts/characterscripts) and Anubis Lua scripts always run on the server. Since the Script Extender Lua API has access to features that require client-side code ([[TODO not up to date]] UI modification, level scaling formulas, status chances, skill damage calculation, etc.) the extender keeps multiple Lua states: one state for the server and one for each client (including the single player "fake client"). These states are completely independent from each other and cannot access the internal state (functions, variables, etc.) of each other.

Because they run in different environments, server and client states have access to a different set of features. Functions/classes in this document are annotated with the following letters, which indicate where they are available:

  • C - The function is only available on the client
  • S - The function is only available on the server
  • R - Restricted; the function is only callable in special contexts/locations

Persistent Variables

The Lua state and all local variables are reset after each game reload. To carry over data to subsequent play sessions it is possible to store them in the savegame by saving them in the mod-local table Mods[ModTable].PersistentVars. (By default this table is uninitialized, i.e. nil.) It is the responsibility of the mod to create the table and populate it with data it wishes to store in the savegame. The contents of PersistentVars is saved when a savegame is created, and restored before the SessionLoaded event is triggered.

The serialization restrictions for PersistentVars are equivalent to the restrictions for Ext.Json.Stringify (i.e. no userdata/function support, no recursive structures, no custom metatables, etc). If PersistentVars cannot be serialized, it will not be written to the savegame!

Example:

-- NOTE: The PersistentVars table is not declared as "local". 
-- This is because the global table (_G) of a mod is the "Mods[ModTable]" table.
PersistentVars = {}
[...]
-- Variable will be restored after the savegame finished loading
function doStuff()
    PersistentVars['Test'] = 'Something to keep'
end

local function OnSessionLoaded()
    -- Persistent variables are available only after SessionLoaded is triggered!
    Ext.Utils.Print(PersistentVars['Test'])
end

Ext.RegisterListener("SessionLoaded", OnSessionLoaded)

Console

The Extender allows commands to be entered to the console window.

Press <enter> to enter input mode; in this mode the normal log output is disabled to avoid log spam while typing commands.

Client/server Lua contexts can be selected by typing client or server. This selects which Lua environment the console commands will execute in. By default the console uses the server context. The reset command reinitializes the server and client Lua VM; reset client only reinitializes the client VM, while reset server only reinitializes the server VM.

Typing exit returns to log mode.

Commands prefixed by a ! will trigger callbacks registered via the RegisterConsoleCommand function. Example:

local function testCmd(cmd, a1, a2, ...)
    Ext.Utils.Print("Cmd: " .. cmd .. ", args: ", a1, ", ", a2);
end
Ext.RegisterConsoleCommand("test", testCmd);

The command !test 123 456 will call testCmd("test", 123, 456) and prints Cmd: test, args: 123, 456.

Anything else typed in the console will be executed as Lua code in the current context. (eg. typing Ext.Utils.Print(1234) will print 123). The console has full access to the underlying Lua state, i.e. server console commands can also call builtin/custom Osiris functions, so Osiris calls like CharacterGiveEquipmentSet(GetHostCharacter(), "EQP_CC_Cleric") are possible using the console. Console commands are executed in the global table, so mod-specific variables must be accessed through Mods[ModTable].whatever, where ModTable is the value of the config variable specified in ScriptExtender\Config.json. Variables can be used just like in Lua, i.e. variable in one command can later on be used in another console command. Be careful, console code runs in global context, so make sure console variable names don't conflict with globals (i.e. Mods, Ext, etc.)! Don't use local for console variables, since the lifetime of the local will be one console command. (Each console command is a separate chunk in Lua terms).

Object Identifiers

Game objects have multiple identifiers that are used for different purposes.

GUID

The UUID or GUID (Globally Unique IDentifier) is a unique textual identifier (e.g. 123e4567-e89b-12d3-a456-426614174000). It can reference any character or item.

Use GUIDs to hardcode references to pre-made characters/items in scripts or to reference newly created characters/items on the server.

Usage Notes:

  • For objects that are created in the editor, the GUID is guaranteed to stay the same in all playthroughs, i.e. it is safe to hardcode the GUID in scripts. This is the identifier Osiris functions use (using the types GUIDSTRING, ITEM, etc.)
  • Objects that are created during the game are assigned a randomly generated GUID. This GUID will not change during the lifetime of the object, i.e. it's safe to store it to reference the object later on.
  • Some object types (eg. projectiles, statuses, etc.) have no GUID, so they must be referenced using their object handle or NetID. Usually if an object is global (i.e. can appear anywhere in any context), it has a GUID. If it is local (i.e. it can only be assigned to a specific character etc.) it won't have a GUID.
  • GUIDs should not be used to reference objects on the client side (it is safe to use GUIDs on the server), as there are bugs on the client that cause the GUID to not replicate properly or it may be missing entirely. Use object handles (in client-only code) or NetID-s (when communicating with the server) to reference client objects instead. [[TODO check if this is still bugged in BG3]]

Object/Entity Handle

Object handles are used by the game engine internally; they're 64-bit numbers. They're used for performance reasons, i.e. it's significantly faster for the engine to find objects using a handle than by a GUID (it's also smaller). Most references in the savegame are also handles, not GUIDs. eg. the parent inventory, items stored in a container, etc. are all saved using their handle, not their GUID.

Since everything has an object handle, you can use it to reference any object on both the server and the client.

Usage Notes:

  • This is a very commonly used identifier in Lua; most Lua game objects refer to others using object handles.
  • Each object is assigned a new handle when it is created. Unlike GUIDs, handles for objects that are created in the editor will not be the same in different playthroughs, so handles cannot be hardcoded in scripts.
  • Unlike GUIDs, the client and server use different handles to reference the same object. (Technically, client and server characters are different objects altogether.)
  • After an object was created, the server handle will not change during the lifetime of the object, i.e. it's safe to store it to reference the object later on. If a savegame is reloaded, the server handle will stay the same. [[TODO note about handle -> GUID translation in BG3]]
  • Client handles can change between play sessions (i.e. after a savegame reload), but they'll remain the same during the play session. They can safely be kept in temporary structures (eg. Lua variables that get reset after a savegame load), but should not be persisted in Osiris databases or via PersistentVariables.

NetID

The NetID (Network Identifier) is a number the game uses in client-server communication to reference objects. Since object handles differ on the server and the client and not every object type has a GUID, NetID is the only identifier that can be reliably used to identify objects on both sides. [[TODO check if client GUID is fixed in BG3]]

Usage:

  • Each object is assigned a new NetID at the start of every play session. If a savegame is reloaded, the NetID may change.
  • Unlike object handles, both the server and the client use the same NetID to reference the same object.
  • Since they're only valid for the duration of the session, they can safely be kept in temporary structures (eg. Lua variables that get reset after a savegame load), but should not be persisted in Osiris databases or via PersistentVariables.

Identifier Matrix

This table describes which identifiers are present/can be used for which object.

Object GUID Object Handle NetID
Server Character
Server Item
Server Projectile
Server Status
Server Surface
Server Surface Action
Server Game Action
Client Character *
Client Item *
Client Projectile
Client Status

* Although client characters/items have a GUID, it cannot be used reliably.

Calling Osiris from Lua S

Lua server contexts have a special global table called Osi that contains every Osiris function, event and database. In addition, built-in functions (calls, queries, events), functions added by the Osiris extender and functions registered from Lua via Ext.Osiris.NewCall, Ext.Osiris.NewQuery and Ext.Osiris.NewEvent are also added to the global table.

Types

The following table describes how Lua values are converted to Osiris values and vice versa.

Lua Type Osiris Type Notes
nil - nil is not convertible to an Osiris value, however it has a special meaning when calling/returning from queries (see Queries).
boolean INTEGER Osiris has no true boolean type, it uses the integer value 1 to represent true and 0 to represent false.
number (integer) INTEGER Although Lua only has one number type, its internal representation can vary depending on whether it stores an integer or floating point number.
number (float) REAL
string STRING Any STRING alias (eg. GUIDSTRING, CHARACTER, ITEM, ...) is also convertible to string.
table - Osiris only supports scalar values, so tables cannot be passed to/from Osiris functions.

Calls

Simply call the method from Lua with the same parameters:

-- Built-in functions are in the global table (_G)
CharacterResetCooldowns(player)
-- Equivalent to the above
Osi.CharacterResetCooldowns(player)

Implementation detail: Name resolution is only performed when the function is called since Osiris allows multiple overloads of the same name and the function is resolved based on the number of arguments. Because of this, getting any key from the Osi table will return a proxy object even if no function with that name exists. Therefore, Osi.Something ~= nil and similar checks cannot be used to determine whether a given Osiris symbol exists.

Queries

The query behavior is a mirror of the one described in the Custom calls/queries chapter.

For queries with zero OUT arguments, the function will return a boolean indicating whether the query succeeded (true) or failed (false).

local succeeded = SysIsCompleted("TestGoal")

Queries with OUT arguments will have a number of return values corresponding to the number of OUT arguments.

-- Single return value
local player = CharacterGetHostCharacter()
-- Multiple return values
local x, y, z = GetPosition(player)

Events

Osiris events can be triggered by calling them like a function. Events are not buffered and the event is triggered synchronously, i.e. the function call returns when every Osiris rule that handles the event has finished.

StoryEvent(player, "event name")

PROCs

Calling PROCs is equivalent to built-in calls, however they are not added to the global table.

Osi.Proc_CharacterFullRestore(player)

User Queries

User queries (QRY) behave just like built-in queries do. Since they can't have OUT arguments (i.e. can't return values), the function will just return a boolean indicating whether the query succeeded or not. User queries are not added to the global table.

local succeeded = Osi.Qry_IsHealingStatus("DAMAGE")

Databases

Databases can be read using the Get method. The method checks its parameters against the database and only returns rows that match the query.

The number of parameters passed to Get must be equivalent to the number of columns in the target database. Each parameter defines an (optional) filter on the corresponding column; if the parameter is nil, the column is not filtered (equivalent to passing _ in Osiris). If the parameter is not nil, only rows with matching values will be returned.

Example:

-- Fetch all rows from DB_GiveTemplateFromNpcToPlayerDialogEvent
local rows = Osi.DB_GiveTemplateFromNpcToPlayerDialogEvent:Get(nil, nil, nil)

-- Fetch rows where the first column is CON_Drink_Cup_A_Tea_080d0e93-12e0-481f-9a71-f0e84ac4d5a9
local rows = Osi.DB_GiveTemplateFromNpcToPlayerDialogEvent:Get("CON_Drink_Cup_A_Tea_080d0e93-12e0-481f-9a71-f0e84ac4d5a9", nil, nil)

It is possible to insert new tuples to Osiris databases by calling the DB like a function.

Osi.DB_CharacterAllCrimesDisabled(player)

The Delete method can be used to delete rows from databases. The number of parameters passed to Delete must be equivalent to the number of columns in the target database. Each parameter defines an (optional) filter on the corresponding column; if the parameter is nil, the column is not filtered (equivalent to passing _ in Osiris). If the parameter is not nil, only rows with matching values will be deleted. Example:

-- Delete all rows from DB_GiveTemplateFromNpcToPlayerDialogEvent
Osi.DB_GiveTemplateFromNpcToPlayerDialogEvent:Delete(nil, nil, nil)

-- Delete rows where the first column is CON_Drink_Cup_A_Tea_080d0e93-12e0-481f-9a71-f0e84ac4d5a9
Osi.DB_GiveTemplateFromNpcToPlayerDialogEvent:Delete("CON_Drink_Cup_A_Tea_080d0e93-12e0-481f-9a71-f0e84ac4d5a9", nil, nil)

Calling Lua from Osiris S

Functions defined in Lua are not visible from Osiris. To make a Lua function (call/query/event) callable from Osiris, it must first be exported to Osiris using Ext.Osiris.NewCall, NewQuery or NewEvent.

Technical detail: During the Lua server bootstrap process, it is possible to declare new functions (calls/queries/events) that will be accessible to Osiris during compilation and execution. Since Osiris only runs on the server, Osiris functions are inaccessible on the client.

Lua functions are registered through the story header (story_header.div). This means that each time a function is added, changed or removed, the story header must be regenerated when using the editor. (The game regenerates its own story header, so it is always up to date.)

Calls

Calls can be registered using the Ext.NewCall(name, parameters) function. The first parameter is the name of the call to create. The second parameter is the argument list of the Osiris call; it should follow the same syntax that the Osiris story header uses.

It is strongly recommended to follow the Osiris naming scheme, i.e. the name of calls should start with the name prefix of your mod.

Examples:

-- Call with a single argument
local function TestLog(msg)
    print(msg)
end
Ext.Osiris.NewCall(TestLog, "NRD_EXT_TestLog", "(STRING)_Msg");

-- Call with multiple arguments
local function Multiply(a, b)
    print(a * b)
end
Ext.Osiris.NewCall(Multiply, "NRD_EXT_Multiply", "(REAL)_A, (REAL)_B");

Functions exported from Lua can be called in Osiris just like any other call:

IF
[...]
THEN
NRD_EXT_TestLog("Test");
NRD_EXT_Multiply(10, 5);

Queries

Unlike QRYs defined in Osiris code, Lua queries can return values just like the built-in queries. Queries can be registered using the Ext.Osiris.NewQuery(name, parameters) function. The first parameter is the name of the query. The second parameter is the argument list of the Osiris query; it should follow the same syntax that the Osiris story header uses.

Queries have two outcomes: they can either succeed or fail. A successful query returns a value for all of its out arguments; a failed query doesn't return any values. To indicate whether a query succeeded or failed, Lua uses the following mechanism:

  • For 0 out parameters (i.e. when the query returns no values) the function should return true when it succeeded and false when it failed.
  • For N (1 or more) out parameters the function should return N non-nil values when it succeeded and N nil values when it failed. It is not permitted to return a mixture of nil and non-nil values.

The following table summarizes the expected return values:

Number of params Result Return values
0 Successful true
0 Failed false
1 Successful non-nil return value
1 Failed nil
3 Successful 3 non-nil return values - (v1, v2, v3)
3 Failed nil, nil, nil

Example:

local function Divide(a, b)
    if b == 0 then
        return nil
    end
    return a / b
end
Ext.Osiris.NewQuery(Divide, "NRD_EXT_Divide", "[in](REAL)_A, [in](REAL)_B, [out](REAL)_Result");

Functions exported from Lua can be called in Osiris just like any other call:

IF
[...]
AND
NRD_EXT_Divide(100, 5, _Result)
THEN
[...]

Events

New Osiris events can be created by calling Ext.NewEvent(name, parameters). The first parameter is the name of the event. The second parameter is the argument list of the Osiris event; it should follow the same syntax that the Osiris story header uses.

Ext.Osiris.NewEvent("NRD_EXT_TestEvent", "(STRING)_Name");

Custom events can be thrown by calling them like a function:

NRD_EXT_TestEvent("Whatever");

Custom calls/queries

It is possible to call Lua functions by name, without exporting them to the Osiris story header. For this purpose multiple polymorphic functions are provided, NRD_ModCall*(Mod, Func, ...) and NRD_ModQuery*(Mod, Func, ...).

NRD_ModCall(Mod, Func, ...) is a call (i.e. usable from the THEN part of the rule) and returns no results. Its first parameters are the mod name in the global table (i.e. the ModTable setting from ScriptExtender/Config.json) and the function name to call, and it accepts an arbitrary number of arguments to pass to the Lua function. It calls the function Mods[Mod].Func(...) in Lua.

function TestFunc()
    Ext.Utils.Print("Woohoo!")
end

-- 2-arg version
function TestFunc2(str, int)
    Ext.Utils.Print(str .. ", " .. int)
end
NRD_ModCall("YourMod", "TestFunc");

// Two argument call
[...]
AND
IntegerSum(1, 2, _Sum)
THEN
NRD_ModCall("YourMod", "TestFunc2", "string arg", (STRING)_Sum);

NRD_ModQuery*(Mod, Func, ...) is a query (i.e. usable from the IF part of the rule). Its first two parameters are the mod name and function name to call, and it accepts an arbitrary number of arguments to pass to the Lua function as well as an arbitrary number of results. The last character of the function name indicates the number of IN parameters (i.e. NRDModQuery2 for a query with 2 input parameters).

-- 0 input, 0 output
function TestFunc()
    return "test"
end

-- 2 inputs, 2 outputs
function TestFunc2(str1, str2)
    return str1 .. str2, "something else"
end
[...]
AND
// Zero argument, zero return value query
NRD_ModQuery0("YourMod", "TestFunc")
THEN
[...]

[...]
AND
// Two argument, two return value query
NRD_ModQuery2("YourMod", "TestFunc2", "asdf", "ghjk", _Out1, _Out2)
THEN
DebugBreak(_Out1);

Capturing Events/Calls

It is possible to capture Osiris events from Lua without adding Osiris boilerplate code. The Ext.Osiris.RegisterListener(name, arity, event, handler) function registers a listener that is called in response to Osiris events. It currently supports capturing events, built-in queries, databases, user-defined PROCs and user-defined QRYs. Capture support for built-in calls will be added in a later version.

Parameters:

  • name is the function or database name
  • arity is the number of columns for DBs or the number of parameters (both IN and OUT) for functions
  • event is the type of event to capture; possible values:
    • before - Trigger event before a call/DB insert is performed
    • after - Trigger event after a call/DB insert is performed
    • beforeDelete - Trigger event before a DB delete is performed (databases only!)
    • afterDelete - Trigger event after a DB delete is performed (databases only!)
  • handler is a Lua function that is called when the specified event is triggered. The function receives all parameters of the original DB/function.

Example:

Ext.Osiris.RegisterListener("Moved", 1, "after", function (item)
    Ext.Utils.Print("Item moved - " .. item)
end)

Stats (Ext.Stats module)

TODO DOCS - stat creation workflow, stat update workflow

Ext.Stats.GetAllStats(type: string): string[]

Returns a table with the names of all stat entries. When the optional parameter type is specified, it'll only return stats with the specified type. The following types are supported: StatusData, SpellData, PassiveData, Armor, Weapon, Character, Object, SpellSet, EquipmentSet, TreasureTable, TreasureCategory, ItemGroup, NameGroup

Stats Objects

The following functions are only usable for Spell, Status, Passive, Armor, Weapon, Character and Object stats entries. Other stats types (eg. ItemGroups, TreasureTables) have their own separate sections in the docs and cannot be manipulated using these functions.

Ext.Stats.GetStatsLoadedBefore(modGuid: string, type: string): string[]

Returns a table with the names of all stat entries that were loaded before the specified mod. This function is useful for retrieving stats that can be overridden by a mod according to the module load order. When the optional parameter type is specified, it'll only return stats with the specified type. (The type of a stat entry is specified in the stat .txt file itself (eg. type "StatusData").

Ext.Stats.CreateStat(name: string, type: string, template: string|nil): StatEntry

Creates a new stats entry. If a stat object with the same name already exists, the specified modifier type is invalid or the specified template doesn't exist, the function returns nil. After all stat properties were initialized, the stats entry must be synchronized by calling stat:Sync().

  • name is the name of stats entry to create; it should be globally unique
  • type is the stats entry type (eg. SkillData, StatusData, Weapon, etc.)
  • If the template parameter is not null, stats properties are copied from the template entry to the newly created entry
  • If the entry was created on the server, stat:Sync() will replicate the stats entry to all clients. If the entry was created on the client, stat:Sync() will only update it locally.

Example:

local stat = Ext.Stats.CreateStat("NRD_Dynamic_Skill", "SkillData", "Rain_Water")
stat.RainEffect = "RS3_FX_Environment_Rain_Fire_01"
stat.SurfaceType = "Fire"
stat:Sync()

Ext.Stats.GetStat(stat, type, [level]): StatEntry

Returns the specified stats entry as an object for easier manipulation. If the level argument is not specified or is nil, the table will contain stat values as specified in the stat entry. If the level argument is not nil, the table will contain level-scaled values for the specified level. A level value of -1 will use the level specified in the stat entry.

The behavior of getting a table entry is identical to that of StatGetAttribute and setting a table entry is identical to StatSetAttribute.

The StatSetAttribute example rewritten using GetStat:

-- Swap DamageType from Poison to Air on all skills
for i,name in pairs(Ext.Stats.GetAllStats("SkillData")) do
    local stat = Ext.Stats.GetStat(name)
    if stat.DamageType == "Poison" then
        stat.DamageType = "Air"
    end
end

StatEntry:Sync([persist: bool])

Synchronizes the changes made to the specified stats entry to each client. Sync() must be called each time a stats entry is modified dynamically (after ModuleLoading/StatsLoaded) to ensure that the host and all clients see the same properties. The optional persist attribute determines whether the stats entry is persistent, i.e. if it will be written to savegames. If not specified, the persist parameter defaults to true.

StatEntry:SetPersistence(persist: bool) S

Toggles whether the specified stats entry should be persisted to savegames. Changes made to non-persistent stats will be lost the next time a game is reloaded. If a dynamically created stats entry is marked as non-persistent, the entry will be deleted completely after the next reload. Make sure that you don't delete entries that are still in use as it could break the game in various ways.

Reading stat attributes

Stat attributes can be retrieved by reading the appropriate property of the StatEntry object:

local spell = Ext.Stats.GetStat("Shout_FlameBlade", "SpellData")
local useCosts = spell.UseCosts

If the stat entry doesn't have the specified attribute or the attribute is not supported, nil is returned. The list of attributes each stat type supports can be found in Public\Shared\Stats\Generated\Structure\Modifiers.txt.

Technical note: The StatEntry object has an __index metamethod that retrieves the stats property; the StatEntry is not a Lua table and shouldn't be treated as such!

Writing stat attributes

Stat attributes can be updated using simple table assignment:

local spell = Ext.Stats.GetStat("Shout_FlameBlade", "SpellData")
spell.UseCosts = "BonusActionPoint:1;SpellSlot:1:1:2"

This essentially allows on-the-fly changing of data loaded from stat .txt files without having to override the whole stat entry. If the function is called while the module is loading (i.e. from a ModuleLoading/StatsLoaded listener) no additional synchronization is needed. If the function is called after module load, the stats entry must be synchronized with the client via the StatEntry:Sync() call.

Technical note: The StatEntry object has a __newindex metamethod that performs validation and updates the real stats entry in the background.

Example usage of stats read/write (Disable autocast on all spells):

for i,name in pairs(Ext.Stats.GetAllEntries("SpellData")) do
    local spell = Ext.Stats.GetStat(name, "SpellData")
    if spell.Autocast == "Yes" then
        spell.Autocast = "No"
    end
end

Note: When modifying stat attributes that are tables (i.e. Requirements, SpellSuccess, SpellProperties etc.) it is not sufficient to just modify the table, the modified table must be reassigned to the stat property:

local requirements = spell.Requirements
table.insert(requirements, {Name = "Immobile", Param = -1, Not = false})
-- Reassign table to update Requirements
spell.Requirements = requirements

Stat property type notes

For a list of enumeration types and their possible values see Public\Shared\Stats\Generated\Structure\Base\ValueLists.txt or Enumerations.xml.

Flags

The AttributeFlags, SpellFlagList, WeaponFlags, ResistanceFlags, PassiveFlags, ProficiencyGroupFlags, StatsFunctorContext, StatusEvent, StatusPropertyFlags, StatusGroupFlags and LineOfSightFlags enumerations are flags; this means that multiple enumeration values may be assigned to a stats property.

Reading flags:

local spell = Ext.Stats.GetStat("Shout_ArmorOfAgathys", "SpellData")
_D(spell.SpellFlags)
-- Prints:
-- ["HasSomaticComponent", "HasVerbalComponent", "IsSpell"]

Writing flags:

local spell = Ext.Stats.GetStat("Shout_ArmorOfAgathys", "SpellData")
spell.SpellFlags = {"HasVerbalComponent", "IsSpell"}

Requirements

Requirements and MemorizationRequirements are returned in the following format:

[
    {
        "Not" : true, // Negated condition?
        "Param" : "Tag", // Parameter; number for ability/attribute level, string for Tag
        "Requirement" : "TADPOLE_POWERS_BLOCKED" // Requirement name
    },
    {
        "Not" : true,
        "Param" : -1,
        "Requirement" : "Immobile"
    }
]

StatsFunctors

StatsFunctors is a list of functors (actions) that are executed when certain conditions are met (eg. when a spell succeeds/fails). The action to perform is determined by the Type field. Each action has its own parameter types that can be further specified how the action should be done (eg. damage amount and type for DealDamage, etc.).

[[TODO - list of functor types and properties?]]

Example format:

[
        {
                "DamageType" : "Acid",
                "IsSelf" : false,
                "Magical" : false,
                "Nonlethal" : false,
                "PropertyContext" :
                [
                        "AOE",
                        "Target"
                ],
                "Type" : "DealDamage",
                "WeaponDamageType" : "None",
                "WeaponType" : "None"
        }
]

Ext.Stats.ExtraData

Ext.ExtraData is an object containing all entries from Data.txt.

Note: It is possible to add custom ExtraData keys by adding a new Data.txt to the mod and then retrieve them using Lua.

Example:

Ext.Utils.Print(Ext.Stats.ExtraData.WisdomTierHigh)

ECS

TODO - WIP

Entity class

Game objects in BG3 are called entities. Each entity consists of multiple components that describes certain properties or behaviors of the entity. The Lua Entity class is the represntation of an ingame object (eg. character, item, trigger, etc.).

Technical note: For a somewhat more detailed description of the ECS system see:

Entity:HasRawComponent(type) : bool

Returns whether the entity has a component with the given engine type (native C++ class name).

Example:

local char = Ext.GetCharacter(GetHostCharacter())
_D(char:HasRawComponent("ls::TransformComponent"))
-- Prints:
-- true

Entity:GetComponentHandles() : string[]

Returns all engine component types (native C++ class names) that the entity has.

Example:

local char = Ext.GetCharacter(GetHostCharacter())
_D(char:GetAllRawComponents())
-- Prints:
-- {
--      "eoc::ActionResourcesComponent" : "eoc::ActionResourcesComponent Object (1c4000010000039e)",
--      "eoc::BackgroundComponent" : "eoc::BackgroundComponent Object (1e000001000003ff)",
--      "eoc::BackgroundPassivesComponent" : "eoc::BackgroundPassivesComponent Object (66c00001000003ff)",
-- ...

Entity:GetAllComponents() : Component[]

Returns all components that are attached to the entity.

Note: This method only returns components whose structure is known to the Script Extender. Components with unknown structure are not returned.

Example:

local entity = Ext.GetCharacter(GetHostCharacter())
_D(entity:GetAllRawComponents())
-- Prints:
-- {
--      "ActionResources" :
--      {
--              "Entity" : "Entity (02c0000100000180)",
--              "GetReplicationFlags" : "function: 00007FFDE482D5E0",
-- ...

Entity:GetComponent(name) : Component|nil

Returns the specified component if it is attached to the entity. If the component is not present the method returns nil.

Note: This method only returns components whose structure is known to the Script Extender. Components with unknown structure are not returned.

Note: Although the type (character, item, etc.) of the entity cannot be determined directly, it can be inferred from the components that are attached to the entity. Eg. to check if the entity is a character, an entity:GetComponent("ServerCharacter") ~= nil check can be used.

Example:

local entity = Ext.GetCharacter(GetHostCharacter())
_D(entity:GetComponent("DisplayName"))
-- Prints:
-- {
--      "Entity" : "Entity (02c0000100000180)",
--      "Name" : "Tav",
--      "NameKey" : "ResStr_669727657",
-- ...

The __index metamethod of the Entity object is a shorthand for GetComponent:

local entity = Ext.GetCharacter(GetHostCharacter())
-- The two below are equivalent
local displayName = entity:GetComponent("DisplayName")
local displayName = entity.DisplayName

Entity:IsAlive() : boolean

Returns whether the entity still exists.

Entity:GetEntityType() : integer

Returns the numeric type ID of the entity. (For development purposes only.)

Entity:GetSalt() : integer

Returns the salt value of the entity handle. (For development purposes only.)

Entity:GetIndex() : integer

Returns the entity index of the entity handle. (For development purposes only.)

TODO - GetComponents() -> move to __index

ComponentHandle class

The Lua ComponentHandle class is a reference to a specific component of an entity. Technical detail: The handle class stores the handle (identifier) of the component; each time an access is made using the handle, the extender looks up the component using its handle.

ComponentHandle:Get() : Component

Returns the component referenced by the component handle. If the component handle is invalid or the component doesn't exist anymore the method returns nil.

Note: This method only returns components whose structure is known to the Script Extender. Components with unknown structure are not returned.

Example:

-- [[ TODO - FIND GOOD EXAMPLE WITH COMPONENT REFS! ]]

ComponentHandle:GetType() : integer

Returns the numeric type ID of the object handle. (For development purposes only.)

ComponentHandle:GetTypeName() : string

Returns the type name of the component referenced by the handle.

ComponentHandle:GetSalt() : integer

Returns the salt value of the object handle. (For development purposes only.)

ComponentHandle:GetIndex() : integer

Returns the object index of the object handle. (For development purposes only.)

Mod Info

IsModLoaded(modGuid)

Returns whether the module with the specified GUID is loaded. This is equivalent to Osiris NRD_IsModLoaded, but is callable when the Osiris scripting runtime is not yet available (i.e. `ModuleLoading˙, etc events).

Example:

if (Ext.IsModLoaded("5cc23efe-f451-c414-117d-b68fbc53d32d"))
    Ext.Utils.Print("Mod loaded")
end

GetModLoadOrder()

Returns the list of loaded module UUIDs in the order they're loaded in.

GetModInfo(modGuid)

Returns detailed information about the specified (loaded) module. Example:

local loadOrder = Ext.GetModLoadOrder()
for k,uuid in pairs(loadOrder) do
    local mod = Ext.GetModInfo(uuid)
    Ext.Utils.Print(Ext.JsonStringify(mod))
end

Output:

{
    "Author" : "Larian Studios",
    "Dependencies" :
    [
        "2bd9bdbe-22ae-4aa2-9c93-205880fc6564",
        "eedf7638-36ff-4f26-a50a-076b87d53ba0"
    ],
    "Description" : "",
    "Directory" : "DivinityOrigins_1301db3d-1f54-4e98-9be5-5094030916e4",
    "ModuleType" : "Adventure",
    "Name" : "Divinity: Original Sin 2",
    "PublishVersion" : 905969667,
    "UUID" : "1301db3d-1f54-4e98-9be5-5094030916e4",
    "Version" : 372645092
}

Utility functions

Ext.Require(path) R

The Ext.Require function is the extender's version of the Lua built-in require function. The function checks if the file at Mods/<ModuleUUID>/ScriptExtender/Lua/<path> was already loaded; if not, it'll load the file, store the return value of the main chunk and return it to the caller. If the file was already loaded, it'll return the stored return value. Note: Ext.Require should only be called during module startup (i.e. when loading BootstrapClient.lua or BoostrapServer.lua). Loading Lua files after module startup is deprecated.

Ext.Utils.Print(...)

Prints the specified value(s) to the debug console. Works similarly to the built-in Lua print(), except that it also logs the printed messages to the editor messages pane.

Ext.GetTranslatedString(key[, fallback])

Returns the text associated with the specified translated string key. If the key doesn't exist, the value of fallback is returned. If no fallback value is specified, an empty string ("") is returned.

local str = Ext.GetTranslatedString("h17edbbb2g9444g4c79g9409gdb8eb5731c7c", "[1] cast [2] on the ground")

Ext.Utils.AddPathOverride(originalPath, newPath)

Redirects file access from originalPath to newPath. This is useful for overriding built-in files or resources that are otherwise not moddable. Make sure that the override is added as early as possible (preferably in ModuleLoading), as adding path overrides after the game has already loaded the resource has no effect.

Example:

Ext.AddPathOverride("Public/Game/GUI/Assets/Buttons/BigBtn_d.DDS", "Public/YourMod/GUI/Assets/Buttons/BigBtn_d.DDS")

Ext.PlayerHasExtender(playerGuid)

Returns whether the player that controls the character playerGuid has a compatible Script Extender version installed. Example:

for i,player in  ipairs(Osi.DB_IsPlayer:Get(nil)) do
    if  not Ext.PlayerHasExtender(player[1]) then
        OpenMessageBox(player[1], "Install the extender!!!")
    end
end

Ext.Utils.MonotonicTime()

Returns a monotonic value representing the current system time in milliseconds. Useful for performance measurements / measuring real world time. (Note: This value is not synchronized between peers and different clients may report different time values!)

Example:

local startTime = Ext.MonotonicTime()
DoLongTask()
local endTime = Ext.MonotonicTime()
Ext.Utils.Print("Took: " .. tostring(endTime - startTime) .. " ms")

JSON Support

Two functions are provided for parsing and building JSON documents:

  • Ext.Json.Parse(json: string)
  • Ext.Json.Stringify(value[, beautify: bool = true, stringifyInternalTypes: bool = false, iterateUserdata: bool = false]) : string.

Lua types are encoded in JSON (and vice versa) using the following table:

Lua Type JS Type
nil null
boolean boolean
number (integer) number
number (float) number
string string
table (sequential numeric keys [1..n]) array
table (non-sequential keys) object

It is not possible to stringify/parse lightuserdata, function and thread values. Iterable userdata values are stringified if the iterateUserdata option is set, however JSON decoding them will result in a table, not a userdata object. Non-iterable userdata types cannot be stringified.

Since JSON only supports string object keys, Lua number (integer/float) keys are saved as string.

Usage example:

local tab = {
    asd = 1234,
    arr = {
        "ab", "bc", 44
    }
}

local json = Ext.Json.Stringify(tab)
Ext.Utils.Print(json)

local decoded = Ext.Json.Parse(json)
Ext.Utils.Print(decoded.arr[1])

Expected output:

{
    "arr": [
        "ab",
        "bc",
        44
    ],
    "asd" : 1234
}

ab

Engine Events

Load Events

ModuleLoadStarted

The ModuleLoadStarted event is thrown when the engine has started loading mods. Mod data (stats, localization, root templates, etc.) is not yet loaded when this listener is called, so most mod editing functionality (eg. Ext.StatSetAttribute) is inaccessible. The purpose of this event is to allow adding filesystem-level hooks using Ext.AddPathOverride before mod data is loaded.

StatsLoaded

StatsLoaded is thrown after stats entries (weapons, skills, etc.) were cleared and subsequently reloaded. Stat modifications that are valid for every game session should be applied here.

ModuleLoading

ModuleLoading is thrown after the stats manager has finished loading.

SessionLoading

SessionLoading is thrown when the the engine has started setting up a game session (i.e. new game, loading a savegame or joining a multiplayer game).

SessionLoaded

SessionLoaded is thrown when the game session was set up.

Osiris Preprocessor S

To support mods that may not want to depend on the extender but want to take advantages of its features when available, two "preprocessor" constructs are provided that allow conditional compilation of code when the extender is present / not present. To make use of this feature, enable the PreprocessStory variable in your extension config.

The first construct allows defining code that only runs when the extender is loaded. To achieve this, the block comment /* ... */ is tagged so the extender can uncomment the code during compilation if it is present. Syntax:

/* [EXTENDER_ONLY]
// Code in this block is normally commented out; however,
// the leading and trailing comment tags are removed if
// the extender is present.
IF
Whatever()
THEN
DB_NOOP(1);
*/

The second construct is the opposite, i.e. it only removes code when the extender is loaded. Syntax:

// [BEGIN_NO_EXTENDER]
// This code is executed if the extender is not loaded.
// With the extender, the code between "// [BEGIN..." and "// [END..." is removed entirely.
IF
Whatever()
THEN
DB_NOOP(1);
// [END_NO_EXTENDER]

Example usage:

IF
TextEventSet("preprocessor")
THEN
/* [EXTENDER_ONLY]
DebugBreak("This code only runs if the script extender is loaded");
*/
DebugBreak("This always runs");
// [BEGIN_NO_EXTENDER]
DebugBreak("This only runs if the script extender is *NOT* loaded");
// [END_NO_EXTENDER]

Osiris Data Types S

The data types mentioned for Osiris functions should be interpreted as follows:

Integer

A 32-bit signed integer; equivalent to the Osiris INTEGER type. Can be read/written using the XyzGetInt() and XyzSetInt() getter/setter functions.

Real

Equivalent to the Osiris REAL type. Can be read/written using the XyzGetReal() and XyzSetReal() getter/setter functions.

String

Equivalent to the Osiris STRING type. Can be read/written using the XyzGetString() and XyzSetString() getter/setter functions.

Flag

An integer value with two allowed values, 0 and 1. These are essentially boolean values, but since Osiris lacks boolean support, they are passed as integers. Can be read/written using the XyzGetInt() and XyzSetInt() getter/setter functions.

Enum

An integer value with a list of allowed values. The values can be found in Enumerations.xml if it is a standard enumeration, or in the Enumerations section of this document if it's a non-standard/undocumented enumeration.

An enum property can be read/written using its index with the XyzGetInt() and XyzSetInt() getter/setter functions, or using its textual label with the XyzGetString() and XyzSetString() getter/setter functions. Example:

// Set DamageSourceType to SurfaceCreate by ID
NRD_StatusSetInt(_Char, _Status, "DamageSourceType", 2);
// Set DamageSourceType to SurfaceCreate by name
NRD_StatusSetString(_Char, _Status, "DamageSourceType", "SurfaceCreate"); 
...
// GetDamageSourceType by ID (_Val == 2)
NRD_StatusGetInt(_Char, _Status, "DamageSourceType", _Val);
// GetDamageSourceType by name(_Val == "SurfaceCreate")
NRD_StatusGetString(_Char, _Status, "DamageSourceType", _Val); 

Osiris Functions S

Stat functions

These functions can be used to query stats entries.

StatExists

query NRD_StatExists([in](STRING)_StatsId)

Checks whether the specified stat entry exists. The query succeeds if the stat entry exists, and fails if it does not.

StatAttributeExists

query NRD_StatAttributeExists([in](STRING)_StatsId, [in](STRING)_Attribute)

Checks whether the stat entry _StatsId has an attribute (data) named _Attribute. The query succeeds if the attribute exists, and fails if it does not.

StatGetInt

query NRD_StatGetInt([in](STRING)_StatsId, [in](STRING)_Attribute, [out](INTEGER)_Value)

Returns the specified _Attribute of the stat entry. If the stat entry does not exist, the stat entry doesn't have an attribute named _Attribute, or the attribute isn't convertible to integer, the query fails.

Notes:

  • For enumerations, the function will return the index of the value in the enumeration. eg. for Damage Type Corrosive, it will return 3.

StatGetString

query NRD_StatGetString([in](STRING)_StatsId, [in](STRING)_Attribute, [out](STRING)_Value)

Returns the specified _Attribute of the stat entry. If the stat entry does not exist, the stat entry doesn't have an attribute named _Attribute, or the attribute isn't convertible to string, the query fails.

Notes:

  • For enumerations, the function will return the name of the enumeration value (eg. Corrosive).

StatGetType

query NRD_StatGetType([in](STRING)_StatsId, [out](STRING)_Type)

Returns the type of the specified stat entry. If the stat entry does not exist, the query fails. Possible return values: Character, Potion, Armor, Object, Shield, Weapon, SkillData, StatusData.

StatGetExtraData

query NRD_StatGetExtraData([in](STRING)_Key, [out](REAL)_Value)

Returns the specified key from Data.txt. If the key does not exist, the query fails.

Math functions

Sin

query NRD_Sin([in](REAL)_In, [out](REAL)_Out)

Computes the sine of the argument _In (measured in radians).

Cos

query NRD_Cos([in](REAL)_In, [out](REAL)_Out)

Computes the cosine of the argument _In (measured in radians).

Tan

query NRD_Tan([in](REAL)_In, [out](REAL)_Out)

Computes the tangent of the argument _In (measured in radians).

Round

query NRD_Round([in](REAL)_In, [out](REAL)_Out)

Computes the nearest integer value to the argument _In, rounding halfway cases away from zero.

Ceil

query NRD_Ceil([in](REAL)_In, [out](REAL)_Out)

Computes the smallest integer value not less than the argument.

Floor

query NRD_Floor([in](REAL)_In, [out](REAL)_Out)

Computes the largest integer value not greater than the argument.

Abs

query NRD_Abs([in](REAL)_In, [out](REAL)_Out)

Computes the absolute value of the argument.

Pow

query NRD_Pow([in](REAL)_Base, [in](INTEGER)_Exp, [out](REAL)_Out)

Computes the value of _Base raised to the power _Exp.

Sqrt

query NRD_Sqrt([in](REAL)_In, [out](REAL)_Out)

Computes the square root of _In.

Exp

query NRD_Exp([in](REAL)_In, [out](REAL)_Out)

Computes e (Euler's number, 2.7182818...) raised to the given power _In.

Log

query NRD_Log([in](REAL)_In, [out](REAL)_Out)

Computes the natural (base e) logarithm of _In.

Factorial

query NRD_Factorial([in](INTEGER)_In, [out](INTEGER)_Out)

Computes the factorial of the value _In.

Random

query NRD_Random([in](INTEGER)_Min, [in](INTEGER)_Max, [out](INTEGER)_Result)
query NRD_RandomReal([in](REAL)_Min, [in](REAL)_Max, [out](REAL)_Result)

Returns uniformly distributed random numbers in the range [_Min ... _Max].

Min

query NRD_Min([in](INTEGER)_A, [in](INTEGER)_B, [in](INTEGER)_C, [out](INTEGER)_Result)
query NRD_Min([in](INTEGER)_A, [in](INTEGER)_B, [in](INTEGER)_C, [in](INTEGER)_D, [out](INTEGER)_Result)
query NRD_MinReal([in](REAL)_A, [in](REAL)_B, [in](REAL)_C, [out](REAL)_Result)
query NRD_MinReal([in](REAL)_A, [in](REAL)_B, [in](REAL)_C, [in](REAL)_D, [out](REAL)_Result)

Returns the smallest input value (_A, _B, _C or _D).

Max

query NRD_Max([in](INTEGER)_A, [in](INTEGER)_B, [in](INTEGER)_C, [out](INTEGER)_Result)
query NRD_Max([in](INTEGER)_A, [in](INTEGER)_B, [in](INTEGER)_C, [in](INTEGER)_D, [out](INTEGER)_Result)
query NRD_MaxReal([in](REAL)_A, [in](REAL)_B, [in](REAL)_C, [out](REAL)_Result)
query NRD_MaxReal([in](REAL)_A, [in](REAL)_B, [in](REAL)_C, [in](REAL)_D, [out](REAL)_Result)

Returns the greatest input value (_A, _B, _C or _D).

String functions

StringCompare

query NRD_StringCompare([in](STRING)_A, [in](STRING)_B, [out](INTEGER)_Result)

Compare strings A and B using lexicographic ordering. Value of Result:

  • -1, if A comes before B
  • 0, if A is equal to B
  • 1, if A comes after B

StringLength

query NRD_StringLength([in](STRING)_String, [out](INTEGER)_Length)

Computes the length of the string _String, and returns it in _Length.

StringFormat

query NRD_StringFormat([in](STRING)_Format, [out](STRING)_Result, [_Arg1], [_Arg2], [_Arg3], ...)

Substitutes the placeholders [1], [2], ... with the function arguments _Arg1, _Arg2 and returns the formatted string in _Result. The arguments accept any type (INTEGER, REAL, ...) as long as it is passed using a STRING typecast (i.e. (STRING)_IntVariable).

Example:

...
Random(_Modulo, _Random)
AND
NRD_StringFormat("Random modulo [1], number [2]", _Fmt, (STRING)_Modulo, (STRING)_Random)
THEN
DebugBreak(_Fmt);

Substring

query NRD_Substring([in](STRING)_String, [in](INTEGER)_From, [in](INTEGER)_Length, [out](STRING)_Result

Returns a substring of length _Length from the position _From. Rules:

  • If _From is past the end of the string an empty string is returned.
  • If _Length is past the end of the string, the portion of the string between _From and the end of the string is returned.
  • If _From is negative, _From is treated as an offset from the end of the string (i.e. for a 8 character string, a _From value of -2 means offset 6); this rule is helpful for fetching the last N characters of a string
  • If _Length is negative, _Length is treated as an offset from the end of the string

Examples:

NRD_Substring("ABCDEFGH", 0, 3, _Res) // "ABC"
NRD_Substring("ABCDEFGH", 9999, 3, _Res) // ""
NRD_Substring("ABCDEFGH", 0, 9999, _Res) // "ABCDEFGH"
NRD_Substring("ABCDEFGH", -2, 2, _Res) // "GH"
NRD_Substring("ABCDEFGH", 0, -2, _Res) // "ABCDEF"
NRD_Substring("ABCDEFGH", 3, -2, _Res) // "DEF"

RegexMatch

query NRD_RegexMatch([in](STRING)_String, [in](STRING)_Regex, [in](INTEGER)_FullMatch, [out](INTEGER)_Result)

Matches the string _String against the ECMAScript regex pattern _Regex. If _FullMatch is 1, the whole string much match the pattern, otherwise a partial match is allowed. The query fails if the specified pattern is not a valid ECMAScript regex pattern . The query returns 1 if the pattern matches the string and 0 otherwise. Because of limitations in the Osiris parser, the character ^ must be replaced with #.

Examples:

NRD_RegexMatch("GetValue", "Get|GetValue", 0, _Ret) // _Ret = 1
NRD_RegexMatch("GetValues", "Get|GetValue", 1, _Ret) // _Ret = 0

RegexReplace

query NRD_RegexReplace([in](STRING)_String, [in](STRING)_Regex, [in](STRING)_Replacement, [out](STRING)_Result)

Replaces all occurrences of the ECMAScript regex pattern _Regex in _String with the replacement pattern _Replacement and returns the result in _Result. The query fails if the specified pattern is not a valid ECMAScript regex.

Examples:

NRD_RegexReplace("Quick brown fox", "a|e|i|o|u", "[$&]", (STRING)_Ret)
// _Ret = "Q[u][i]ck br[o]wn f[o]x"

GuidString

query NRD_GuidString([in](STRING)_String, [out](GUIDSTRING)_Result)

Attempts to convert _String to a GuidString. If the conversion succeeds (i.e. the string is a valid NameGUID), the value is returned in _Result. If _String is not a valid NameGUID, the query fails.

StringToInt

query NRD_StringToInt([in](STRING)_String, [out](INTEGER)_Result)

Attempts to convert _String to an integer value. If the conversion succeeds (i.e. the string is a valid integer), the integer value is returned in _Result. If _String is not a valid integer, the query fails.

For detailed rules check the reference

StringToReal

query NRD_StringToReal([in](STRING)_String, [out](REAL)_Result)

Attempts to convert _String to a real value. If the conversion succeeds (i.e. the string is a valid real), the real value is returned in _Result. If _String is not a valid real value, the query fails.

For detailed rules see check the reference

IntegerToString

query NRD_IntegerToString([in](INTEGER)_Integer, [out](STRING)_Result)

Converts _Integer to a string value.

RealToString

query NRD_RealToString([in](REAL)_Real, [out](STRING)_Result)

Converts _Real to a string value.

Miscellaneous functions

ShowErrorMessage

call NRD_ShowErrorMessage((STRING)_Message)

Displays an error dialog box with the text _Message.

DebugLog

call NRD_DebugLog((STRING)_Message)

Functionally equivalent to DebugBreak(), except that the _Message argument accepts any type, not only strings. To use non-string arguments, cast the variable to the appropriate type.

Example usage:

IF
StoryEvent((ITEMGUID)_Item, "TEST")
THEN
NRD_DebugLog((STRING)_Item);

ForLoop

call NRD_ForLoop((STRING)_Event, (INTEGER)_Count)
call NRD_ForLoop((GUIDSTRING)_Object, (STRING)_Event, (INTEGER)_Count)
event NRD_Loop((STRING)_Event, (INTEGER)_Num)
event NRD_Loop((GUIDSTRING)_Object, (STRING)_Event, (INTEGER)_Num)

Counts from 0 up to _Count - 1 and throws loop event _Event for each value. Unlike regular events, NRD_Loop are not queued and are thrown immediately (i.e. during the NRD_ForLoop call), so there is no need for an additional cleanup/finalizer event.

Example usage:

// ...
NRD_ForLoop("MyMod_SomeLoopEvent", 10);

IF
NRD_Loop("MyMod_SomeLoopEvent", _Int)
THEN
NRD_DebugLog((STRING)_Int);

Example usage with GUIDs:

// ...
NRD_ForLoop(CHARACTERGUID_S_GLO_CharacterCreationDummy_001_da072fe7-fdd5-42ae-9139-8bd4b9fca406, "MyMod_SomeLoopEvent", 10);
  
IF
NRD_Loop((CHARACTERGUID)_Char, "MyMod_SomeLoopEvent", _Int)
THEN
NRD_DebugLog((STRING)_Char);
NRD_DebugLog((STRING)_Int);