Event modding

Version

Please help with verifying or updating older sections of this article. At least some were last verified for version 3.6.

This article is for the PC version of Stellaris only.

## Event types

There are several types of events:

1. `event` – for an entire game (global event)
2. `country_event` – for an entire empire
3. `planet_event` – for a planet
4. `fleet_event` – for a fleet
5. `ship_event` – for a ship
6. `pop_faction_event` – for a faction
7. `pop_event` – for a population unit
8. `observer_event` – for the observer (development usage only; use console command "observe" to enter observer mode)
9. `system_event` – for a system, galactic\_object (since 3.0)
10. `starbase_event` – for a starbase (since 3.0)
11. `leader_event` – for a leader (since 3.0)
12. `espionage_operation_event` – for use inside espionage operations (since 3.0)
13. `first_contact_event` – for first contacts (since 3.0)
14. `agreement_event`  – for subject agreements (since 3.4)

## Basic Behavior

### Namespace and Event ID

The top of every event file must contain a namespace line. The namespace is used as the basis for identifying all events in the file. If you open the `on_action_events.txt` file near the top of the file you will find `namespace = action`. Then every event in this file has an ID that starts with `action` followed by a period then a unique number as shown here for the `actions.8` event:

```
country_event = {
	**id = action.8**
	hide_window = yes
	is_triggered_only = yes
	trigger = {
		is_country_type = default
		from = { is_country_type = default }
		NOT = { has_communications = from }
		is_hostile = from
	}
	immediate = {
		establish_communications = from
		fromfrom = {
			conquer = root
			set_controller = root
		}
	}
}

```

Anywhere in the game that needs to call this event will use the ID property.

An event file can have any number of namespace.

Event ID can't have letters, or it will be recognized as "namespace.0". Leading zeros in IDs are truncated (namespace.003 is the same as namespace.3).

### Execution

By default every event has all its triggers checked against every game object at least once per game day, possibly once per game tick. For the objects where the triggers are all met, the code of the event is executed with the scope of said object.

However, events should usually not be run in this fashion, as it is very expensive on performance. Instead, an event should either use `is_triggered_only = yes`, which will exempt the event from this persistent polling, and trigger the event from elsewhere; or it should use `mean_time_to_happen`, which increases the intervals at which the event is checked.

Similarly works `fire_only_once = yes`. However unlike `is_triggered_only` the code will still be polled, only to be removed after it was executed at least once. Also, as of 2.1.2, the conditions for a `fire_only_once` event should exclude the event firing a second time or error log entries are created.

### Condition

Events can have conditions represented with `trigger` section of Condition statements . If the condition is evaluated false, this event doesn't trigger. Here is an example from the event "apoc.1".

```
 trigger = {
 owner = {
 NOT = { has\_country\_flag = encountered_first_gateway }
 }
 from = {
 has\_star\_flag = abandoned_gateway
 any\_system\_megastructure = { is\_megastructure\_type = gateway_ruined }
 }
 }

```

#### Pre triggers

`pre_triggers` are fast triggers that are tested before full triggers, they are meant to be fast and quickly exclude target scopes from events, they take the form of "yes/no" question. Use this to improve performance related to events.
If an entry is not included it will be ignored. They work for planet, pop, system, starbase and leader scopes.

Possible **planet** values:

```
pre\_triggers = {
	has\_owner = yes				# whether the planet has an owner
	is\_homeworld = no			# whether the planet is its owner's homeworld
	original\_owner = yes		# whether the planet still belong to its original owner
	is\_ai = no					# whether the planet owner is controlled by the computer (vs controlled by a human)
	has\_ground\_combat = no		# whether ground combat is going on the planet
	is\_capital = no				# whether the planet is the capital world of the empire it belongs too
	is\_occupied\_flag = no		# whether the planet is occupied
}

```

Possible **pop** from jobs values:

```
possible\_pre\_triggers = {
	has\_owner = yes				# whether the pop planet has an owner
	is\_enslaved = no			# whether the pop is enslaved
	is\_being\_purged = no		# whether the pop is being purged
	is\_being\_assimilated = no	# whether the pop is being assimilated
	has\_planet = yes			# whether the pop is on a planet or not
	is\_sapient = yes			# whether the pop is sapient
}

```

