Action Files
Action Files
Section titled “Action Files”Every action is one HOCON file in config/showdown_actions/actions/. The filename without the .conf extension is the action’s id by default; an optional id = "..." field at the top level overrides it.
A complete action file is up to four blocks:
# Top-level fields - id, displayName, enabled, priority, target, chance, oncePerBattle, permission, mutexGroupid = "..."priority = 0target = "SELF"
# What gates the action - sugar fields + optional whenMolangmatch { ...}
# What happens when the gate passes - sugar effectsapply { ...}
# Optional tier-3 escape hatch - raw Showdown JS hooksshowdown { ...}Of those, only id and one of apply {} / showdown {} are strictly required to be useful. An action with no match clause matches every Pokémon in every battle (which is rare but legal). An action with an empty apply and no showdown is a no-op — useful only as a debug placeholder for testing predicates.
The Three Tiers
Section titled “The Three Tiers”ShowdownActions is built around three escalating tiers of authoring power. The tier you’re at is determined by which fields you populate, not by any explicit “tier” switch. You can mix tiers freely in a single action — e.g. tier-1 sugar effects gated by a tier-2 Molang predicate.
Tier 1 — Sugar (no scripting)
Section titled “Tier 1 — Sugar (no scripting)”Fill in match and apply with predefined sugar fields. The gate becomes a set of property checks plus (where needed) a Molang predicate, and the effect becomes an auto-applied Showdown condition with an onStart hook.
id = "lycanroc_dusk"displayName = "Lycanroc - Dusk Form"target = "SELF"
match { species = "lycanroc" sugar { time = "dusk" }}
apply { aspect = "dusk" boosts { spe = 1, atk = 1 } message = "{name} resonates with the dusk light!"}This covers most cases. See Match Clause and Apply Clause for every sugar field.
Tier 2 — Raw Molang predicates
Section titled “Tier 2 — Raw Molang predicates”When the sugar gates don’t compose into the condition you want, set whenMolang to any expression Cobblemon’s Molang runtime can evaluate. The expression is ANDed with whatever sugar fields you also populated — you don’t have to choose between them.
id = "rainy_swift_swim"match { ability = "swiftswim" whenMolang = "q.world.is_raining && q.pokemon.level >= 30"}apply { boosts { spe = 2 }}The runtime binds q.pokemon, q.player, q.world, and q.battle for you. Read the Molang Predicates page for the full surface, including the helpers ShowdownActions registers under q.world.* (is_time, dimension_string, etc.).
Tier 3 — Raw Showdown JS
Section titled “Tier 3 — Raw Showdown JS”When sugar can’t reach the effect (multi-handler conditions, custom turn logic, anything that depends on Showdown’s internal API), drop a showdown { } block. The hooks register straight into Dex.data.Conditions[…] — the same place Showdown stores its own built-in conditions.
id = "custom_intimidate"match { species = "gyarados" }
showdown { conditionId = "showdownactionsgyaradosintimidate" conditionName = "Sigil Intimidate" scope = "volatile" hooks { onStart = """function(pokemon) { var foe = pokemon.side.foe.active[0]; if (foe && !foe.fainted) { this.boost({atk: -2}, foe, pokemon); this.add('-message', pokemon.name + ' glares menacingly at ' + foe.name + '!'); } }""" }}Any Showdown handler is supported — onStart, onModifyAtk, onAnyDamage, onResidual, onTryHit, onAfterMoveSecondary, the lot. The hook name goes straight in as a map key under hooks { } and is passed verbatim to Showdown. See Raw Showdown Hooks for the catalogue and Showdown Research for guidance on learning the engine well enough to write them.
When a showdown {} block is present, ShowdownActions does not auto-generate a species/form guard around your hooks. You have full control of what fires, and you’re responsible for adding identity checks if your hook needs them.
Top-Level Fields
Section titled “Top-Level Fields”These fields apply to the action as a whole, regardless of tier.
| Field | Type | Default | Purpose |
|---|---|---|---|
id | String | filename without .conf | Stable identifier. Used by /showdownactions info <id> and command suggestions. |
displayName | String | "" | Free-form label shown in admin GUIs/logs and as the condition’s name in Showdown. No behavioral effect. |
enabled | Boolean | true | When false, the file is loaded but skipped at every match attempt. Treat false as “comment out without deleting”. |
priority | Int | 0 | Higher priority is evaluated first when several actions match the same (actor, pokemon). Mostly relevant inside a mutexGroup. |
target | enum | SELF | Which side+slot the resulting Showdown effect is registered against. See Targeting. |
chance | Double | 1.0 | Probability gate in [0, 1]. Rolled once per battle, before the effect is registered. 1.0 always fires. |
oncePerBattle | Boolean | false | Fire at most once per battle, regardless of how many times the matched Pokémon switches in. |
permission | String? | null | Optional permission node. When set, the matching player must satisfy it (via fabric-permissions-api) for the action to fire. |
mutexGroup | String? | null | Mutual-exclusion key. Within one battle and trainer, only the highest-priority matching action whose mutexGroup equals this string fires; the rest are dropped. Use it to swap form variants without writing negative predicates. |
mutexGroup in practice
Section titled “mutexGroup in practice”The canonical use case is form rotation. Three Lycanroc variants — Midday, Dusk, Midnight — with mutexGroup = "lycanroc_form" and different sugar.time predicates. Whichever matches the current time wins the group; the others are silently dropped, even though they’re also valid actions.
# Three files, all with mutexGroup = "lycanroc_form"# - lycanroc_midday.conf: sugar.time = "day", priority = 5# - lycanroc_dusk.conf: sugar.time = "dusk", priority = 5# - lycanroc_midnight.conf: sugar.time = "night", priority = 5Mutex resolution happens after chance and permission gates pass. Dropped losers are logged at trace level when debug = true.
File Loading and Reload Behavior
Section titled “File Loading and Reload Behavior”ShowdownActions loads action files when the server finishes starting. The first time the server starts with the mod installed, the bundled 00_bible.conf, example_*.conf, recipe_*.conf, and a few working samples are copied into actions/ (only if actions/ is empty). On subsequent starts, your edits are preserved.
/showdownactions reload re-runs the same pipeline:
- Re-parses
config.conf. Thedebugflag updates immediately. - Walks
actions/and re-parses every.conf. - Swaps in the new ruleset all at once — mid-flight battle-start hooks never see a half-loaded state.
- Re-registers Showdown conditions for every action whose
apply(orshowdown) emits a Showdown-side effect. Each newly-registered condition is propagated to every cachedModdedDexinstance so format-specific battles can resolve it.
Battles that are already in progress keep the conditions they were registered with at the start of that battle. New battles pick up whatever’s loaded now.
If a single .conf fails to parse, the rest of the ruleset is preserved. The failing file is logged with its parse error and the previous version of that action (if any) stays live until the file parses again.
What’s “Re-Evaluated” and What Isn’t
Section titled “What’s “Re-Evaluated” and What Isn’t”Match-clause fields fall into two buckets: gates that re-check on every switch-in, and gates that are static for the whole battle.
| Re-checked on every switch-in | Static for the whole battle |
|---|---|
species, form, type, ability, heldItem, teraType, minLevel, maxLevel | aspect, nature, gender, minFriendship, isLegendary, sugar.*, whenMolang |
The re-checked set is enforced by an auto-applied guard inside the switch-in hook. This matters because in this Cobblemon environment Showdown’s onStart re-fires on every switch-in — without the guard, swapping Pikachu out and Bulbasaur in would re-apply a Pikachu-targeted effect to Bulbasaur. The static set is evaluated once at battle start and not re-checked, because the values can’t change mid-battle.
If you write a custom showdown {} block (tier 3), no guard is generated — you control the entire effect, and you’re responsible for any identity checks.
What’s Next
Section titled “What’s Next”- Match Clause — every sugar gate field, what it filters on, and when it re-evaluates.
- Apply Clause — every sugar effect field and the Showdown JS it compiles to.
- Targeting — the five targeting modes and when to use each.
- Molang Predicates — the
whenMolangsurface, bindings, and helpers. - Recipes — working examples to copy from.