Scripting

From Crusader Kings II Wiki
Jump to navigation Jump to search

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 is the engine ability to display dynamically 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[edit]

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[edit]

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[edit]

Operators used to combine several conditions:

Operator Description Example
AND Returns true if all enclosed conditions return true. This is the default operator.
AND = {
  age = 15
  NOT = {
    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 (i.e default operator becomes OR rather than AND inside NOT), which can be confusing.

Thus for clarity NOR should be 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
  }
}
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
  }
}
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[edit]

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 less). 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 lesser 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

NOT = { ai_greed = 6 }

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 does not apply 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[edit]

If[edit]

If/limit 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[edit]

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 "if" did not match, and these other conditions matched
}
else_if = {
  limit = {
    #Additional conditions
  }
  #ELSE IF (2): Commands to execute if the previous "if" or "else_if" did not match, and these additional conditions matched
}
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 match
}
if = {
  limit = {
    NOT = { 
      #Conditions
    }
    #Other conditions
  }
  #ELSE IF: Commands to execute if the previous conditions did not match, and these other conditions matched
}
if = {
  limit = {
    NOT = { 
      #Conditions
      #Other conditions
    }
    #Additional conditions
  }
  #ELSE IF (2): Commands to execute if neither the previous conditions nor other conditions matched, and these additional conditions matched
}
if = {
  limit = {
    NOT = { 
      #Conditions
      #Other conditions
      #Additional conditions
    }
  }
  #ELSE: Commands to execute if none of the above conditions matched
}

Switch[edit]

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[edit]

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[edit]

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[edit]

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 No 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[edit]

Variables are numeric values attached to character, province, title, or global scope ( global variables added with patch 2.7 ). 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:

  set_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 = myvar_2 which = global_myvar } # Checks that global_variable's value is >= 2nd variable
}

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]:

  • 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_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_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
  • 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> }

Variables can be used in localization:[7]

  • [<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[edit]

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[edit]

The flag name may be dynamic, by appending @ and a scope (character, province or title):[8]

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).[9]

Note that scopes saved as variables in event targets can be used in dynamic flags as well.[10]

Earmarks[edit]

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[edit]

Scopes (character, province or title) can be saved as variables and re-used during an event chain, using:[11]

  • command save_event_target_as = <target_name> or save_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[edit]

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 persistent target takes precedence.

Timers[edit]

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[edit]

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 by set_<scope>_flag:
clr_character_flag = money_from_the_pope # Reset timer
set_character_flag = money_from_the_pope

Event modifiers as timers[edit]

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 or add_holding_modifier commands:
add_character_modifier = {
	name = lustful_affair_timer
	duration = 2190
	hidden = yes
}
  • Checking it via has_character_modifier, has_province_modifier or has_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[edit]

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 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

Random[edit]

random = {
  chance = <percentage>
  modifier = {
    factor = <factor_1>
    #Conditions for factor to apply
  }
  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[edit]

random_list = {
  <weight_A> = {
    modifier = {
      factor = <factor_1>
      #Conditions for factor to apply
    }
    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 = {
    modifier = {
      factor = 0
      religion = buddhist
    }
    give_nickname = nick_priest_hater
  }
}

Scope chain[edit]

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[edit]

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:

  • Events: the entity (character or province) who got the event. For on_action events, varies depending on the action. In some rare cases, the default scope (THIS at the beginning of the block) and ROOT are different: this happens for major = yes events.
  • Decisions: the decision taker.
  • Targeted decisions: the targeted character.
  • Objectives: the plotting character
  • Casus Belli: the character who has a case for war.
  • Buildings: the province.
  • job_actions: the councillor
FROM
  • Events: the entity (character or province) who sent the event. Only works if an event was triggered by another event/decision, to scope to that trigger. May be chained with FROMFROM to go up the chain of events (maximum 4 FROMs as of patch 2.3). For on_action events, varies depending on the action.
  • Targeted decisions: the decision taker.
  • Objectives: the target character of the plot
  • Casus Belli: the character who is being declared war on.
  • Buildings: the character (baron) holding the settlement. FROMFROM is the holding (barony title).
  • job_actions: the ruler
PREV Previous scope used in the chain.

May be chained with PREVPREV to go several scopes up the chain (maximum 4 PREVs).
Note: PREV, ROOT and FROM do count as a scope change.

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 to ROOT. The stacks looks like that:
ROOT		<- THIS
  • #2: owner is a scope, so THIS now points to the owner of the province. PREV points to ROOT (previous scope). The stacks looks like that:
owner		<- THIS
ROOT		<- PREV
  • #3 top_liege is a scope, so THIS points to the top liege of the owner of the province. PREV points to the owner, PREVPREV points to ROOT. 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 to ROOT.
  • #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[edit]

Comparison[edit]

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[edit]

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[edit]

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.

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[edit]

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[edit]

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
}

What this does is it 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).

Tooltips[edit]

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[edit]

Hides the entire evaluation of the effect from the player, typically the firing of an event.

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.

Custom tooltips[edit]

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 = runestone_carved }

These allow very complex trigger chains to be reduced to a simple message.

Conditional tooltips[edit]

Added in version 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[edit]

Triggers and effects may also be filtered for better readability.

show_scope_change (2.8.0+)[edit]

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+)[edit]

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 (2.8.0+)[edit]

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[edit]

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[edit]

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[edit]

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[edit]

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[edit]

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[edit]

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.

Differences from other languages[edit]

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 2.
  • 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[edit]

References[edit]