`can_join_pre_triggers` in `pop_factions` accepts the same parameters.

system

```
 	has\_owner				# whether the system has an owned starbase
 	is\_capital				# whether the system has its owner's capital in it
 	is\_occupied\_flag		# whether the system is fully occupied by someone other than its owner, including all of its planets

```

starbase

```
 	has\_owner				# whether it has an owner
 	is\_occupied\_flag		# whether the controller is not the owner

```

leader

```
	has\_owner
	is\_idle					# whether the leader is assigned to a task

```

### Visibility

By default every Event will display a window and thus needs at least one option and a number of textfields to display. In order to suppress that window (and the need to set all the necessary text fields) `hide_window = yes` can be used. This is mainly used to trigger events that are used for achievements or events that the player should not know are happening.

### Code execution

The primary place for code is the `immediate` section of Effect statements. Most events have all their code contained inside this block. You can use if statement in `immediate` section, like the following, as seen in colony.events.txt.

```
 immediate = {
 	if = {
 		limit = {
 			event\_target:subterranean\_nation = {
 				NOT = { has\_country\_flag = tech\_request\_approved }
 			}
 		}
 		create\_army = {
 			name = "NAME\_Invading\_Horde"
 			owner = event\_target:subterranean\_nation
 			species = event\_target:subterranean\_species
 			type = "industrial\_army"
 			leader = last\_created\_leader
 		}
 	}
 }

```

However visible events need at least one option (the OK Button of a message box). Every option can carry its own code.

Scope is a very important consideration for any code executed in any event and it is heavily based on how the event was called.

### Mean Time To Happen

A additional condition very often used on event that triggered by regular polling, is the **mean\_time\_to\_happen** (MTTH). This will delay the execution of the event by a random amount of ingame days. On **average** the activation will be delayed as given, but the exact number can vary considerably. Counting starts the moment all other trigger conditions are met and it is not clear how exact the internal implementation of this delay works, however it seems likely the MTTH is regularly polled just like all other triggers.

* `mean_time_to_happen = { months = 5 }` or `mean_time_to_happen = { days = 15 }`

Available time units : days, months, years.

The MTTH can be modified with any number of conditional modifiers and it seems such things can take effect after the MTTH counting has begun.

Testing has shown that a simple MTTH will be called with a 50% chance during the period of time specified for each item in that scope e.g. each country for a country event. It will then check the triggers, and if they are true, the event will be triggered. However, if modifiers to the MTTH are introduced, the game will check the conditions every day for each scope – meaning that these are **considerably** more expensive in terms of performance.

**Warning: Due to poor performance, using MTTH has fallen out of favor, and vanilla has started to remove uses of MTTH, and is instead using events with random delays (see below).**

## Calling events from other events

It is possible to manually call an event onto any gameobject from a running event. Doing so will put the event into the game objects scope. There might be a slight delay of at least one or more game ticks until the event is actually called. For example:

```
 random\_galaxy\_planet = {
 planet\_event = { id = my\_planet\_event.1 }
 }

```

While doing this the triggering can be delayed by any fixed or semi-fixed timeframe (with optional DAYS and RANDOM delay).

For example: `country_event = { id = crisis.2000 days = 200 random = 100 }` delays it by 200–300 days.

As the event attempts to execute, if its conditions are not met, it doesn't execute. If the event is delayed, the conditions are checked only before execute, but not before queuing up.

Since 3.0 there is also an optional **`scope`** parameter, where you can override the calling scope: `scopes = { from = fromfrom }`.
Do note that when defining scopes, local event\_targets will not be properly scoped to in events firing from the event. For more see section *On Actions* below.

## On Actions

Aside from the default polling another very common way to get an event triggered is `on_action`. The vanilla game itself has a number of events registered this way in `Stellaris\common\on_actions\00_on_actions.txt`

