revert Alignment
This commit is contained in:
Vannifar 2026-04-25 10:02:11 +02:00
parent 15a5108e23
commit c4b6d9bdeb
250 changed files with 11067 additions and 16365 deletions

View file

@ -1,49 +1,14 @@
 Structure ===
=== Structure ===
Note: May not be exhaustive.
Note: this is very very incomplete...
Event IDs are namespaced. In each events file, define a namespace once at the top:
namespace = my_events
Then define events as:
my_events.1001 = { ... }
event_namespace.42 = {
type = character_event/letter_event/court_event/activity_event # Default to character, what type of event this is
scope = scope_type # Optional, defaults to character, will make this event fire with a different expected root scope. none can be used to have no scopes set up.
window = window_name # Optional, what special event file and widget in gui/event_windows should this event use, only used for character events
my_events.1001 = {
type = character_event/letter_event/court_event/activity_event # Optional, defaults to character_event
scope = scope_type # Overrides the events root scope. Optional, defaults to character scope. Use scope = none for no root scope, scope = artifact for events centered around artifacts, etc.
# Optional custom event window name.
# For character/letter events this is the popup window in gui/event_windows.
# For activity events this is used by activity event inserts.
window = window_name
# anonymous_letter_event - letter_event, but without sender portrait and sigil widget
# big_event_window - used for task_contracts, bookmark events, decision outcomes, story cycles, black plague, etc.
# character_event - default
# duel_event - used in single combat events
# fullscreen_event - use splash screen queue
# letter_event - used for responses to character_interactions, between characters that are not in the same location
# scheme_conclusion_window - big_event_window, however we always use one of the sub-types:
# scheme_failed_event - scheme_conclusion_window, but with failure header
# scheme_preparations_event - scheme_conclusion_window, but with scheme_preparation widget
# scheme_successful_event - scheme_conclusion_window, but with successful header
# scheme_conclusion_event_no_header
# scheme_conclusion_window, but without header
#visit_settlement_window - big_event_window, used by laamp visit settlement decision
# Basic event text/effects (commonly used)
title = my_event_title # Dynamic Description syntax, see bottom of document
desc = my_event_desc # Dynamic Description syntax, see bottom of document
trigger = { ... }
immediate = { ... }
after = { ... }
hidden = yes/no # If yes, no event window is shown
major = yes/no
major_trigger = { ... }
# Non-character scoped events generally need to be hidden or major.
# If you have a cooldown, the recipient root gets a saved variable with that duration.
# The variable name is based on the event ID.
# Trigger legality checks include cooldown.
# If you have a cooldown, the recipient will get a variable saved with that duration. The variable name will be the event ID
# Anything that checks that an event is legal to fire will also check that the recipient doesn't have that variable
cooldown = {
days/weeks/months/years = script value
}
@ -58,112 +23,89 @@ my_events.1001 = {
lower_left_portrait = X
lower_center_portrait = X
lower_right_portrait = X
sender = X # required for letter events
sender = X # for letter events, required
# X can be one of:
X = event target
X = {
character = event target
trigger = ... # optional, controls visibility for this portrait in the scope of the portrait character
animation = animation name # optional default animation, used if no triggered animations pass their trigger. See animations.txt for all possible animations, in-game: toggle event tools in the event or open portrait editor through the console.
scripted_animation = key_of_scripted_animation # optional alternative to animation
trigger = ... # optional, a trigger saying whether this portrait should be visible, in the scope of the portrait character, the event scope is accessible as root scope
animation = animation name # optional, the animation to show for this portrait. It's used if no triggered animations pass their trigger. The default animation will be used if nothing is this item is not specified.
scripted_animation = key_of_scripted_animation # optional, instead of 'animation' you can also define a 'scripted_animation'
# First triggered animation whose trigger passes is used.
# A list of triggered animations. The first triggered animation that passes the trigger check will be used.
triggered_animation = {
trigger = ...
animation = animation name
# Or use scripted_animation instead of animation
# Instead of 'animation' you can also define a 'scripted_animation'
scripted_animation = key_of_scripted_animation
camera = camera_name # optional, overrides portrait camera when chosen
### brief: camera ( database key, optional )
# A camera type defined for a triggered animation will override the default
# camera defined for the event portrait if that animation is chosen
#
camera = camera_name
}
triggered_animation = ...
# First triggered outfit whose trigger passes is used.
# A list of triggered outfits. The first triggered otfit that passes the trigger check will be used.
triggered_outfit = {
trigger = ...
outfit_tags = ...
remove_default_outfit = ...
hide_info = ...
}
triggered_outfit = ...
# Optional portrait behavior toggles
# Override camera to be used instead of the normal event ones
camera = camera_key
override_imprisonment_visuals = yes/no
animate_if_dead = yes/no
outfit_tags = { tag1 tag2 } # Specifies outfit tags for this portrait in ascending priority (i.e. tag2 will "override" tag1 here if anything with tag2 is found in a specific portrait modifier category)
remove_default_outfit = yes/no # If set to yes, portrait modifier categories in which nothing matches any of the event tags will be disabled completely (no by default)
hide_info = yes/no # If set to yes, only the portrait will be shown, with no identifiable elements (no CoA, tooltips, clicks...) (no by default)
outfit_tags = { tag1 tag2 } # Specifies outfit tags for this portrait in ascending priority (i.e. tag2 will "override" tag1 here if anything with tag2 is found in a specific portrait modifier category)
remove_default_outfit = yes/no # If set to yes, portrait modifier categories in which nothing matches any of the event tags will be disabled completely (no by default)
hide_info = yes/no # If set to yes, only the portrait will be shown, with no identifiable elements (no CoA, tooltips, clicks...) (no by default)
}
# Specify an artifact to appear in the event on the specified position
artifact = {
target = event target
position = lower_left_portrait/lower_center_portrait/lower_right_portrait
# Cannot share the same position as a portrait
trigger = ... # Optional, as for portraits
# Can't be in the same position as a portrait
trigger = ... # Optional, as for character portraits
}
# Letter events should define an opening text
opening = my_letter_opening # Localization key or dynamic desc block
# Court events may define court scene behavior
court_scene = {
button_position_character = scope:a_character
court_owner = scope:a_character
court_event_force_open = yes/no
show_timeout_info = yes/no
should_pause_time = yes/no
roles = {
scope:a_character = {
role = some_court_scene_role # or group = some_court_scene_group
animation = some_animation
scripted_animation = some_scripted_animation
}
}
}
# Runs if a queued/instant event fails trigger checks.
# Events selected from on_actions are filtered by validity before queuing, so this is typically not run for that path.
# This will be run if a queued event (or one triggered immediately from script) does not fulfil its trigger
# Events failing to trigger from an on-action will *not* run this
on_trigger_fail = {
some effect
}
# Specify custom widgets to embed in the event. See Custom Widgets below.
# Specify custom widgets to embed in the event. See section about Custom Widgets below.
widgets = {
widget = {
# Scope: event scope after immediate effect. Default: always = yes
# Trigger that controls the availability of the widget. Scope: same as the event, after immediate effect. Default: always = yes
is_shown = {}
# Widget file at <event_window_widgets>/<widget_name>.gui
# Name of the widget to use. Must be at the path <event_window_widgets>/<widget_name>.gui
gui = "<widget_name>"
# Parent container widget name in the event window
# Name of the widget where this custome widget will be insert
container = "<container_widget_name>"
# Controller syntax:
# Simple form:
# Some widgets require a custom controller (see below). Default: default
controller = <controller_type>
# Structured form (used by some controllers, e.g. text):
# controller = {
# type = <controller_type>
# data = { ... }
# }
# Optional scope setup effect for controller expectations
# Effect to set up the scope as required by the controller. Scope: same as the event, after immediate effect, doesn't modify the event scope, though. Default: {}
setup_scope = {}
}
}
widget = { ... } # alternative syntax for one widget
widget = { ... } # alternative syntax for a single widget. Follows the same info as the widget in the widgets parameter
option = { # An option the player/AI can pick
# Localization key or dynamic name block
name = X # Dynamic Description syntax, see bottom of document
# Localization key for the event option button text
name = X
# Effects run when this option is picked (inline, no label)
# The effects that will be run when picking the options. Written directly here with no label
X..
# Trigger required for option to be valid
# A trigger that has to be fulfilled for this option to be valid.
trigger = {}
# If the option is invalid, but this trigger is valid, then the option will be shown (but disabled).
# If the event is invalid, but this trigger is valid, then the option will be shown (but disabled).
# This behavior is also influenced by the EVENT_OPTIONS_SHOWN_HIDE_UNAVAILABLE or SCHEME_PREPARATION_OPTIONS_SHOWN_HIDE_UNAVAILABLE defines depending on event type.
show_as_unavailable = {}
@ -171,19 +113,9 @@ my_events.1001 = {
highlight_portrait = scope:a_character
reason = <flag> # Special reason for why this option is unlocked, can be any arbitrary string, is be checked in the UI to show special by reason
skill = diplomacy/martial/stewardship/intrigue/learning/prowess # Marks this option as skill-relevant in unlock reason UI (if shown); you still have to specify the skill and level in the trigger to unlock it
trait = some_trait # Marks this option as trait-relevant in unlock reason UI (if shown); you still have to specify the trait in the trigger to unlock it
# Misc option behavior
show_unlock_reason = yes/no # Controls whether unlock reason UI is shown for this option
is_cancel_option = yes/no # Marks this option as a cancel/back-out style option (used by some widgets/controllers)
clicksound = "sound_event" # Sound to play when selecting this option
fallback = yes/no # If no regular options are valid, fallback options are considered
exclusive = yes/no # If any exclusive option is valid, non-exclusive options are ignored
# Parameters to impact the way ai-characters pick options to resolve their events.
# We have 2 mutually exclusive parameters; ai_chance, and ai_will_select where the only difference is the syntax for calculating the value.
# In practice if both are present, ai_will_select is used.
# Parameters to impact the way ai-characters pick options to resolve their events
# We have 2 mutually exclusive parameters; ai_chance, and ai_will_select where the only difference is the syntax for calculating the value
ai_chance = { # See common/scripted_modifiers/_scripted_modifiers.info for more details
base = 10
modifier = {
@ -213,20 +145,20 @@ my_events.1001 = {
}
}
theme = "" # Theme to use in the event. For a list, check: 00_event_themes.txt
override_background = { # A background that can be shown when the event pops up. This overrides the theme one. In case that there are multiples the first one that fits the trigger will be the one selected. In case none fits the ones in the theme will be checked after.
theme = "" # Theme to use in the event. For a list, check: 00_event_themes.txt
override_background = { # A background that can be shown when the event pops up. This overrides the theme one. In case that there are multiples the first one that fits the trigger will be the one selected. In case none fits the ones inthe theme will be checked after.
trigger = {} # Receives the event scope to check if it's valid.
reference = "" # Path to the texture
}
override_transition = { # A transition that can be shown when the event pops up, before the event options and backgrounds. This overrides the theme one. In case that there are multiples the first one that fits the trigger will be the one selected. In case none fits the ones in the theme will be checked after.
override_transition = { # A transition that can be shown when the event pops up, before the event options and backgrounds. This overrides the theme one. In case that there are multiples the first one that fits the trigger will be the one selected. In case none fits the ones inthe theme will be checked after.
trigger = {} # Receives the event scope to check if it's valid.
reference = "" # Path to the texture
}
override_effect_2d = { # A 2d effect that can be put on top of the background. This overrides the theme one. In case that there are multiples the first one that fits the trigger will be the one selected. In case none fits the ones in the theme will be checked after.
override_effect_2d = { # A 2d effect that can be put on top of the background. This overrides the theme one. In case that there are multiples the first one that fits the trigger will be the one selected. In case none fits the ones inthe theme will be checked after.
trigger = {} # Receives the event scope to check if it's valid.
reference = "" # key to the effect
}
override_icon = { # An icon that can be shown when the event pops up. This overrides the theme one. In case that there are multiples the first one that fits the trigger will be the one selected. In case none fits the ones in the theme will be checked after.
override_icon = { # An icon that can be shown when the event pops up. This overrides the theme one. In case that there are multiples the first one that fits the trigger will be the one selected. In case none fits the ones inthe theme will be checked after.
trigger = {} # Receives the event scope to check if it's valid.
reference = "" # Path to the texture
}
@ -234,11 +166,10 @@ my_events.1001 = {
trigger = {}
reference = ""
}
override_sound = { # A sound that can be played when the event pops up. This overrides the theme one. In case that there are multiples the first one that fits the trigger will be the one selected. In case none fits the ones in the theme will be checked after.
override_sound = { # A sound that can be played when the event pops up. This overrides the theme one. In case that there are multiples the first one that fits the trigger will be the one selected. In case none fits the ones inthe theme will be checked after.
trigger = {} # Receives the event scope to check if it's valid.
reference = "" # Reference of the sound
}
orphan = yes # The game will not log an error about this event being unreferenced. Useful for debug events
}
@ -258,139 +189,7 @@ Controller Type | Data Context Name | Notes
------------------------+----------------------------------------+-------------------------------------------------------------------------------------------------------------
default | EventWindowWidget | Default controller, no special behavior
name_character | EventWindowWidgetNameCharacter | Changes a character's name. Scope must have the name_character_target saved scope.
text | EventWindowWidgetEnterText | Saves some text onto the character. May use controller = { type = text data = { ... } } and setup_scope for text_target.
text | EventWindowWidgetEnterText | Saves some text onto the character.
event_chain_progress | EventWindowWidgetChainProgress | Displays progress through an event chain, needs event_chain_length and event_chain_progress scope values set
struggle_info | EventWindowCustomWidgetStruggleInfo | Displays information for the struggle, needs "start" scope value set
situation_info | EventWindowCustomWidgetSituationInfo | Displays information for the situation
=== Dynamic Description Appendix ===
Dynamic descriptions are supported by event `title`, `desc`, `opening`, and option `name` (with special notes for option names below).
They resolve localization keys at runtime using current event scopes.
High-level behavior:
- A plain localization key is valid:
- `desc = my_event.desc`
- A block is also valid:
- `desc = { ... }`
- In blocks, entries are evaluated in script order.
- Each selected piece is appended to the final text with automatic spacing between pieces.
- Missing localization keys are logged as errors.
Core block members (CDynamicDescription):
1) `desc`
- Appends text to output.
- Accepts either a key or another nested dynamic description block.
Example:
desc = {
desc = my_event.intro
desc = my_event.outro
}
2) `triggered_desc`
- Conditional description node.
- Body may only contain:
- `trigger = { ... }` (optional; if omitted, always valid)
- `desc = <loc_key>` or `desc = { ... }`
- If trigger passes, its `desc` content is appended.
Example:
triggered_desc = {
trigger = { has_trait = brave }
desc = my_event.brave_line
}
3) `first_valid`
- Checks child entries in order.
- Uses only the first child whose validity is true.
- Typical fallback pattern is placing a plain `desc = some_fallback_key` last.
Example:
first_valid = {
triggered_desc = {
trigger = { has_trait = brave }
desc = my_event.brave
}
triggered_desc = {
trigger = { has_trait = craven }
desc = my_event.craven
}
desc = my_event.fallback
}
4) `random_valid`
- Collects all valid children, then picks random entries from that set.
- Default picks: `count = 1`
- Optional: `count = N` picks up to N unique valid children (no replacement).
- If fewer than N are valid, all valid children are used.
- Chosen entries are appended by the random order.
Example:
random_valid = {
count = 2
desc = my_event.random_a
triggered_desc = {
trigger = { has_trait = patient }
desc = my_event.random_b
}
desc = my_event.random_c
}
5) `switch`
- Value-based branching for description selection.
- Structure:
- `trigger = <simple-assign trigger type>`
- `<case_value> = <loc_key or nested block>`
- `fallback = <loc_key or nested block>` (optional)
- First matching case is used.
- If no case matches and no `fallback` exists, nothing is appended.
- Case values are written as keys/tokens that match what the switch trigger returns.
Example:
switch = {
trigger = scope:rite_memory.var:rites_of_passage_type
flag:dueling_rite_memory = { desc = bp2_yearly.7029.desc_duel }
flag:scarification_rite_memory = { desc = bp2_yearly.7029.desc_scarification }
fallback = { desc = bp2_yearly.7029.desc }
}
Composition and nesting:
- Nodes can be nested arbitrarily (`desc` in `desc`, `first_valid` inside `random_valid`, etc.).
Option name specifics:
- Option names support two forms:
- `name = my_option_text_key`
- `name = { text = <loc_key or dynamic description block> trigger = { ... } }`
- `trigger` inside `name = { ... }` gates whether that name candidate is available.
- Multiple `name = { ... }` blocks are allowed per option.
- If more than one candidate is valid, one valid candidate is chosen randomly.
- If none are valid, first defined candidate is used as fallback.
- Important limitation: name candidates are separate sibling `name` entries; you cannot nest multiple `name` nodes inside one `name` block.
Known-good vanilla patterns to copy:
- Multi-chunk nested assembly with `random_valid`, `first_valid`, and `triggered_desc`:
- `game/events/board_game_events.txt` (e.g. event `board_games.0041`)
- `game/events/relations_events/adultery_events.txt` (e.g. event `adultery.4001`)
- Standard fallback chain in `first_valid`:
- `game/events/relations_events/adultery_events.txt` (e.g. event `adultery.1006`)
- Letter `opening` using dynamic desc block:
- `game/events/interaction_events/marriage_interaction_events.txt` (e.g. event `marriage_interaction.0001`)
- Dynamic description `switch`:
- `game/events/dlc/bp2/bp2_yearly_7.txt` (e.g. event `bp2_yearly.7029`)
Practical authoring checklist:
- Always include a safe fallback path (`desc` fallback in `first_valid`, or `fallback` in `switch`).
- Keep localization keys flat and explicit; nesting controls selection, loc does the prose.
- When building long descriptions, split into semantic chunks and compose with nested blocks.
- Prefer `first_valid` for deterministic branching and `random_valid` for variation.
- For option names, prefer multiple gated `name` entries over overly complex single-entry nesting.
COMMON GOTCHA WHEN BUILDING DYNAMIC DESCRIPTIONS, FOR INTERNAL DESIGNERS: If you're building a complex loc string that includes dialogue, it's common to end up with keys that contains an odd number of citation marks. In these cases you may need to escape the odd citation marks using double-backslash to pass the sanity checking git hook before you commit your work. E.g.:
my_cool_event_desc.intro: "The guy says, \\"Hey "
my_cool_event_desc.intro.friend: "friend"
my_cool_event_desc.intro.buddy: "buddy"
my_cool_event_desc.intro.idiot: "idiot"
my_cool_event_desc.intro.outro: ", watch where you're going!\\" I pay him no mind and continue on my way."