Interface

From CK3 Wiki
Jump to navigation Jump to search

CK3's user interface (UI) is highly moddable, but for this reason UI mods disable achievements, since a player could cheat with them.

The game also includes a GUI editor, which allows to inspect UI elements and edit them in the game, although it has performance and usability issues.

Modders can:

  • change the visual style of the interface
  • make windows movable and resizable
  • change and remove elements
  • add new buttons
  • show more information from the code
  • add new windows

Modders can’t:

  • add new hotkeys (only reuse the existing ones)
  • display information from one window in another, unless developers included that possibility.


Basics[edit | edit source]

The interface in CK3 is created through .gui files in the game/gui folder, which are somewhat similar to html files.

As such, you can edit them with any text editor, like VS Code, Sublime or Atom. Choose Python or Perl 6 for syntax highlighting, they fit well.

CK3 uses .dds files for textures, which are saved inside the game/gfx/ folder.

To edit or save .dds files use either Photoshop with Intel plugin or GIMP with this plugin.


To auto-reload gui files in the game and use the GUI editor, add -debug_mode and -develop launch options:

  • right-click the game on Steam, choose Properties, in Launch Options at the bottom enter -debug_mode -develop

Every time a .gui file is saved it will be reloaded by the game.

You can also use the following console commands:

  • reload gui - manually reload all gui files (usually not needed with auto-reload)
  • reload texture - reloads all texture files. Add the name of the texture to only reload it, like reload texture flatmap.dds
  • gui_editor - opens the GUI editor
  • tweak gui.debug - allows to enable highlighting of UI elements. The tooltips will display their name, size and position.
  • DumpDataTypes - will create a data_types.log in your log folder (Documents/Paradox Interactive/Crusader Kings III/logs) listing all available GUI functions for each window/game object