Mods can provide their own, uniquely named, on\_action file whose events are called in addition the vanilla events. The file should be put into `Stellaris\common\on_actions\`, but the Modding Guidelines should be observed

The situations of triggering range from polling less aggressive (monthly or yearly) to numerous developments of the galaxy like ending of a planetary invasion, survey, entering of a system and so forth. This is later part is comparable to registering an Event in a GUI Environment of many higher programming languages.

Registered events should be marked with "triggered only" modifier. As easily dozens of events can be registered to any one development (often belonging to the same chain) the triggers decide which events are actually called.

A special case is random events which have a specific chance to trigger anytime that development happens. However, the triggers can make this a lot rarer than even chance might indicates.

Aside from "on\_actions", events can be triggered from any file with an effects field. Most notable are anomalies, but one can similarly trigger an event from e.g. within an edict (it will then trigger every time the edict is activated), policy, diplomatic action, etc.

Since 3.0 you can define your own "on\_actions" in script by effect `fire_on_action = { on_action = <string> scopes = { from = X fromfrom = Y } }`.
Do note that when defining scopes, fire\_on\_action appears to be considered a scope and all non-event\_target scopes require a prev added before them (`from = prev` will scope to the scope firing the on\_action, and to scope to the previous scope `from = prevprev` must be used instead).
In addition, event\_targets will not be properly scoped to in events firing from the on\_action if used for any scope above from (fromfrom, fromfromfrom, etc.).

## Conditional Description

The description of an event may change based on conditions. It uses the following syntax as seen in `colony_events.txt`.

```
 planet\_event = {
 	id = colony.182
 	title = "colony.182.name"
 	desc = {
 		trigger = {
 			owner = { NOT = { has\_authority = auth\_machine\_intelligence } }
 		}
 		text = colony.182.desc
 	}
 	desc = {
 		trigger = {
 			owner = { has\_authority = auth\_machine\_intelligence }
 		}
 		text = colony.182.desc.mach
 	}
 	…
 }

```

If multiple triggers match, only one of them will be shown at random. If none of the triggers match, the system will show the first description anyway.

If more than one desc entry is provided, only one of them will be shown at random (since 3.0).

So conditional description and static description can be used together. For instance:

```
 planet\_event = {
 	id = colony.182
 	title = "colony.182.name"
 	desc = {
 		trigger = {
 			owner = { NOT = { has\_authority = auth\_machine\_intelligence } }
 		}
 		text = colony.182.desc
 	}
 	desc = colony.182.desc.mach
 	…
 }

```

## Options

All visible events need at least one Option (comparable to the “OK” button of a message box). Options themselves have numerous variables that can be set.

The **after** block has to be written as a peer of the options, it is executed after an option is chosen, regardless which option was chosen. That makes it comparable to a finally block in many error handling systems of a language like Java.

**name** Defines the display name of the option. It is the only value somewhat mandatory. Often a localizable string is used here. There are numerous default strings that can be used here which are already localised.

*name* can also take a triggered block with multiple *name* blocks per *option'*

```
 option = {
 name = {
 trigger = { <display\_triggers> }
 text = <localization\_string>
 }
 name = {
 trigger = { <display\_triggers> }
 text = <localization\_string>
 }
 ...
 }

```

**trigger** Defines if the option is shown at all. If it’s not shown, so is it unchoosable.

**exclusive\_trigger** If valid, disables all other event options (added with 2.0).

**default\_hide\_option** If set to yes, this option is selected by default when a “Cancel” key is pressed (does NOT hide the option as the name suggests).

**icon** Defines an optional icon sprite.

**sound** Defines an optional sound-effect file played when the option is chosen.

**allow** Defines if the option is choosable. A option can be both shown and not choosable, often to show which options will become available with specific play. This subblock is comparable to the "Enabled" value or property in many GUI environments. The check is done only when the Event window is first shown and not updated with game progress.

**Effect statements** can be put into the body of the option. No special block must be put around it.

The game tries to generate a tooltip for this option based on all the effects. `custom_tooltip = xxx` can be used to show the player a tooltip in addition to the generated tooltips. Also, `hidden_effect = { … }` can be used to contain other effects to prevent the game from generating tooltips based on them.

```
 option = {
 name = "xxx"
 custom\_tooltip = "yyy"
 hidden\_effect = {
 (Effect statements to be executed when this event is chosen)
 }
 }

```

**ai\_chance** use this subblock to allow an AI to do a semi-random decision between all available options, to make some choices more likely to be picked by the AI. Here is an example from a War in Heaven event.

```
 ai\_chance = {
 factor = 100
 modifier = {
 factor = 0
 OR = {
 has\_valid\_civic = civic\_hive\_devouring\_swarm
 has\_valid\_civic = civic\_fanatic\_purifiers
 has\_valid\_civic = civic\_machine\_terminator
 }
 # Instead you can also use the corresponding scripted trigger: is\_homicidal = yes
 }
 }

