Scripting allows to modify dynamically the world and govern A.I. character behaviors. It is opposed to static history modding, though some scripts may exceptionally be triggered from history files.
Scripting is event-based, with Clausewitz engine being optimized to filter and process massive amounts of events on characters and provinces.
The structure is based on definitions of entities, that have:
- properties (or "flags")
- pre-defined functions (or "blocks") attached and called in a specific order by the engine, and containing scopes, conditions and commands.
A relatively unique characteristic of the engine is the ability to dynamically display a textual representation of those "blocks" in tooltips (each scope, condition and command being localized), for the player to preview the effects of an action before taking it or know why a decision isn't available, and without any extra work required by modders.
Basics
The scripts are plain .txt files, and use a JSON-like syntax:
- = separates key from a value
- { } is a structured/complex value
- # is the start of a comment
# Definition of entity_1 entity_1 = { property_1 = value1 property_2 = value2 block_1 = { # A condition block scope = { condition = yes } } block_2 = { # A command block scope = { command = yes } } } # Another entity entity_2 = { }
The grammar of allowed properties and blocks depend on the relative path of the file toward the base game directory:
- common/religions will contain .txt files where entities are religion groups and religion, with properties specific to religions (see religion modding).
- events will contain .txt files where entities are events, with properties and blocks specific to events (see event modding).
Value types
Most frequent value types are:
Type | Description |
---|---|
bool | Boolean value yes/no |
int | Integer value |
float | Floating point value |
date | YYYY.MM.dd format, starting from 1.1.1 .
Note: negative dates are not properly handled by the engine. |
string | String literal values, e.g. "Orsini". Quotes are only strictly needed when the value contains spaces, though recommended for readability. |
color | Color values either as RGB list { 255 255 255 } or hexadecimal 0xff0000 .
|
clause | A complex structure where parameters depend on the condition.
<condition> = { <parameter1> = <value1> <parameter2> = <value2> } |
ID | An ID referencing an entity defined in another file (culture, religion, culturegfx, localization key,...). Does not contain spaces (_ is typically used as word separator). It may be quoted, though it leads to confusion with string literals. |
Boolean operators
Operators used to combine several conditions:
Operator | Description | Example |
---|---|---|
AND | Returns true if all enclosed conditions return true. This is the default operator after a scope change. | AND = { age >= 15 age < 35 } |
OR | Returns true if at least one enclosed condition returns true | OR = { culture = norman culture = saxon } |
NOT | Returns true if the enclosed condition is false.
It behaves the same as NOR, but for clarity NOR is preferred in case of more than 1 condition. |
NOT = { trait = on_hajj } |
NOR | Returns true if none of the enclosed conditions return true. | NOR = { trait = seducer trait = seductress } which is equivalent to: NOT = { OR = { trait = seducer trait = seductress } } or alternately: AND = { NOT = { trait = seducer } NOT = { trait = seductress } } |
NAND | Returns true if at least one enclosed condition return false. | NAND = { culture = norman religion = catholic } which is equivalent to: NOT = { AND = { culture = norman religion = catholic } } or alternately: OR = { NOT = { culture = norman } NOT = { religion = catholic } } |
calc_true_if | Returns true if at least amount conditions return true.
|
calc_true_if = { amount >= 3 culture = swedish religion = catholic is_female = no is_adult = yes age >= 50 } |
Numeric operators
With patch 2.8 comparison operators have been generalized to all numeric triggers.
Operator | Comparison | Example | Pre-2.8 | Notes |
---|---|---|---|---|
= | Greater or equal | ai_greed = 5
|
ai_greed = 5
|
Backward-compatible greater equivalence (mandatory in version 2.7.2 or earlier). Contrary to expectation, this is not an exact comparison. ai_greed = 5 is read as "ai_greed is at least 5", not "ai_greed is equal to 5". When inside a NOT, it is read as "ai_greed is less than 5".
|
< | Less than | ai_greed < 5
|
NOT = { ai_greed = 5 }
|
Lesser unequivalence. True if value is less than numeral and not equal to numeral. |
> | Greater than | ai_greed > 5
|
ai_greed = 6
|
Greater unequivalence. True if value is greater than numeral, but not equal to numeral. Pre-2.8 equivalent works only for integers (otherwise, will need to check against the target number plus a very small floating point, such as scaled_wealth = 5.00001 ).
|
<= | Lesser or equal | ai_greed <= 5
|
NOT = { ai_greed = 6 }
|
Lesser equivalence. True if variable is less or equal to numeral. Pre-2.8 equivalent works only for integers (otherwise, will need to check against the target number plus a very small floating point, such as NOT = { scaled_wealth = 5.00001 } ).
|
>= | Greater or equal | ai_greed >= 5
|
ai_greed = 5
|
Greater equivalence. True if value is greater than or equal to numeral. Identical, but lexically superior, to = .
|
== | Equal | ai_greed == 5
|
ai_greed = 5
|
Exact comparison. True if and only if value equals numeral, and no other value. Pre-2.8 equivalent works only for integers (otherwise, will need to check against the target number plus a very small floating point, such as wealth = 5 NOT = { wealth = 5.00001 } ).
|
Warning: the above feature applies neither to the tier
nor among_most_powerful_vassals
triggers.
This is for backward compatibility reasons, since existing code might assume that "tier = duke" means equal and not greater-than-or-equal comparison. This would break multiple mods if they required a change to reflect the new operators.
Control flow statements
If
if
statements allow to execute commands only if certain conditions are met. The structure is:
if = { limit = { #Conditions } #Commands to execute if the conditions match }
Else_if/Else
In version 2.8, else_if
and else
statements were added.
if = { limit = { #Conditions } #IF: Commands to execute if the conditions match } else_if = { limit = { #Other conditions } #ELSE IF: Commands to execute if the previous <code>if</code> did not match, and these other conditions are met } else_if = { limit = { #Additional conditions } #ELSE IF (2): Commands to execute if the previous <code>if</code> and <code>else_if</code> did not match, and these additional conditions are met } else = { #ELSE: Commands to execute if none of the above limits passed }
In versions prior to 2.8, if/else_if/else
can still be simulated by repeating the conditions in an if
with a NOT
operator:
if = { limit = { #Conditions } #IF: Commands to execute if conditions are met } if = { limit = { NOT = { #Conditions } #Other conditions } #ELSE IF: Commands to execute if the previous conditions are not met, but these other conditions are met } if = { limit = { NOT = { #Conditions #Other conditions } #Additional conditions } #ELSE IF (2): Commands to execute if neither the previous conditions nor other conditions are met, but these additional conditions are met } if = { limit = { NOT = { #Conditions #Other conditions #Additional conditions } } #ELSE: Commands to execute if none of the above conditions are met }
Trigger If
Patch 3.0 added support for control flow statements in trigger clauses, expanding on condition_tooltip
. trigger_if
is an alias for conditional_tooltip
, which can be chained with trigger_else_if
and trigger_else
. Additionally, limit
can be used instead of trigger
, matching regular syntax for if
.
These are very useful in indicating control flow and intent in lengthy, complex triggers. Keep in mind that long chains of trigger_else_if
that require one of the limit
clauses to be true have to end with trigger_else
, since the entire chain is ignored and is thus always true if none of the limit
clauses are true.
Consider the following:
OR = { AND = { is_female = yes NOR = { religion_group has_religion_feature - religion_patriarchal } } AND = { is_female = no NOT = { has_religion_feature = religion_matriarchal } }
This can be written much clearer in the following way:
trigger_if = { limit = { is_female = yes } NOR = { religion_group = muslim has_religion_feature = religion_patriarchal } } trigger_else = { NOT = { has_religion_feature = religion_matriarchal } }
Switch
trigger_switch
was introduced in patch 2.4 and is a shortcut to multiple if/limit blocks, in case the ifs are on different values of the same condition, specified via on_trigger
.
It works with all triggers that have a non-complex right-hand-side argument and will try to do a normal evaluation during the on_triggers from the scope that the trigger_switch clause is residing in. If more than one value in on_triggers evaluate as true, it will still only execute the first in the list that evaluates to true.[1] If a fallback
value is present and no other value evaluated as true, then the fallback option will be executed.
Switch doesn't work with scripted triggers, as they are macros and thus are complex right-hand-side arguments.[2]
As of patch 2.5, it was not working with the region
trigger, due to a specific issue that will get fixed.[3]
Here are some examples:
trigger_switch = { on_trigger = religion_group christian = { FROM = { add_trait = sympathy_christendom } } muslim = { FROM = { add_trait = sympathy_islam } } pagan_group = { FROM = { add_trait = sympathy_pagans } } zoroastrian_group = { FROM = { add_trait = sympathy_zoroastrianism } } jewish_group = { FROM = { add_trait = sympathy_judaism } } indian_group = { FROM = { add_trait = sympathy_indian } } }
any_demesne_title = { trigger_switch = { on_trigger = title e_hre = { holder_scope = { ... } } e_byzantium = { holder_scope = { ... } } k_france = { holder_scope = { ... } } } }
any_realm_province = { trigger_switch = { on_trigger = province_id 31 = { owner = { ... } } 32 = { owner = { ... } } } }
# Gives 100 wealth to christians, 100 piety to muslims and 100 prestige to a character belonging to any other religion group trigger_switch = { on_trigger = religion_group christian = { wealth = 100 } muslim = { piety = 100 } fallback = { prestige = 100 } }
Break
break
was introduced with patch 2.3 and acts as a return statement, causing everything after it (in the main block: option, immediate, ...) to be ignored.[4] It avoids the need to repeat the conditions with NOT operator in another if/limit block, when commands are exclusive. The structure is:
if = { limit = { #Conditions } #Commands to execute if the conditions match break = yes } #Commands to execute if the conditions do not match
While
while
effect was added in patch 2.6[5], allowing for the implementation of while loops.
The syntax is:
while = { limit = { # conditions } # effects that are executed until the limit is no longer true. }
Here is an example:
set_variable = { which = count value = 20 } while = { limit = { check_variable = { which = count value >= 1 } } subtract_variable = { which = count value = 1 } wealth = 5 }
This will add 100 wealth to THIS, the current scoped character, as it will loop 20 times.
Notes:
- the
while
effect does not produce any tooltip due to the problems that would cause. - make sure you don't create an endless loop... Although there is a fail-safe at 100K loops.
In patch 2.8, an additional count
parameter was added which allows a fixed number of loops (i.e. a for loop), and does not require the use of variables. This provides much cleaner and more concise code if a fixed number of loops is desired.
while = { count = 20 wealth = 5 }
The above effect block will add 100 wealth to THIS, just like the previous example.
If the two methods are combined, the loop will exit either when the limit fails, or when the count is passed, whichever happens first.
set_variable = { which = myvar value = 25 } while = { count = 20 limit = { check_variable = { which = myvar value >= 1 } } subtract_variable = { which = myvar value = 1 } wealth = 5 }
Even though the above while loop "should" execute 25 times because of myvar, it will end after 20 loops because of the count. If myvar was set to 15 instead, it would execute only 15 times, and not 20 times, because the variable check would be false.
Storing information
Here is a summary of the possible ways to store information, depending on the type of scope that information is attached to:
Type of storage / Supported scopes |
Variable (numeric) | Flag (boolean + date) | Modifier (boolean + date) | Event target (scope reference) | Earmark (boolean) |
---|---|---|---|---|---|
Global scope | set_variable with 'global_' prefix in front of variable name |
set_global_flag
|
No | save_global_event_target_as
|
No |
Event chain | set_variable with 'local_' prefix in front of variable name |
No | No | save_event_target_as
|
No |
Character scope | set_variable
|
set_character_flag
|
add_character_modifier
|
save_persistent_event_target
|
No |
Province scope | set_variable
|
set_province_flag
|
add_province_modifier
|
save_persistent_event_target
|
No |
Title scope | set_variable
|
set_title_flag
|
No | save_persistent_event_target
|
No |
Holding scope | No | No | add_holding_modifier
|
No | No |
Dynasty | No | set_dynasty_flag
|
add_dynasty_modifier
|
No | No |
Bloodline scope | No | set_bloodline_flag or flags = {} in bloodline types.
|
No | No | No |
Unit scope | No | No | No | No | When using spawn_unit
|
Artifact scope | set_variable
|
set_artifact_flag or flags = {} in artifact definitions.
|
No | save_persistent_event_target
|
No |
Society scope | set_variable
|
set_flag
|
add_society_modifier
|
save_persistent_event_target
|
No |
Offmap power scope | set_variable
|
set_offmap_flag
|
No | save_persistent_event_target
|
No |
Religion scope | set_variable
|
set_flag
|
religion_authority
|
save_event_target_as
|
No |
Culture scope | set_variable
|
set_flag
|
No | save_event_target_as
|
No |
Variables
Variables are numeric values attached to character, province, title, global scope (added with patch 2.7) or event chains (added with patch 2.8). They have a range of −2,147,483.648 to 2,147,483.647, in steps of .001 (32 bit signed fixed-point, 3 decimal places).
Variable are persisted in saves, and though there is no command to clear variables, the increased file size is usually negligible. Variables set to 0 are not saved to file, and so will be cleared after reload.
Since patch 2.8, it is possible to check a variable with a trigger/condition, and to assign a variable to an effect/command.
set_variable = { which = myvar value = 5 } wealth = myvar #gives 5 wealth
Important: in versions prior to patch 2.8, scripting variables are only able to pull, not set, the other numeric variables used by the game (attributes, gold, piety, ...), i.e. it's not possible to assign the value of a variable to a trigger.
Variables are always stored/retrieve in the scope where the commands/triggers are executed. So for instance to create a variable myvar with value 1 in ROOT scope:
ROOT = { set_variable = { which = myvar value = 1 } }
Or to create a global variable, add the prefix 'global_' in front of the variable's name:
export_to_variable = { which = global_myvar value = 1 }
and to check for it:
ROOT = { check_variable = { which = myvar value >= 1 } } or <any_scope> = { check_variable = { which = global_myvar value >= 1 } }
Since the introduction of global_variables with patch 2.7, it is possible to compare two variables in separate scopes by setting the value of one of the variables to a global_variable, then comparing the global variable to any scoped variable as follows:
<any_scope> = { set_variable = { which = global_myvar which = myvar_1 } # Creates a global_variable with value of myvar_1 } <any_scope> = { check_variable = { which = global_myvar which >= myvar_2 } # Checks that global_variable's value is greater than or equal to myvar_2 }
It is also possible to count number of instances of a scope:
export_to_variable = { which = global_debug_count value = 0 } <any_scope> = { change_variable = { which = global_debug_count value = 1 # increase by 1 for every instance of scope } }
Support for event variables was added in patch 2.8, which are only accessible for the duration of an event chain. These are not bound to any scope and can be convenient when dealing with scopes that do not support variables, such as bloodlines. Save event variables by starting the name with 'local_'.
Variables can be assigned the value of certain triggers using the export_to_variable command
ROOT = { export_to_variable = { which = myvar value = stewardship } }
Variables which can be exported[6][7]:
- martial
- diplomacy
- intrigue
- stewardship
- learning
- base_health
- health
- demesne_efficiency
- decadence
- dynasty_realm_power
- fertility
- infamy
- mercenary_siphon_factor
- monthly_income
- plot_power
- population_factor
- relative_power_to_liege
- religion_authority
- revolt_risk
- scaled_wealth
- treasury/wealth
- yearly_income
- age
- day_of_birth
- month_of_birth
- year_of_birth
- ai_ambition
- ai_greed
- ai_honor
- ai_rationality
- ai_zeal
- among_most_powerful_vassals
- combat_rating
- demesne_garrison_size
- demesne_size
- health_traits
- imprisoned_days
- lifestyle_traits
- max_manpower
- num_fitting_characters_for_title
- num_of_baron_titles
- num_of_buildings
- num_of_children
- num_of_claims
- num_of_consorts
- num_of_count_titles
- num_of_count_titles_in_realm
- num_of_demesne_castles
- num_of_demesne_cities
- num_of_demesne_empty_provinces
- num_of_demesne_temples
- num_of_demesne_tribes
- num_of_duke_titles
- num_of_dynasty_members
- num_of_emperor_titles
- num_of_empty_holdings
- num_of_extra_landed_titles
- num_of_feuds
- num_of_friends
- num_of_holy_sites
- num_of_king_titles
- num_of_lovers
- num_of_max_settlements
- num_of_plot_backers
- num_of_prisoners
- num_of_rivals
- num_of_settlements
- num_of_subrealm_castles
- num_of_subrealm_cities
- num_of_subrealm_empty_provinces
- num_of_subrealm_temples
- num_of_subrealm_tribes
- num_of_spouses
- num_of_titles
- num_of_trade_posts
- num_of_traits
- num_of_unique_dynasty_vassals
- num_of_vassals
- over_max_demesne_size
- over_vassal_limit
- personality_traits
- piety
- population
- population_and_manpower
- prestige
- realm_diplomacy
- realm_intrigue
- realm_learning
- realm_martial
- realm_stewardship
- realm_levies
- realm_levies_plus_allies
- max_realm_levies
- realm_size
- republic_total_num_of_trade_posts
- ruled_years
- score
- unused_manpower
- retinue_points_max
- retinue_points_used
- retinue_points_free
- total_tax_value
- holding_tax_value
- dynastic_prestige
- society_currency
- day
- month
- year
- total_years_played
- holding_garrison
- holding_garrison_percent
- holding_raisable_levy
- holding_raisable_levy_percent
- holding_total_levy
- holding_total_levy_percent
Normally, the value is pulled from the current scope, but another scope can be set with the who
parameter.
ROOT = { export_to_variable = { which = myvar value = stewardship who = FROM } }
Commands on variables have a second parameter, which can be:
- A literal value:
(set|change|subtract|multiply|divide|modulo)_variable = { which = <variable_name> value = <value> }
- A reference to another variable in same scope:
(set|change|subtract|multiply|divide|modulo)_variable = { which = <variable_name> which = <another_variable_name> }
- A scope (in the form of ROOT/FROM/PREV/THIS) which contains a variable with the same name:
(set|change|subtract|multiply|divide|modulo)_variable = { which = <variable_name> which = <scope> }
Variables can be checked with usual superior or equal comparison, or with strict equality:
check_variable = { which = <variable_name> value = <value> }
is_variable_equal = { which = <variable_name> value = <value> }
As of patch 2.8, numerical operators are supported in check_variable
, on both numerical values and other variables.
Variables can be used in localization:[8]
[<variable_name>.GetName]
, ex:[Root.PrimaryTitle.test_var.GetName]
[<variable_name>.GetValue]
, ex:[Root.PrimaryTitle.test_var.GetValue]
The variables themselves are localized via a .csv entry whose key is the variable name:
myvar;My Variable;;;;;;;;;;;;;x
Any variable command will treat a previously undefined variable as having a value of 0.
Flags
Flags are boolean values (flag is either present or not), which can be attached to the following scopes: character, province, title, dynasty, or global.
The command to set or clear a flag are:
set_<scope>_flag = <flag_name>
clr_<scope>_flag = <flag_name>
Flags can be checked via conditions, for either the flag presence or the duration since the flag has been set:
has_<scope>_flag = <flag_name>
had_<scope>_flag = { flag = <flag_name> days = <duration> }
Note that modifiers are somewhat similar to flags, but in addition they alter the statistics of the scope they are attached to.
Dynamic flags
The flag name may be dynamic, by appending @ and a scope (character, province or title):[9]
It only affects how the actual flag name is created, but doesn't change how the flag is checked.
FROM = { set_character_flag = is_friend_of_@ROOT }
For example, if a character has a character ID of 140, the saved character flag will be is_friend_of_140
.
They can also be used in triggers: has_character_flag = is_friend_of_@FROM
Use of dynamic flags is currently only supported for set|clr_scope_flag commands and has|had_scope_flag conditions. They cannot be used in the flag line of the create_character command (flags used in instances other than those listed previously must be static).[10]
Note that scopes saved as variables in event targets can be used in dynamic flags as well.[11]
Earmarks
Event spawned units have a similar but more limited mechanism than flags, called earmarks.
An earmark is added when creating a unit via:
spawn_unit = { earmark = <earmark> }
It can then be tested on the owner of the unit:
has_earmarked_regiments = <earmark>
And finally to disband the unit with the given earmark:
disband_event_forces = <earmark>
Event targets
Scopes (character, province or title) can be saved as variables and re-used during an event chain, using:[12]
- command
save_event_target_as = <target_name>
orsave_global_event_target_as = <target_name>
- special scope
event_target:<target_name>
ROOT = { # Save current scope save_event_target_as = target_adulterer }
event_target:target_adulterer = { # Saved scope is restored add_character_modifier = {name = adulterer years = 10} }
Event targets can be used in the tooltip of the event in which they are saved (which is normally not possible, as tooltips are built before effects are executed).
Event targets are used in localization by referencing directly the variable name:
I have decided to punish my vassal [target_adulterer.GetFullName]
Persistent event targets
Persistent event targets work much like regular event targets, except they are tied to a scope (which can be a province, character, title, artifact, society, or offmap power), not to an event chain. That means that you can keep referencing it from different contexts long after the original event chain is over, such as from new events, society names, trait names (they are used in vanilla to store the name of the person responsible for the coronation in the "Crowned by X" traits). They are only cleared when purposely cleared, or when the holder scope (if it is a character) dies.
save_persistent_event_target = { name = name_of_target scope = event_target:my_character }
. Scope is the scope to save. It'll be saved in whatever the current scope is scoped to, assuming it supports persistent event targets. Relative scoping like ROOT, liege, and similar work as well.clear_persistent_event_target = name_of_target
. Removes the given target.persistent_event_target:name_of_target = { }
. Scopes to the given target.[name_of_target.SomeLocCommand]
. Scopes to the given target in localisation. In the case of collision with a regular event target, the non-persistent target takes precedence.
Timers
It is sometime needed to measure how much time has elapsed since an event occurred as a condition of another event.
There are 2 techniques for that: flags or event modifiers.
Flags as timers
Scripting flags keep a reference to when they were set.
- Setting a flag using
set_<scope>_flag
command:
set_character_flag = money_from_the_pope
- Using
had_<scope>_flag
triggers to check elapsed time in days:
had_character_flag = { flag = money_from_the_pope days = 730 }
- If needed, timer can be reset by using
clr_<scope>_flag
command followed byset_<scope>_flag
:
clr_character_flag = money_from_the_pope # Reset timer set_character_flag = money_from_the_pope
Event modifiers as timers
A modifier can act as a cooldown timer, and make the scripts simpler, but they are limited to characters, provinces and holdings.
- Setting a hidden modifier that will expire after a given time via
add_character_modifier
,add_province_modifier
oradd_holding_modifier
commands:
add_character_modifier = { name = lustful_affair_timer duration = 2190 hidden = yes }
- Checking it via
has_character_modifier
,has_province_modifier
orhas_holding_modifier
has_character_modifier = lustful_affair_timer
- Modifiers need to be defined in common\event_modifiers
lustful_affair_timer = { icon = 1 }
Note: hidden modifiers don't need localization as they are not displayed.
- Modifiers can also be stacked
add_holding_modifier = { name = nomad_population_boom years = 10 stacking = yes }
After you can check the quantity of this modifier
has_instances_of_holding_modifier = { modifier = nomad_population_boom amount = 2 }
You can also remove a quantity of this modifier
remove_holding_modifiers = { modifier = nomad_population_boom amount = 2 }
Randomness
There are two commands that can be used to generate a random result: random
and random_list
.
random_list
is similar to using several random
blocks, except it guarantees that only one block will execute, which is not the case with multiple random
blocks.
Since patch 2.3 both commands can be weighted with some mult_modifier = { }
blocks, that based on conditions, can:
- increase the chances if factor > 1
- decrease the chances if factor < 1
- annihilate the chances if factor = 0
Since patch 2.7 random_list
supports the use of trigger
. trigger
obsoletes the use of modifiers with factor 0 and requires no inverted logic. random
can be wrapped inside an if
block instead.
Random
random = { chance = <percentage> (from 0-100) mult_modifier = { factor = <factor_1> #Conditions for factor to apply } mult_modifier = { factor = <factor_2> #Conditions for factor to apply } #Commands to execute if the random check succeeded }
The actual chances (if both <factor_1> and <factor_2> conditions match) are: <percentage> * <factor_1> * <factor_2> / 100
Example:
random = { chance = 20 add_trait = stressed }
Random list
random_list = { <weight_A> = { mult_modifier = { factor = <factor_1> #Conditions for factor to apply } mult_modifier = { factor = <factor_2> #Conditions for factor to apply } #Commands for case A } <weight_B> = { modifier = { factor = <factor_3> #Conditions for factor to apply } #Commands for case B } ... fallback = { # Fallback commands are executed if no other option has a chance above 0 } }
The actual chances (in case all factor match) are:
- Total = <weight_A> * <factor_1> * <factor_2> + <weight_B> * <factor_3> + ...
- A chances: <weight_A> * <factor_1> * <factor_2> / Total
- B chances: <weight_B> * <factor_3> / Total
- Fallback: if all other options have 0 weight
Example:
random_list = { 10 = { give_nickname = nick_the_wise } 10 = { give_nickname = nick_the_able } 10 = { trigger = { NOT = { religion = buddhist } } give_nickname = nick_priest_hater } }
In the example below, if the player has Holy Fury installed there will be a 50/50 chance of either option being chosen. If the player does not have Holy Fury installed then only the first option will be chosen.
random_list = { 50 = { set_graphical_culture = horse culture = horse } 50 = { trigger = { has_dlc = "Holy Fury" } set_graphical_culture = hedgehog_culture culture = hedgehog_culture } }
Scope chain
To understand scope chains, imagine that every time the game engine encounters a scope while processing a script block, the scope will be put on the top of a stack. Once the triggers, commands or nested scopes in that scope have been evaluated, the current scope is removed from that stack, and game engine continues with processing the parent scope.
Note that the scope chain is different from the event chain: each time an effect that triggers an event (like character_event
) is executed inside an event or decision, the base scope of that event (i.e. the caller) is added to the event chain, so that the new event (i.e the callee) may reference it.
Special scopes
When nesting scopes, special variables are used to reference other scopes in the chain. These values cannot be changed.
Variable | Description |
---|---|
ROOT |
Original and lowest scope in the chain:
|
FROM |
|
ROOT_FROM |
ROOT_FROM is a shortcut for Since FROM is relative, using |
PREV | Previous scope used in the chain. May be chained with PREVPREV to go several scopes up the chain (maximum 4 PREVs). |
THIS | Current scope. Used in conditions/commands that work against a scope, as that argument is mandatory: any_realm_character = { ROOT = { is_liege_of = THIS } } Note that using ROOT and FROM scopes do not change the scope of THIS. |
For instance imagine the following province event:
province_event = { ... trigger = { #1 owner = { #2 top_liege = { #3 culture = PREV } #4 NOT = { #5 culture = ROOT } } } ... }
In order of evaluation, at position:
- #1:
trigger
is not a scope, but the beginning of script block (aka function).ROOT
is referencing the province for which the event is being evaluated.THIS
points toROOT
. The stacks looks like that:
ROOT <- THIS
- #2:
owner
is a scope, soTHIS
now points to the owner of the province.PREV
points toROOT
(previous scope). The stacks looks like that:
owner <- THIS ROOT <- PREV
- #3
top_liege
is a scope, soTHIS
points to the top liege of the owner of the province.PREV
points to the owner,PREVPREV
points toROOT
. So here it is comparing the culture of the top liege with the culture of the owner. The stacks looks like:
top_liege <- THIS owner <- PREV ROOT <- PREVPREV
- #4 Previous block is finished, so THIS is now pointing again to the owner of the province,
PREV
points toROOT
. - #5
NOT
is an operator, not a scope. Scope chain remains unchanged. So here it is comparing the culture of the owner with the culture of the province.
Scopes and conditions
Comparison
Scopes cannot be compared directly for equality: for instance primary_title = k_england
is invalid. The appropriate condition must be used:
Characters | Titles | Provinces |
---|---|---|
current_heir = { character = PREV } |
primary_title = { title = k_england } |
location = { province = 272 # Akershus } |
Checking if a scope exists
When a scope is used in a condition block, always = yes
condition can be used to check if anything was matched by the scope:
trigger = { #Will evaluate to true if scope is not empty scope = { always = yes } }
For instance to check a character has a mother: mother_even_if_dead = { always = yes }
.
Count
When a scope is used in a condition block, count = N
condition will cause the scope to evaluate to true, if at least N elements are matched in the scope.
As of patch 2.3 any scopes starting with any_
can be used with count.
As of patch 2.8 count supports numerical operators in most any_
scopes.
trigger = { #Will evaluate to true if at least N elements in the scope match the conditions scope = { #Conditions count >= N } }
For instance to check if a ruler has at least 7 dwarf courtier at his court:
any_courtier = { count >= 7 trait = dwarf }
Limit
Limit can be used to further reduce what is matched by a multi-variant scope (any_
, random_
), when a scope is used in command block:
scope = { limit = { #Conditions on elements of the scope } #Commands to execute for each element of the scope that has been matched }
For instance to scope only other rulers of same religion in the realm and fire some event to them:
any_realm_lord = { limit = { religion = ROOT } character_event = { id = xxx } }
A frequent modding mistake is to try using limit inside a trigger block, which is not only unnecessary, but will also break the event.
Preferred Limit
In Patch 3.0, the new preferred limit syntax was added to the game, which can be used in any random_
scope to help specify multiple layers of specificity when choosing a random target.
For example, say you want to find the strongest vassal in your realm who is ambitious. You cannot directly sort a list of vassals by strength and pick the top one who is ambitious, without an overly complex system of pinging events and variables, but you can have a good approximation using preferred limits.
random_vassal = { limit = { trait = ambitious } preferred_limit = { is_powerful_vassal = yes } preferred_limit = { OR = { tier = KING tier = DUKE } } preferred_limit = { tier = COUNT } #Commands }
This looks for a random vassal, who follows the regular limit (is ambitious), but first looks amongst those who meet the first preferred limit (is powerful vassal). If it can find an ambitious powerful vassal, it will execute the commands and call it a day - otherwise, it will look among the king-tier and duke-tier vassals for one who is ambitious. And if there is no ambitious king or duke vassal, it will look among the count-tier vassals. If none of the preferred scopes are met, then it will disregard them all and look for any vassal who is ambitious (who will be a baron in this case, by process of elimination).
Score Value
Patch 3.0 added a score selection to any_
effect scopes, used in conjunction with count
. Rather than executing effects on random matching elements, the highest scoring elements are selected instead. This can be combined with limit
, but not preferred_limit
, which is exclusive to random_
scopes.
any_society_member = { score_value = { value = 1 additive_modifier = { society_rank == 4 value = 1000 } additive_modifier = { society_rank == 3 value = 100 } } count = 3 add_trait = maimed }
Tooltips
In triggers and effects, tooltips are automatically generated by the Clausewitz engine in order to provide a detailed summary of what will occur when the player chooses an option. For instance, when the user hovers their mouse over a decision in the Intrigue menu, a detailed list of tooltips showing the conditions that allow them to take that decision will be automatically generated by the game, allowing the player to see at a glance what changes are needed before the decision becomes available -- or why they can choose that decision now, if they already meet all the conditions. Likewise, when the player moves their mouse over one of the buttons in an event, the list of results is automatically generated from the effects contained in that option.
These automatically generated tooltips usually range from helpful to very helpful, but there are rare occasions (especially with highly complex scripting) where they may produce tooltips that are unnecessarily technical or cluttered, are improperly localised, or even state the opposite of what they actually do. To correct this, the mod author is able to override these automatically generated tooltips with custom tooltips of their own.
Three special clauses can be used to change the tooltip displayed for an effect or condition:
Hidden tooltips
Hides the entire evaluation of an effect or trigger from the player, typically the firing of an event, or conditions that only AI has to satisfy.
hidden_tooltip = { province_event = { id = CM.1106 } }
Hidden tooltips are very useful for decisions and events if the actual results are to be hidden from the player for dramatic effect, or if the automatically generated tooltips produce unnecessarily technical details (such as the setting and modification of variables).
Hidden tooltips can also be used in triggers, but depending on the specific context it may be very confusing for the player if a decision satisfies all of its visible conditions, but fails a hidden one; a custom_tooltip
is preferable in this case.
In 2.6.2 hidden_effect
and hidden_trigger
aliases were added, which can be used to reduce ambiguity.
Custom tooltips
Custom tooltips simplify some complex or hard to read triggers or effects, by replacing the tooltip content with the contents of a localisation key.
custom_tooltip = { text = pagan_subjugation_tip attacker = { subjugate_or_take_under_title = { # If the target only has territory within the kingdom, he is simply vassalized title = PREV enemy = defender } } }
custom_tooltip = { text = UNOCCUPIED_DEMESNE_TITLE any_demesne_title = { NOT = { higher_tier_than = count } is_occupied = no } }
These allow very complex trigger chains to be reduced to a simple message.
custom_tooltip
can also be used to display a message without hiding effects or triggers:
custom_tooltip = { text = disables_centralization_1 }
Conditional tooltips
Added in version patch 2.8, these display the tooltip conditions only if the included trigger evaluates successfully.
conditional_tooltip = { trigger = { liege = { independent = no } } liege = { liege = { will_liege_enforce_peace = no has_liege_enforced_peace = no } } }
In the above example, the liege's realm peace conditions are only shown if the liege is not independent (as if the liege were independent, this would produce a strange-looking tooltip when it scopes to the non-existent liege of the liege).
This can also be used to rule out massive, ugly trigger chains for a better user experience -- for instance, when checking the tier or society membership of the ruler, and then checking specific conditions for each tier or society. Each specific block could check to see if the character is of that tier or a member of that society, therefore showing only the relevant conditions.
Tooltip filtering
Triggers and effects may also be filtered for better readability.
show_scope_change (patch 2.8)
trigger = { location = { show_scope_change = no owner = { show_scope_change = no opinion = { who = ROOT value = 50 } } } }
The above example scopes to the owner of the character's current location to check their opinion of the root character. By default, this would produce an ugly chain of your current location, followed by a blank line (the "owner" scope is not localised in the game), followed by the correct and intended line. With the show_scope_change = no limiter, only the single intended line would be shown.
show_only_failed_conditions (2.8.1)
trigger = { show_only_failed_conditions = yes always = yes character = ROOT is_vassal_of = FROM }
This feature applies to triggers only. In the above example, "always = yes" and "character = ROOT" will always be true. The only condition that can be true or false is whether the "is_vassal_of = FROM" (is FROM the vassal of ROOT?). With show_only_failed_conditions = yes, the "always" and "character" conditions will not be shown. This is useful when there are a lot of edge cases that need to be handled and the vast majority of the test cases will be true most of the time.
This feature is used heavily in the Jade Dragon decisions to show only why China will reject a given proposal, without showing all of the obvious reasons why they would accept it.
generate_tooltip (patch 2.8)
hidden_tooltip = { generate_tooltip = no any_realm_character = { limit = { controls_religion = no religion_group = ROOT is_landed = yes NOT = { character = ROOT } } opinion = { who = ROOT modifier = opinion_declared_unjust_conquest years = 10 } } }
Even though the hidden_tooltip
will hide the tooltips, it will still perform all of the necessary localisation processing of the tooltips. The generate_tooltip = no parameter suppresses this behaviour. Note that this will break randomisation and suppress the assignment of the localisation of event_targets and other similar behaviour. This feature is used in vanilla to suppress the processing of complex "any_" scopes for localisation when the localisation won't be shown to the player (in, e.g., casus belli).
Combining tooltips
It is possible to use a conditional_tooltip
to hide a custom_tooltip
. You may surround a series of custom tooltips in an over-arching conditional tooltip.
A custom_tooltip
will hide all of the automatically-generated tooltips in its block, replacing it with the localised text. Therefore, it is not necessary (but not harmful) to use a hidden_tooltip
inside a custom_tooltip
, as the custom tooltip will already hide its contents and display only the localised text, with a (*) or (x) based on its evaluated value.
Likewise, surrounding either a custom_tooltip
or conditional_tooltip
with a hidden_tooltip
will simply hide everything inside, rendering them unnecessary.
Notes
Tooltips can seem to be wrong when an effect both sets and reads a flag or updates and reads a variable in the same block. This is because to display the tooltip, the triggers in option block are executed, but not the commands (they will only when the option is actually chosen), so it may be inconsistent. Here is an example where tooltip cannot reflect what will actually happen:
option = { set_character_flag = flag_do_something # Not executed to calculate tooltip if = { limit = { has_character_flag = flag_do_something } # Executed to calculate tooltip: will be false # Do it } }
To resolve this, use custom_tooltip
. For event options, consider moving the commands that modify flags or variables up from the option
block into an immediate
block.
Scripted block
A scripted trigger (or scripted effect) is a macro, comprised of conditions (respectively commands) that can be executed in a single batch.
It allows reusing common scripts throughout a mod, instead of copying and pasting that code wherever it is used (and having to remember to update each separate use every time). On the other hand, over-use of scripted triggers or scripted effects can lead to spaghetti code. Unless you are re-using the same code over and over and it is growing unnecessarily large in file size or unwieldy to maintain, it is usually preferable to keep triggers or effects where they are used instead of moving them to a scripted trigger or effect.
Definition
Scripted blocks are defined in any filename in the \common\scripted_effects\
or \common\scripted_triggers\
folders, using the extremely simple syntax:
scripted_block_name = { #commands }
Thereafter, the scripted block can be called in your code like any other trigger or command, using the format: scripted_block_name = yes
It is always best practice to include a proper naming convention for your scripted blocks to avoid overlap with other mods, and never to overwrite the original vanilla files, as there is no reason to do so: simply define a new file for your own scripted blocks. Also use a prefix or suffix that identifies that your trigger/command is a scripted trigger/effect, and not built-in: mymod_helloworld_effect = yes #Paradox naming convention fn_mymod_helloworld = yes #Other naming convention
It is important to use unique names. For instance, if you have a trait that has the same name as an effect block, it will not be parsed correctly if you refer to that trait within the block.
Scopes
The scopes of the scripted blocks are always equal to the context in which the scripted block is called—THIS, ROOT, FROM, PREV, etc. will always be assigned to the same as where the scripted effect was originally used. Be extremely careful to use your scripted block in the correct scope it was created for, including the entire scope chain, or else portions of the code may fail silently. While this is unlikely to crash anything, it can result in silent failures and logic errors and can be a source of frustration because of the degree of separation between the file where the scripted block is being used and the file where the scripted block is defined—code that appears correct in either file may not be correct if the scopes are different than intended.
Example
The following scripted effect removes all "vanilla" education traits from the character:
- SCOPE: THIS = Character
fn_remove_education_traits = { #DIPLOMACY remove_trait = naive_appeaser remove_trait = underhanded_rogue remove_trait = charismatic_negotiator remove_trait = grey_eminence #MARTIAL remove_trait = misguided_warrior remove_trait = tough_soldier remove_trait = skilled_tactician remove_trait = brilliant_strategist #STEWARDSHIP remove_trait = indulgent_wastrel remove_trait = thrifty_clerk remove_trait = fortune_builder remove_trait = midas_touched #INTRIGUE remove_trait = amateurish_plotter remove_trait = flamboyant_schemer remove_trait = intricate_webweaver remove_trait = elusive_shadow #LEARNING remove_trait = detached_priest remove_trait = martial_cleric remove_trait = scholarly_theologian remove_trait = mastermind_theologian }
Then it can be called in any effect block as if it were a command included in the base game:
character_event = { id = ''mymod''.1 desc = "I can't help but feel like I'm forgetting something..." trigger = { trait = imbecile } mean_time_to_happen = { years = 800 } option = { name = "Damn this amnesia!" ''#Remove all education traits from THIS (character)'' '''fn_remove_education_traits = yes''' } }
It is strongly recommended to document the scopes which a scripted block requires, in a comment at the beginning of that scripted block.
Scripted blocks are known to work in events, decisions, disease definitions, and character history.
If your scripted trigger uses a custom tooltip then it is best to use NOT = { scripted_trigger = yes } instead of scripted_trigger = no otherwise the custom tooltip will not be inverted correctly like automatically generated tooltips from normal conditions are.
Usage
Scripted blocks are intended to make code easier to read and maintain by placing conditions or effects in a single location of a mod.
If you have a single set of conditions being tested for repeatedly within a mod or have an effect being used frequently then it's recommended to use a scripted block to be able to easily modify and adjust behaviour. The use of scripted blocks also helps reduce the number of repeated lines from conditions or effects.
There's a great number of pre-made triggers and effects within the vanilla code and be aware that they can be used freely within your own mods.
Differences from other languages
Modders skilled with other programming languages need to be aware of the following quirks of scripted blocks:
- There are no parameters or other variables that can be passed to a function; the argument is always the singular boolean "yes". This can be worked around by setting variables prior to calling the function, and then checking those variables inside the function, bearing in mind the limited support for variables in Crusader Kings II.
- The behaviour of the function depends on the context from which the function is called. The function does not create a new context unless it explicitly includes a new scope. Always ensure that the function is called from the correct context.
- Functions cannot return a value—they behave as "void" functions. To "return" a value, set a flag (e.g.,
set_character_flag
) or set a variable (i.e.,set_variable
) inside the function. Remember that the function takes in the same context as the place it is called, so all variables assigned will be saved to the calling scope and accessible outside the function. - Scripted blocks are more like non-hygienic macros than proper functions (similar to batch files and other context-dependent macros).
See also
References
- ↑ forum:597480/page-110#post-20878292
- ↑ forum:874495/page-21#post-20889305
- ↑ forum:597480/page-110#post-20879441
- ↑ Forum:820483
- ↑ forum:774248/page-30#post-21507013
- ↑ forum:606906/page-123#post-23434440
- ↑ forum:1163658/#post-25303812
- ↑ Forum:774248
- ↑ forum:774248/page-20#post-20109979
- ↑ forum:589686/page-1707#post-24382261
- ↑ forum:589686/page-1633#post-23624865
- ↑ Forum:800491
历史 | 角色 • 家族 • 省份 • 头衔 • 剧本 |
脚本 | 指令 • 条件 • 作用域 • 修正 • 事件 • 决议 |
常规 | 定义 • 游戏规则 • 另类开局 • 宗教 • 文化 • 政体 • 特质 • 血脉 • 科技 • 法律 • 建筑 • 宣战理由 • 朝贡国 • 单位 • 目标 • 疾病 • 死亡 • 荣誉头衔 • 社团 • 宝物 • 地图外政权 • 内阁成员 • 贸易路线 • 继承 • 奇观 • 称号 |
图像/音效/本地化 | 地图 • 图形 • 盾徽 • 肖像 • 界面 • 小地图 • 音乐 • 本地化 |
其他 | 故障排除 • 验证器 • 控制台指令 • 编辑游戏存档 • Steam创意工坊 • EU4转档器模组制作 |