Other tips:

  • always have the error log open to see if there is a mistake in the code (it's in the same log folder)
  • enable the error tracker in the game by clicking "Toggle Release Mode" below the console, to see if your changes craeted any new errors
  • mute the game when using reload gui command, as it triggers the intro sound every time
  • add the gui folder to your text editor, so it uses it for autocompletion
  • you can use test_gui.gui in gui/debug for testing. To show this window, open the console and click Test Window.
  • fold code so it's easier to see its structure. Usual hotkeys are Ctrl+K, Ctrl+1 (where 1 is the level at which to fold the code).

Creating a GUI mod[edit | edit source]

1. Start the game launcher, go to Mods, Mod Tools and fill in all the fields, including tags.

Clicking Create will create a new folder and a .mod file in Documents/Paradox Interactive/Crusader Kings III/mod.

2. Next, create a "gui" folder inside your mod and copy the files you want to mod there from game/gui.

  • If you don’t know which file is needed, use the GUI editor and inspect it in the game.

GUI Editor[edit | edit source]

GUI Editor is a developer tool for editing the UI in the game.

To use it you need to launch the game with -debug_mode and -develop options.


To open the editor, either:

  • press Ctrl+F8
  • open the console with the ` key (below Esc), click GUI Editor
  • open the console, run gui_editor command

Features[edit | edit source]

By default the editor starts with the Edit mode enabled. You can disable it in the top window, called Outliner. Hotkey "E".

  • The edit mode is similar to the Inspect mode in browsers. While it’s enabled you can’t interact with the game, but it allows you to select parts of the UI and change them in the Properties window below.
  • Scroll with your mouse wheel to change what element the editor should focus on, since hud.gui tends to get on top of other windows.
  • Yellow border indicated the selected element. To hide other borders, uncheck "Show Hierarchy" in the Outliner (Hotkey "L").
  • Holding the right mouse button allows to move the selected element.
  • To undo anything, press Ctrl+Z or the undo button in the Outliner. Redo is next to it, Ctrl+Y.
  • Red stars * in the Outliner indicate unsaved changes. Press Ctrl+S or the save button at the top to save them. Make sure the gui files you're editing are in your mod, otherwise it will write the changes to the game folder. (To reset them, verify integrity from Steam's properties window)
  • You can move any dev windows by dragging them and resize them by dragging the edges.
  • You can drag UI elements in the Outliner's hierarchy to reorder them. Right-click to show the context menu.
  • You can change or add new properties (by clicking the plus symbol) in the Properties window.


By clicking "Window" in the Outliner, you can open two more windows: UI components and Registered Data Types.

  • UI Components is like a palette from which you can drag new elements to the UI. gui/shared/standard.gui and gui/defaults.gui contain the most common things, like buttons, icons and text.
  • Registered Data Types may be used to look up what functions are available to display data from the game.
  • Clicking the top right button in the Data Types will dump this data to your log folder (Documents/Paradox Interactive/CK3/logs). You can also use "DumpDataTypes" console command.


Note: it is often easy to select a template by mistake, changing which will affect all instances of it in the UI. Pay attention to what file you have selected in the outliner. If you see in the Properties window a blue header that starts with "type:", it is a template, so be careful not to edit this part (unless you intend to).

UI code[edit | edit source]

CK3’s UI is composed of containers and objects inside them.

Most windows, for example, are created using a window container, while map icons use a widget or an hbox.

The order in the file determines the order on the screen: what's lower in the code will appear on a higher layer.

Most elements can also contain others, for example, there can be a textbox inside an icon inside a button. Nested elemenets, aka children, will be moved with their parent.

Position is set relative to the top left corner (either of the screen or the parent). This can be changed with parentanchor property. The available options are: left, right, top, bottom, hcenter (horizontal center) and vcenter (vertical center). They can be combined with a | like this: parentanchor = right|vcenter.

Every element is opened and closed with curly brackets, like this: container = { }.

The common code style is to open and close the block on the same level, while indenting the contents with one tab:

widget = {
  size = { 50 50 }
  alpha = 0.5
}

This helps you see the structure of the code better, notice any missing or extra brackets, and is needed for some editors to correctly fold the blocks of code.

Promotes and Functions[edit | edit source]

Each window has a predefined set of commands - promotes and functions - available for it. These can be found in the data_types.log file in Documents/Paradox Interactive/Crusader Kings III/logs/ after you use the DumpDataTypes console command.

They are used to display all data from the game, like your name, gold, children, and to set button actions.

A promote returns a scope, i.e a game object, like Character or Province, while a function returns a number, a string or a boolean (true/false), etc.

Global commands can be used anywhere, like GetPlayer (returns the player character) or GetCurrentDate.

Other commands can only be used in their window/object, for example, GetParents can only be used in the character window and must be started with CharacterWindow.GetParents.

Commands can be chained like this:

CharacterWindow.GetCharacter.GetPrimaryTitle.GetHeir.GetPrimarySpouse.GetFather

Objects can inherit the scope from their parent, meaning we wouldn't need to retype the line above to show information about this character. Instead we can set datacontext of a widget to this line and then every textbox in it will use "[Character.GetNameNoTooltip]", "[Character.GetGold]", etc.

The same applies to items in gridboxes.

UI Components[edit | edit source]

window

  • A movable container. To enable movement, add movable = yes property.
  • Can be fixed size or resized by its children.
  • In the game the background is set using templates, like using = Window_Background and using = Window_Decoration.
  • If a child is outside of a window, it won't be clickable and won't show a tooltip. Use allow_outside = yes to change this. There is no z-coordinate, so z ordering is done in a way that first written child will be placed behind second written child. To make component clickable and also on top of other component, you need to place it after that other component.

widget

  • A static container. Similar to a window in other regards.

margin_widget

  • Similar to a widget, but can be resized with margins. (This allows us to make windows that resize to screens of different size, by setting height to 100% and margins to ~50 to show the hud)

container

  • Does not have a fixed size (but you can set maximumsize).
  • Resizes automatically to fit all its children, including invisible ones. Use ignoreinvisible = yes to ignore them.
  • Often used to group multiple elements to move them together.

flowcontainer

  • Arranges all its children in a horizontal row and resizes to fit them. Use direction = vertical to make it vertical.
  • Doesn't ignore invisible children by default. Use ignoreinvisible = yes to change it.
  • Can't have fixed size.
  • Its children cannot have positions, as they are set automatically.
  • If you need to adjust position of its child, you can put it inside a container or a widget and then change position relative to this parent.

hbox vbox

  • Arranges all its children in a horizontal row and spreads them along its width. Vbox is the same but vertical.
  • Can't have fixed size, instead resizes to fit its children or to the size of the parent, depending on the layout policy.
  • Can be limited by minimumsize, maximumsize, min_width, max_width and margins.
  • Ignores invisible children by default. Use ignoreinvisible = no to change this.
  • Accepts datamodels (to create lists from game data).

dynamicgridbox

  • Is only used with datamodels.
  • Arranges all the items vertically. Use flipdirection = yes to make it horizontal.
  • Doesn't ignore invisible items by default. Use ignoreinvisible = yes to change it.
  • Resized by the content and limited by minimumsize and maximumsize.
  • Items can be of different size.
  • Can become laggy with very long lists.

fixedgridbox

  • Similar to a dynamic box but all its items are of fixed size (it is essentially a table).
  • Is only used with datamodels.
  • Arranges all its items vertically. Use flipdirection = yes to make it horizontal.
  • Cannot ignore invisible items.
  • Can be fixed size, resized by the content and limited by minimumsize and maximumsize.
  • Much better for performance with long lists.

overlappingitembox

  • Is only used with datamodels.
  • Arranges all its items horizontally and overlaps them if the list is longer that the size of the box. Use flipdirection = yes to make it horizontal.
  • Can be fixed size or autoresized.

scrollarea

  • A widget with scrollbars that appear if the content is bigger than its size.
  • Scrollbars can be disabled with scrollbarpolicy_horizontal = always_off and scrollbarpolicy_vertical = always_off.
    • A scrollarea with no scrollbars can be used to crop lists or images.
  • Can be fixed size, resized by the content and limited by minimumsize and maximumsize.

button

  • A clickable object. Accepts onclick and onrightclick.
    • When adding a right click function, include button_ignore = none.
  • Doesn't have a texture by default.
  • Can be fixed size or resized by its children.
    • A 0x0 button can be used to add invisible hotkeys.

icon

  • Displays a texture.
  • Can be used as a widget to store children.
  • Can be flipped with mirror = horizontal or mirror = vertical.

textbox

  • Shows text.
  • Can be fixed size or autoresized.
  • Use elide = right or elide = left to cut off text that is too long
  • Can be a single line or multiple, with multiline = yes.
  • Game files often use templates set in gui/shared/text.gui, like text_single. Use them to keep visual consistency and to type less code every time.

hbox/vbox[edit | edit source]

Hboxes and vboxes are resizable container that order, resize or spread out their children. hbox orders children horizontally, vbox vertically, otherwise they work the same way, so all examples here apply to both.

On the screenshots below, hboxes have black background. All of the examples are available in the UI Library mod.

By default, an hbox works similar to a flowcontainer: it orders children horizontally and resizes to fit them.
hbox = {
    button_round = {}
    button_round = {}
}
Simple hbox wide.jpg
With layoutpolicy_horizontal = expanding it expands to the width of its parent and spreads its chilren out
hbox = {
    layoutpolicy_horizontal = expanding
    button_round = {}
    button_round = {}
}
Expanded hbox.jpg
To group its children, we can use expand = {}, which is a template widget with layout policies set to "growing" (it's defined in gui/shared/windows.gui)
hbox = {
    layoutpolicy_horizontal = expanding
    button_round = {}
    button_round = {}
    expand = {}
}
Ordered hbox.jpg
With "expanding" policy on children, it also resizes them

This is useful for creating tabs, without needing to manually set their size

hbox = {
    layoutpolicy_horizontal = expanding
    button_standard = { layoutpolicy_horizontal = expanding }
    button_standard = { layoutpolicy_horizontal = expanding }
}
Tabs hbox 2.jpg
With "expanding" horizontal and vertical policy, the hbox and its children will resize in both directions
hbox = {
    layoutpolicy_horizontal = expanding   layoutpolicy_vertical = expanding
    button_standard = {
        layoutpolicy_horizontal = expanding   layoutpolicy_vertical = expanding
    }
    button_standard = {
        layoutpolicy_horizontal = expanding   layoutpolicy_vertical = expanding
    }
}
Big hbox.png

If placed in fixed size parent it will by default expand both horizontally and vertically up to entire size of the parent. But if placed in other vbox/hbox they will not expand to the parent size.

Layout policies[edit | edit source]

Layout policies control how children are resized by hboxes/vboxes. This also applies to boxes inside other boxes.

There are two types, layoutpolicy_horizontal and layoutpolicy_vertical, controlling horizontal and vertical behavior.

There are five policies, with fixed applied by default:

  1. fixed - keeps original size. Cannot grow or shrink past it. An hbox/vbox with "fixed" will resize to fit children, like a container.
  2. expanding - grows to the width/height of the parent, cannot shrink below original size. Has priority over children with other policies. If multiple children are set to "expanding", they split available space equally.
  3. growing - same as "expanding" but with lower priority. If a child with "expanding" is present, "growing" will not grow. This also means the expand = {} widget would resize to 0, so change its policies if you want to use it with "expanding".
  4. preferred - grows or shrinks, depending on available space.
  5. shrinking - can shrink below its original size, cannot grow past it.

Layout policies also respect minimumsize, maximumsize, min_width and max_width.

"expanding" takes priority over "growing" but will not shrink it smaller than the original (fixed) size
hbox = {
	max_width = 400
	layoutpolicy_horizontal = expanding

	button_standard_small = { layoutpolicy_horizontal = expanding }
	button_standard_small = { layoutpolicy_horizontal = growing }
}
Growing hbox 2.png
"preferred" and "shrinking" can both shrink, when there is little space. You may need to limit your hbox with max_width or "shrinking" policy to see this effect.
hbox = {
	layoutpolicy_horizontal = shrinking

	button_standard_small = { layoutpolicy_horizontal = growing }
	button_standard_small = { layoutpolicy_horizontal = preferred }
	button_standard_small = { layoutpolicy_horizontal = shrinking }
}
Shrinking hbox 2.png

Note that objects with large size can stretch the hbox/vbox, even when visually it looks like they fit. Set max_width and test any of your text fields with very long strings to make sure the window doesn't change.

Templates[edit | edit source]

Templates and types are named blocks of code which can be used multiple times throughout the code, like buttons, window backgrounds, and text styles. This helps maintain the same style and reduces the amount of code we write.

Templates can store the contents of an entire window or just one line, like this one:

template Window_Size_Sidebar
{
		size = { 610 100% }
}

This template can be used with "using = Window_Size_Sidebar", which will, essentially, replace the "using" line with the contents of the template.

Templates are global and can be defined in any .gui file. Most of the game templates are stored in gui/shared. A local version, local_template, has to be defined within the same file.

Commonly used templates and types can be seen in the UI Library. To access it, open the console, toggle Release Mode, and a new button called "UI Library" should appear.

Types[edit | edit source]

While templates may be as small as one property, types are always whole elements, like a button or a widget.

text_single and text_multi are types of a textbox with many properties already defined for them, so we don't need to retype them every time and instead simply write:

text_single = {
	text = "my text"
}

Types are defined in a slightly different way, by creating a named group of types first:

types Standard_Types
{
	type text_single = textbox {
	...
	}
}

Blockoverride[edit | edit source]

Templates and types can have named override blocks, which allow us to edit a part of an instance without changing the whole template. For example, a template may have a default block of text:

block "text"
{
	text = "default_text"
}

To replace it, we add a blockoverride with the same name in our instance:

blockoverride "text"
{
	text = "actual text"
}

We can also remove it from our instance like this: blockoverride "text" {}

Adding mod compatibility[edit | edit source]

It is common that mods overwrite the same file and can't work together.

Modders can collaborate to provide compatibility by using templates and creating a "hook" for another mod.

To do this, every modder includes templates from other mods in the modded window, while templates themselves are defined in separate files.

For example, Fullscreen Barbershop includes "using = cfp_bg_buttons" template from the Community Flavor Pack which adds extra backgrounds. If a user isn't subscribed to CFP, this template can't be found and doesn't affect the game at all. If the user is subscribed to both mods, then the template is applied.

This, of course, reqiures modders to work together to set up templates for each other's mods.

Scripted GUIs[edit | edit source]

Scripted guis are, essentially, hidden events triggered from the UI.

The are stored as .txt files in game/common/scripted_guis and cannot be reloaded from the game, unlike the .gui files.

The basic structure of a scripted gui is:

gui_name = {
	scope = character 		# the root scope, i.e. the target of the effects
	saved_scopes = {} 		# any additional targets

	is_shown = {} 		# is it visible on the UI?

	ai_is_valid = {} 		# is the AI allowed to use it? Disabled by default.

	is_valid = {} 		# can the player use it?

	effect = {			# what it does
		custom_tooltip = ""	# adds a tooltip
	}
}

Not all of the blocks are necessary. Sometimes a scripted gui may only contain the scope and is_shown or effect.

In the .gui file we use the following:

datacontext = "[GetScriptedGui('gui_name')]"

onclick = "[ScriptedGui.Execute( GuiScope.SetRoot( GetPlayer.MakeScope ).End)]"

visible = "[ScriptedGui.IsShown( GuiScope.SetRoot( GetPlayer.MakeScope ).End)]"

enabled = "[ScriptedGui.IsValid( GuiScope.SetRoot( GetPlayer.MakeScope ).End)]"

tooltip = "[ScriptedGui.BuildTooltip( GuiScope.SetRoot( GetPlayer.MakeScope ).End)]"

datacontext is necessary to link the element to the scripted gui. Other commands won't work without it.

In this example the scripted gui is scoped to the player with a global function GetPlayer.MakeScope. It can be changed to someone in the character window, e.g. CharacterWindow.GetCharacter.MakeScope, or if the scope was a province, HoldingView.GetProvince.MakeScope.

ScriptedGui.Execute is used with buttons and will execute everything listed in the effect block in the scripted gui.

IsShown and IsValid check for conditions in is_shown and is_valid blocks.

BuildTooltip can also be used with a textbox to display custom text in the UI.

To save another scope to our scripted gui, we use AddScope like this:

"[ScriptedGui.Execute( GuiScope.SetRoot( GetPlayer.MakeScope ).AddScope('target', CharacterWindow.GetCharacter.MakeScope ).End )]"

And then we use the same name in the scripted gui:

saved_scope = {
  target
}

Multiple scopes can be saved this way.

It is important to not put any spaces before the dots or opening parentheses! Execute( is correct, Execute ( is not. Other spaces can be omitted, but they help with readability.

Displaying a variable or script value[edit | edit source]

Variables and script values can be shown on the UI like this:

variable:

text = "[GetPlayer.MakeScope.Var('test_var').GetValue|1]"

script value (as found in common > script_values):

text = "[GuiScope.SetRoot( GetPlayer.MakeScope ).ScriptValue('test_value')|0]"

In this example the variable is stored in the player character and is called test_var. Any other scope can be used, just remember to add MakeScope.

|1 at the end is optional and will cut off all decimals save for one, so instead of 1.573 you will see 1.5. Note that it does not round the value. You can set this at any number, add % to convert to a percentage, add = or + to color the value if it's positive or negative.

When using localization to display values in events, we use this:

event_var: "[ROOT.GetCharacter.MakeScope.Var('test_var').GetValue|0]"

event_value: "[SCOPE.ScriptValue('test_value')|0]"

If you have a saved scope (named "target" here), it changes to this:

event_var: "[target.MakeScope.Var('test_var').GetValue|0]"

event_value: "[GuiScope.SetRoot( target.MakeScope ).ScriptValue('test_value')|0]"

Displaying data lists[edit | edit source]

We can create custom lists of characters, for example, to make societies.

To do this, first, we need to add them to a variable list. This can be done though an event or a scripted gui, like this:

effect = {
	every_living_character = {
		limit = {
			has_trait = paranoid
		}

		root = {
			add_to_variable_list = {
				name = secret_society
				target = prev
			}
		}
	}
}

If we fire this effect for the player, they will be the root, so the list will be stored in them.

Then we use any list box (vbox, dynamicgridbox, fixedgridbox) with the datamodel set to our list:

dynamicgridbox = {
	datamodel = "[GetPlayer.MakeScope.GetList('secret_society')]"

	item = {
		flowcontainer = {
			datacontext = "[Scope.GetCharacter]"

			portrait_head_small = {}

			text_single = {
				text = "[Character.GetNameNoTooltip]"
			}
		}
	}
}

New windows and toggles[edit | edit source]

There isn't a simple way to create a new window and make your mod compatible with others. We have to either overwrite the hud or other windows or use an event to open the window.

The usual way is to add the new window to hud.gui inside of ingame_topbar widget and then add a button to show/hide it.

The alternative is to rewrite one of the unused windows, like test_gui and add a button with the console command that shows it.

There is a number of ways to add toggles, the following three do not persist between game sessions, but can be easily set up in the .gui file itself:

  • PdxGuiWidget - straightfoward, but gets complicated with multiple toggles
  • Animations - longer, but easier to trigger multiple things
  • System variables - somewhat complicated but very flexible and convenient

Scripted Guis allows us to save toggles to the save file as variables and trigger multiple things, but are more complicated and have to be set up in a separate folder.

Toggles with PdxGuiWidget[edit | edit source]

PdxGuiWidget is a simple function used to hide or reveal named elements.

In this example we have a container with a hidden submenu, one button that shows it and another that hides it.

  1. when clicked, the first button goes back to its parent, searches in it for the submenu and the other button, reveals them and hides itself
  2. the second button searches for the element and the button, reveals them and hides itself
container = {
	button = {
		name = "show submenu"

		onclick = "[PdxGuiWidget.AccessParent.FindChild('submenu').Show]"
		onclick = "[PdxGuiWidget.AccessParent.FindChild('hide submenu').Show]"
		onclick = "[PdxGuiWidget.Hide]"
	}

	button = {
		name = "hide submenu"
		visible = no

		onclick = "[PdxGuiWidget.AccessParent.FindChild('submenu').Hide]"
		onclick = "[PdxGuiWidget.AccessParent.FindChild('show submenu').Show]"
		onclick = "[PdxGuiWidget.Hide]"
	}

	widget = {
		name = "submenu"
		visible = no
	}
}

If the elements are separated by more parents/children, we can repeat AccessParent like this:

onclick = "[PdxGuiWidget.AccessParent.AccessParent.AccessParent.AccessParent.FindChild('submnenu').Show]"

Each button can hide or reveal multiple elements of any type. You only need to provide the name.

Pros:

  • easy to set up for a simple toggle

Cons:

  • the toggles will reset any time the game is restarted
  • If you want to hide multiple things or toggles, the code will get very bloated and hard to manage
  • trying to hide entries in a data list (like a dynamicgridbox) will only hide the first instance

Toggles with animation[edit | edit source]

We can set up animations that are triggered by buttons (or conditions) to hide/show elements or to even move them. This way we don't need to count how many parents separate the button and the window and we can trigger many things at once with just one onclick.

The previous example would look like this and work the same way. The first button triggers "show_submenu", which hides the button and shows the rest, while the second button triggers "hide_submenu", which hides this button and the widget and shows the first button.

container = {
	button = {

		state = { # this is an animation
			name = show_submenu
			on_start = "[PdxGuiWidget.Hide]"
		}
		state = {
			name = hide_submenu
			on_start = "[PdxGuiWidget.Show]"
		}

		onclick = "[PdxGuiTriggerAllAnimations('show_submenu')]"
	}

	button = {
		visible = no

		state = {
			name = show_submenu
			on_start = "[PdxGuiWidget.Show]"
		}
		state = {
			name = hide_submenu
			on_start = "[PdxGuiWidget.Hide]"
		}

		onclick = "[PdxGuiTriggerAllAnimations('hide_submenu')]"
	}

	widget = {
		visible = no

		state = {
			name = show_submenu
			on_start = "[PdxGuiWidget.Show]"
		}
		state = {
			name = hide_submenu
			on_start = "[PdxGuiWidget.Hide]"
		}
	}
}

it is longer, but animations can be saved as templates and reused with one line, like using = hide_animation. Fullscreen Barbershop uses animations extensively, if you want a better example.

Pros:

  • easier to link many things together, and even open a different window and trigger an animation in it

Cons:

  • animation blocks can be quite lengthy
  • all toggles will reset when the game is restarted

Toggles with Scripted GUIs[edit | edit source]

Scripted guis allows us to use script to toggle visibility. They make it easier to manage multiple things but can impact performance when used in big numbers.

For a scripted toggle we need to create a .txt file in common/scripted_guis/. The name of the file can be anything.

A basic toggle in this file would looks like this:

gui_toggle = {
	scope = character

	is_shown = {
		has_variable = gui_toggle
	}

	effect = {
		if = {
			limit = {
				has_variable = gui_toggle
			}
			remove_variable = gui_toggle
		}
		else = {
			set_variable = gui_toggle
		}
	}
}

When clicked, it adds a variable to the scoped character and removes it when clicked again.

Then, if the variable is present, our window will be visible. If it's not, it's hidden.

This is how the gui file would look like. We can use just one button as its function will change with each click.

container = {
	button = {
		datacontext = "[GetScriptedGui('gui_toggle')]"
		onclick = "[ScriptedGui.Execute( GuiScope.SetRoot( GetPlayer.MakeScope ).End )]"
	}

widget = {
	datacontext = "[GetScriptedGui('gui_toggle')]"
	visible = "[ScriptedGui.IsShown( GuiScope.SetRoot( GetPlayer.MakeScope ).End )]"
	}
}

"GetPlayer" is a global promote and returns the player character.

"Player.MakeScope" makes our player the scope of the scripted gui, meaning this is where we store the variable in.

Two buttons can be used if you want different tooltips for them. In this case, copy the 'visible' property to both buttons, but add 'Not' to one of them, so it's hidden by default:

visible = "[Not(ScriptedGui.IsShown( GuiScope.SetRoot( GetPlayer.MakeScope ).End ))]"

Pros:

  • the toggles are saved in the character and won't reset unless they die or you start a new game
  • easier to link many things, even in different windows

The downsides:

  • it is harder to remember the syntax (copy the code from here to reduce the chance of mistakes)

System Variables[edit | edit source]

System variables are used internally by the game, they are not save persistent and cannot be directly accessed through scripts.

No setup is needed for them as they can be directly created in the .gui file.

The syntax for using system variables is:

onclick = "[GetVariableSystem.Toggle( 'var_name' )]"

or:

datacontext = "[GetVariableSystem]"
onclick = "[VariableSystem.Toggle( 'var_name' )]"

The available functions are:

  • Clear - Clear( 'var_name' ) clears the variable
  • ClearIf - ClearIf( 'var_name', Condition ) clears the variable if Condition is true
  • Exists - Exists( 'var_name' ) Boolean, returns true if the variable exists
  • Get - Get( 'var_name' ) CString, returns the value stored in the variable
  • HasValue - HasValue( 'var_name', 'string' ) Boolean, returns true if the stored value matches the provided value
  • Set - Set( 'var_name', 'string' ) sets the stored value to the provided value
  • Toggle - Toggle( 'var_name' ) clears the variable if it exists, creates it if it does not

Toggles with System Variables[edit | edit source]

A basic toggle using system variables in this file would looks like this:

container = {
	button = {
		onclick = "[GetVariableSystem.Toggle( 'gui_toggle' )]"
	}

	widget = {
		visible = "[GetVariableSystem.Exists( 'gui_toggle' )]"
	}
}

When clicked, the system variable is toggled depending on whether it exists, this is then used to show/hide the widget.

Tabs with System Variables[edit | edit source]

A basic setup for three tabs would look like this:

container = {
	button = {
		onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_1' )]"
	}
	button = {
		onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_2' )]"
	}
	button = {
		onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_3' )]"
	}

	widget = {
		visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_1' )]"
	}
	widget = {
		visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_2' )]"
	}
	widget = {
		visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_3' )]"
	}
}

Note that the variable initially has no value and none of the widgets would show.

To set a default tab the variable needs to be set by the button opening the window:

button = {
	onclick = "[GetVariableSystem.Toggle( 'gui_toggle' )]" # this opens the window
	onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_1' )]" # this set the default tab
}

or using a state block when the window is shown:

state = {
	name = _show
	on_start = "[GetVariableSystem.Set( 'gui_tabs', 'tab_1' )]"
}

Alternatively one of the widgets can be set to appear when the variable doesn't exist, avoiding the need for an initial value:

container = {
	button = {
		onclick = "[GetVariableSystem.Clear( 'gui_tabs' )]"
	}
	button = {
		onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_2' )]"
	}
	button = {
		onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_3' )]"
	}

	widget = {
		visible = "[Not( GetVariableSystem.Exists( 'gui_toggle' ) )]"
	}
	widget = {
		visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_2' )]"
	}
	widget = {
		visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_3' )]"
	}
}

This is the equivalent of the first example with one of the methods to set a default value.

Pros:

  • simple and easy to remember syntax
  • easier to link many things, even in different windows
  • can be extended with additional commands (see below) to show entirely new windows, avoiding the need to have the widget in hud.gui

The downsides:

  • no direct interaction with scripts, they must be set & cleared using the gui
  • can be harder to keep track of
  • all toggles will reset when the game is restarted

Creating a new Widget[edit | edit source]

It is possible to create entirely new windows by using the ExecuteConsoleCommand( ... ) command.

First create a window in a .gui file to be displayed, for example "gui/custom_windows/my_window.gui". Ensure that you have named the main window.

window = {
	name = "my_custom_window"
	parentanchor = center
	layer = middle
	size = { 100 100 }
	using = Window_Background
}

The above creates a 100 by 100 window in the center of the screen, the important part is the name as it is used to create the window.

The button to create the window would look like:

button = {
	onclick = "[ExecuteConsoleCommand('gui.createwidget gui/custom_windows/my_window.gui my_custom_window')]"
}

To close it:

button = {
	onclick = "[ExecuteConsoleCommand('gui.ClearWidgets my_custom_window')]"
}

Make sure that there is only one space between gui.ClearWidgets and my_custom_window. If there is more than 1 space, gui.ClearWidgets will clear all widgets created with console, not just my_custom_window.

Or combined into a toggle:

button = {
	onclick = "[ExecuteConsoleCommand( Select_CString( GetVariableSystem.Exists('my_window_open'), 'gui.ClearWidgets my_custom_window', 'gui.createwidget gui/custom_windows/my_window.gui my_custom_window' ) )]"
	onclick = "[GetVariableSystem.Toggle('my_window_open')]"
}

The toggle works by setting a system variable and selecting the command to execute based on it.

Select_CString( ... ) takes three arguments:

  • A Condition
  • A string for true
  • A string for false

If the condition returns true, the first string is used, else the second is. In the above toggle, if the system variable exists the window is destroyed, otherwise it is created.