```

The following options are available for diplomatic events (if `diplomatic = yes`):

**response\_text**: optional setting for an localisation key.
**is\_dialog\_only**: optional boolean setting that opens a text response only.
**custom\_gui**: optional setting that changes the looking of this field.
## Best Practices

### Triggering

Hide Window and Triggered Only are the two most common settings for events, with the bulk of vanilla events having either or even both of them. The MTTH is also a very common setting on any event that is using polling to add some randomness to the game where it seems useful.

Due to a possible massive CPU load load regular polling should be avoided unless absolutely necessary. The preferred way to get a series of events started initially is either via a gatekeeper event that does use regular polling with early failing triggers, or by having it triggered by a on\_action development. A combination of on\_action developments that lead to gatekeeper events may also be useful.

### Chaining Events, Scope, Execution Order

The vanilla code often has long chains of events calling one another indicating that this chaining might be necessary for proper execution and scope setting.

In particular a pattern where there is a hidden Event is calling a visible event is extremely common. The hidden event often doing actually (setup) work. This indicates that even "Immediate" code needs a least until the next event is called to take effect past any internal caching.

### Example Events

Visible Events (Robot Rebellion, 1.6.2 version; *actually removed*):

```
 # Servant AI Perfected
 country\_event = {
 	id = crisis.2192
 	title = crisis.2192.name
 	desc = crisis.2192.desc
 	picture = GFX\_evt\_robot\_assembly\_plant
 	show\_sound = event\_laboratory\_sound
 	location = root

 	is\_triggered\_only = yes

 	immediate = {
 		set\_country\_flag = robots\_pacified
 	}

 	option = {
 		name = crisis.2192.a
 		custom\_tooltip = crisis.2192.a.tooltip
 		hidden\_effect = {
 			set\_country\_flag = ai\_perfect\_servants
 		}
 	}

 	option = {
 		name = crisis.2192.b
 		hidden\_effect = {
 			country\_event = {
 				id = crisis.2000
 				days = 400
 				random = 400
 			}
 		}
 	}
 }

