Replacing Game UI and Creating Custom GUI
GUI Creation Tools and Preparation
Section titled “GUI Creation Tools and Preparation”Recent versions use a new GUI framework so MOD creators can make their own graphical interfaces and related features. The new UI framework is built with the FairyGUI editor. It can be used without Unity for UI design and creation, then imported into the game through overrides.
FairyGUI editor download: https://www.fairygui.com/
The Sands of Salzaar project uses FairyGUI editor version 5.0.10. Use the same version when making MODs where possible to avoid code compatibility issues.
After a custom UI is complete, export the related resources into your MOD folder. Use RES or ABS overrides to map them under Assets\BuildSource\UIRes\; then the Package and its contained control resources can be accessed normally.
For a GUI sample, search for “Custom GUI and Lua Script Demo MOD” in the mod store and download the corresponding open-source MOD sample.
Replacing the Game Main Screen
Section titled “Replacing the Game Main Screen”Story campaign MODs can specify a game background image or animation through special MOD config fields, and can replace the main screen UI. These replacements take effect when that MOD is enabled.
To configure main screen replacement, edit default.json under the Config folder in the MOD directory and add the following fields for the target main screen elements:
homepage_background: game background image or UI animation prefab mapping path, such asAssets/BuildSource/Backgrounds/xxx.png.homepage_gui: custom GUI screen info. Format:PackageName#ComponentName#ScriptName, for exampleMyGUI#MyHomeScene#MyHomeSceneScript.
Preloading Game UI Packages
Section titled “Preloading Game UI Packages”When a UI package is large, or when resources inside a MOD package are needed early, such as using an image inside a UI package as a skill page background, the plugin settings table can preload the UI package.
Place the UI resources to preload under MODFolder\RES\UIRes. In the plugin settings table, add an entry with Type ADD, Target Field gui_preload_package, and Target Value set to the UI package name. When the MOD reloads, all defined UI packages are loaded.
Replaceable Game UI Screens
Section titled “Replaceable Game UI Screens”Game UI screens can be replaced by setting the following Target Field values in the plugin settings table. The Type field of the plugin info must be OVERRIDE. The format is PackageName#ControlName#ScriptName(optional, only effective for some main screens).
For UI replacement, only the object with the highest priority among all entries for the same target field takes effect.
Supported replaceable UI objects:
| Plugin Table Target Field | Description |
|---|---|
| gui_sandbox_panel | Main world-map panel. Must specify the corresponding Lua script, for example MyGUI#MySandboxPanel#MySandboxPanel.The UI Lua script must implement: initPanel(_view, _controllerScript): initializes control objects. _view is the GComponent object for the screen. _controllerScript is the current C# controller object for the panel.onHotkeyPressed(_keycode): called when a registered hotkey is pressed. _keycode is the integer code of the pressed hotkey. See “Hotkey Registration and Replacement” for hotkey codes. Return true if the hotkey is handled successfully; otherwise return false.onUpdateGameInfo(): called when in-game info updates. |
| gui_team_infobox | Replaces the team info panel on the world map. Only fill PackageName#ControlName; script override is not supported. Name key objects according to the sample. |
| gui_place_infobox | Replaces the place info panel on the world map. Same requirements as above. |
| gui_building_infobox | Replaces the building info panel on the world map. Same requirements as above. |
| gui_bs_hero_infobox_f | Replaces the allied hero info panel in battle. Same requirements as above. |
| gui_bs_hero_infobox_e | Replaces the enemy hero info panel in battle. Same requirements as above. |
| gui_bs_unit_infobox_f | Replaces the allied common unit info panel in battle. Same requirements as above. |
| gui_bs_unit_infobox_e | Replaces the enemy common unit info panel in battle. Same requirements as above. |
| gui_bs_building_infobox_f | Replaces the allied building unit info panel in battle. Same requirements as above. |
| gui_bs_building_infobox_e | Replaces the enemy building unit info panel in battle. Same requirements as above. |
| gui_bs_panel | Main battle panel. Must specify the corresponding Lua script, for example MyGUI#MyBsPanel#MyBsPanel.The UI Lua script must implement: initPanel(_view, _controllerScript): initializes control objects. _view is the GComponent object for the screen. _controllerScript is the current C# controller object for the panel.onHotkeyPressed(_keycode): called when a registered hotkey is pressed. _keycode is the integer code of the pressed hotkey. See gui_sandbox_panel.onUpdateGameInfo(): called when in-game info updates. |
| gui_bs_panel | Game battle screen panel. Must specify the corresponding Lua script, for example MyGUI#MyBsPanel#MyBsPanel.The UI Lua script must implement the required panel interfaces. |
| gui_trade_win | UI replacement for the trade screen. Format: PackageName#ControlName#ScriptName. |
| gui_inventory_win | UI replacement for the inventory screen. Format: PackageName#ControlName#ScriptName. |
| gui_place_win | UI replacement for the place screen. Format: PackageName#ControlName#ScriptName. |
| gui_repair_win | UI replacement for the equipment repair screen. Format: PackageName#ControlName#ScriptName. |
| gui_party_win | UI replacement for the party screen. Format: PackageName#ControlName#ScriptName. |
| gui_globaltalent_win | UI replacement for the talent screen. Format: PackageName#ControlName#ScriptName. |
| gui_information_win | UI replacement for the information screen. Format: PackageName#ControlName#ScriptName. |
| gui_tooltips | UI replacement for tooltip boxes. Format: PackageName#ControlName#ScriptName. |
UI Patch Scripts
Section titled “UI Patch Scripts”Some UI scripts may not be listed in the replaceable UI set. Sometimes you may only want to change one function of one screen instead of replacing the whole screen, while keeping the change adaptable to version updates and compatible with other MODs. In this case, use a patch script to modify the original script.
Patch script Type is ADD, Target Field is lua_ui_patch#ScriptPath, and Target Value is the patch script path.
Example: to show the lord’s level on the place screen panel, add a plugin settings table entry in the MOD:
| KEY | Type_Type | TargetKey_TargetKey | Priority_Priority | TargetValue_TargetValue |
|---|---|---|---|---|
| game_place_ui_patch_1 | ADD | lua_ui_patch#gui/GamePlaceWin | 0 | Patch/GamePlaceWin1 |
Then open Res/LuaScript/Patch/GamePlaceWin1.lua.txt under the MOD directory and write:
local oldGetPlaceDescInfoStr = GetPlaceDescInfoStr
function GetPlaceDescInfoStr(tagPlace) local str = oldGetPlaceDescInfoStr(tagPlace) local lord = tagPlace:GetLordRole() local lordLevel = "None" if lord ~= nil then lordLevel = tostring(lord.roleLevel) end local newStr = "Lord level: " .. lordLevel .. "\n" .. str return newStrendThis caches the old GetPlaceDescInfoStr method, then defines a new GetPlaceDescInfoStr method and injects custom logic. During game runtime, Lua-driven screens load gui/GamePlaceWin.lua.txt first, then load the defined patch script Patch/GamePlaceWin1.lua.txt.
Getting Local Variables
Section titled “Getting Local Variables”Some scripts store values in local variables. After the script finishes executing, these local variables are dumped into debugTools.localVariables as a dictionary table.
To get the original script’s local contentPane, use:
debugTools.localVariables["contentPane"]Alternative Patch Style
Section titled “Alternative Patch Style”The game provides the Lua debugTools library to make patch writing easier. The patch above can be written as:
GetPlaceDescInfoStr = debugTools:postfix(GetPlaceDescInfoStr, function(args, ret) local str = ret local tagPlace = args[1] local lord = tagPlace:GetLordRole() local lordLevel = "None" if lord ~= nil then lordLevel = tostring(lord.roleLevel) end local newStr = "Lord level: " .. lordLevel .. "\n" .. str return true, newStrend)debugTools is injected into the current globals by default after a UI script loads.
Functions in the debugTools environment must be called with a colon : instead of a dot .. Colon calls are Lua syntax sugar, meaning the object itself is passed as the first parameter. For example, debugTools:prefix(func, patch, priority) is equivalent to debugTools.prefix(debugTools, func, patch, priority).
Using methods provided by debugTools removes the need to add patches manually and improves compatibility when multiple MODs use the same approach.
Methods in debugTools:
| Name | Parameters | Description | Example |
|---|---|---|---|
| debugTools:locals() | None | Gets all local variables in the call environment and stores them in debugTools.localVariable. | |
| debugTools:prefix(func, patch, priority) | func: original functionpatch: patch function. The function parameter is the original function argument table. Return values are whether to interrupt execution and the interrupted return value; if no return value is given, the original function is not interrupted.priority: optional patch priority, default 0; higher priority runs earlier. | Applies a prefix patch to the original function. The prefix patch runs before the original function and can modify the argument table to replace function parameters. | onInit = debugTools.prefix(onInit, function(args) print(“run before onInit”) end |
| debugTools:postfix(func, patch, priority) | func: original functionpatch: patch function. Parameters are the original argument table and original return value. Return values are whether to replace the return value and the replacement return value; if no return value is given, the original return value is not replaced.priority: optional patch priority, default 0; higher priority runs earlier. | Applies a postfix patch to the original function. The postfix patch runs after the original function and can modify the argument table and return value to replace parameters and returns. | onInit = debugTools:postfix(onInit, function(args, ret) print(“run after onInit”) end |
Hotkey Registration and Replacement
Section titled “Hotkey Registration and Replacement”Use the plugin settings table to register your own hotkey codes or disable existing game hotkey codes. To register a hotkey, fill the Type field with HOTKEY, then fill the Target Field with the hotkey group and hotkey code in the format GroupID/HotkeyCode/[SubcategoryID(optional)], for example battle/11000.
Only two hotkey groups are currently supported: common and battle.
common: common shortcut setting group.battle: battle shortcut setting group.
The hotkey code is a globally unique integer code. After registration, it can be checked in the corresponding script interface.
Subcategory ID is the identifier for a category under each group. If the hotkey code is an existing game code, this field has no effect. Otherwise, the game first tries to register it under the specified subcategory. If the subcategory does not exist or this field is empty, it is registered under the “Other” category.
The Target Value field in the plugin settings table can contain hotkey reset information. Fill disable to directly disable the hotkey. Otherwise, use the following format:
Hotkey display field name (can use [@str=ID] to look up localized text by ID)#Default KeyCode for key binding 1#Default KeyCode for key binding 2#Key description (optional, can use [@str=ID])Hotkey registration from multiple MOD plugins can take effect at the same time, but only one hotkey entry for the same target field is effective. The effective entry is the one with the highest priority among entries for that target field.