```

Invisible Events (test Prethoryn crisis – from version 1.8.3):

*# **WARNING:** May cause galactic mass extinction and/or loss of appetite*
country\_event = {
 **id** = crisis.199 *# Event ID*
**hide\_window** = yes *# Makes the event run without the player knowing, essential because this is a trigger event*

 **trigger** = { always = no } *# Makes event only trigger when called on (I think)*

 **immediate** = {
 set\_global\_flag = prethoryn\_invasion\_happened *# Global Flags are created to tell the game that the crisis is happening*
 set\_global\_flag = prethoryn\_transmission
 begin\_event\_chain = {
 event\_chain = "coming\_storm\_chain"
 target = ROOT
 }
 random\_rim\_system = {
 set\_star\_flag = swarm\_invasion\_target\_1
 save\_event\_target\_as = prethoryn\_invasion\_system
 }
 create\_point\_of\_interest = {
 id = coming\_storm\_poi.1
 name = "coming\_storm\_poi\_1\_poi"
 desc = "coming\_storm\_poi\_1\_poi\_desc"
 event\_chain = "coming\_storm\_chain"
 location = event\_target:prethoryn\_invasion\_system
 }
 country\_event = { id = crisis.17 days = 10 }
 }

}
## Example event with all parameters

*# Specifies the type of the event.*
**country\_event** = {
 *# Unique ID for your event. Must match namespace parameter*
**id** = example.1

 *# Specifies localization string for the title (Header) of the event*
**title** = example.1.name

 *# Specifies localization string for event text that describes what’s happening.*
*# Multiple is allowed; random one will be shown.*
**desc** = example.1.desc
 *# Descriptions can be conditional*
**desc** = {
 **text** = example.1.desc.conditional *# Localization string*
**trigger** = {
 *# Conditions for this to be available.*
*# For example: has\_authory = auth\_machine\_intelligence*
*# Similar to custom\_tooltip few sub commands can be used (also switch trigger)*
**text** = example.1.desc.conditional *# Shows custom text*
**success\_text** = *# Shows custom text when the associated trigger passes (multiple are possible and merged)*
**fail\_text** = *# Shows custom text when the associated trigger not passes*
 }
 *# If valid, disables all other desc (added with 2.1).*
**exclusive\_trigger** = {
 *# Condition statement(s)*
 }
 }

 *# A name of a picture to display. Pictures are defined at "interface/xxx.gfx".*
**picture** = GFX\_evt\_exploding\_ship
 *# Pictures can also be conditional*
**picture** = {
 **picture** = *# The name of a picture*
**trigger** = { *# Condition statement(s)* }
 **exclusive\_trigger** = {
 *# Condition statement(s) – if you have more than one*
 }
 }

 *# A scope to the object that is relevant to the event that player can move to. For example, the planet where event is happening.*
**location** = from

 *# Name of the sound clip to be played when event is shown. Sounds are defined in "sound/xxx.asset".*
**show\_sound** = event\_ship\_explosion

 *# If event is not meant to be seen or there’s no person to see. Makes title and desc unnecessary.*
*# Service event that runs some sort of routine or prepares grounds for other events should have this.*
**hide\_window** = yes

 *# Makes event look like diplomatic communications. First contact events use this.*
**diplomatic** = yes

 *# An optional setting that changes the looking of this event window, if "diplomatic = yes".*
**custom\_gui** = "enclave\_curator\_option"

 *# Specifies picture for diplomatic event. Most options are optional here*
**picture\_event\_data** = {
 *# Animated portrait. Accepts country, leader or species scope as an input*
**portrait** = event\_target:contact\_empire
 *# Planet background, if your picture has a window*
**planet\_background** = event\_target:contact\_empire
 *# City graphic type on the planet in the window*
**graphical\_culture** = event\_target:contact\_empire
 *# The size of the city. Usable to make planet behind look like capital*
**city\_level** = event\_target:contact\_empire
 *# Static background. Can use static pictures or scopes as the input*
**room** = event\_target:contact\_empire.ruler
 }

 *# Probably removed in v.3.8: Event will show for other countries if this is set to yes. Those countries can be narrowed down with major\_trigger.*
~~*major* = yes~~

 **auto\_select** = yes

 *# Force a diplomatic event to be viewed.*
**force\_open** = yes

 *# Forces the event to pop-up even if player has supressed pop-ups.*
**auto\_opens** = yes

 *# Makes the event happen only once per game*
**fire\_only\_once** = yes

 **trackable** = yes

 *# This event will not fire itself. It must be called by another event or an on\_action.*
*# Most events will use this.*
**is\_triggered\_only** = yes

 *# The event be considered for starting with daily probability calculated so on average it would happen in time specified.*
**mean\_time\_to\_happen** = {
 *# To specify average time for the event to fire. "months" or "days" are also acceptable input.*
 years = 100

 *# Mean time to happen can be conditionally modified*
**modifier** = {
 *# Multiply MTTH by number specify. Here it will make this event trigger in a mean time of 10 years.*
 factor = 0.1
 *# Condition statement(s)*
 }
 }

 *# If neither is\_triggered\_only or MTTH is set, the event will trigger every day the conditions are met.*
*# So don't forget them, lest you might affect the entire galaxy or see event trigger again and again forever.*

 *# Trigger block contains conditions. The event will not start if conditions inside aren't met.*
*# Used for events on mean\_time\_to\_happen or for events that called from situation where*
*# you can't or want specify conditions for it to happen, before calling*
*# (For example an event in on\_action block or a delayed event that might be blocked by other events)*
**trigger** = {
 *# Condition statement(s)*
 }

 *# Effects that are applied the moment event fires. Can be effects that can't wait like*
*# setting a flag to prevent other events, or the undesirable effects you don't want player to be able to delay*
*# (For example, killing science ship’s captain.) This is also the only block you'll need on hidden events*
**immediate** = {
 *# Effect statement(s)*
 }

 *# The button under the event, allowing player to pick a reaction.*
*# Any event that is not hidden, needs at least one. Multiple can be specified.*
**option** = {
 *# Reference to localization string with option text*
**name** = example.1.a

 *# If not met, this option will be disabled and hidden.*
**trigger** = {
 *# Condition statement(s)*
 }

 *# If valid, disables all other event options (added with 2.0).*
**exclusive\_trigger** = {
 *# Condition statement(s)*
 }

 *# If not met, this option will be disabled, but still shown.*
**allow** = {
 *# Condition statement(s)*
 }

 *# Effects of the event can be described here. They will generate tooltips shown when hovering over option.*

 *# This is a special Effect that it does nothing more than a customizable tooltip.*
*# Static strings can be used here, but using localisation keys is recommended.*
**custom\_tooltip** = example.1.a.tooltip

 *# This is a special Effect that it makes the game to generate tooltips based on the Effects inside.*
*# These statements have NO actual effects, just tooltips.*
**tooltip** = {
 *# effect statement(s)*
 }

 *# This is a special Effect that it prevents the game from generating tooltips based on the Effects inside.*
**hidden\_effect** = {
 *# effect statement(s)*
 }
 }

 *# Probably removed in v.3.8: Triggers for other countries on whether a major event should show for them. I.e. if it reads has\_ethic = ethic\_materialist, then all materialist countries will see the event.*
~~*major\_trigger* = {
 *# Condition statement(s)*
 }~~
*# Event will cancel (disappear without executing any of the effects in the options) if these triggers return true. Not evaluated while the game is paused.*
**abort\_trigger** = {
 *# Condition statement(s)*
 }
 *# Effects executed when abort\_trigger returns true.*
**abort\_effect** = {
 *# effect statement(s)*
 }

 **after** = {
 *# Effects that are applied after option is chosen.*
*# Will generate a tooltip in addition the tooltip of every option, unless hidden\_effect is used.*
 }

}
With v.3.4 there is a new “**event inheritance**” system added (e.g. it saved thousands of lines in the scripts for enclaves). With this, an event can be specified to inherit properties from another via “`base = <some_event_id>`”. Then, various properties can be overwritten via “`desc_clear`”, “`option_clear`”, “`picture_clear`” and “`show_sound_clear`”. Basically, this lets us inherit the behaviour of a given event while changing its flavour.
**Note:** the base event must be defined before.

## Empty Event Box Bug

Sometimes the game fills the screen with empty event boxes. They have no title, no text, no picture and a single button that says "OK". New events like that pop up every day rendering game unplayable. Normally this happens in a modded game.

### What is happening?

This bug is caused by a corrupted event file being loaded into the game. When game encounters a pair of **curly braces** in the wrong places, or doesn't find curly braces where they should be, the file becomes corrupted. The events after the syntax error still exist, however their code gets essentially hollowed out – parameters such as title, description and whatever is supposed to keep the event from firing daily on every single object in the game are not read.

Usually this is a result of a mod that hasn't been **updated to accommodate syntax** changes between versions – either by modders themselves or new file simply haven't made it to your machine.

### How to deal with it

First you need to find out what events are causing it. By using `debugtooltip` console command and hovering over "OK" button, you can find out the event’s id. With luck, the modder who made this event would add some kind of identification to help you, such as adding an acronym of their mod’s name, or their own.

You would need then to investigate if the mod is up to date. If it is not, you'd have to disable it and wait. If the problem is on your local machine, you'd need to force a re-download of the mod files – starting with unsubscribing and subscribing again.

**Note:** There is also a (Python) tool on GitHub, which can easy fix outdated syntax of a whole mod folder for you (>2.8 – created by FirePrince).

## References

1. ↑ Technically, you can place the namespace anywhere as long as the file is loaded before the namespace is used, but it’s absolutely not good practise.
2. ↑ 2.0 2.1 See *#Mean Time To Happen* for more details on both of these processes.
3. ↑ (ingame pre\_triggers docu "000\_added\_pre\_triggers\_to\_planet\_events.txt").
4. ↑ "possible\_pre\_triggers" to jobs, see "common/pop\_jobs/000\_pretriggers.txt"
5. ↑ 5.0 5.1 See *#Options* for details.
6. ↑ 6.0 6.1 This only applies if the event target is not global.
7. ↑ See *Event pictures* for more.
8. ↑ Forum: Stellaris Dev Diary #253 – Three in One 3.4 'Cepheus’ Patch Notes and more!, 2022-05-05

**Modding**