Skip to content

Recipe Cookbook

Every recipe below ships with ShowdownActions in config/showdown_actions/actions/ as a .conf file with enabled = false. To use one, copy it to a new file with your own id, set enabled = true, and adjust the specifics.

The recipes are deliberately small — each one demonstrates one or two features in isolation. Real actions on your server will combine these patterns.


Pattern: Three (or more) actions sharing a mutexGroup, each with a different sugar.time predicate. Only the matching variant fires per battle.

The bundled recipe_lycanroc_form_rotation.conf is the dusk variant; in production you’d write three of these (midday, dusk, midnight).

id = "lycanroc_dusk"
displayName = "Lycanroc - Dusk Variant"
priority = 5
target = "SELF"
mutexGroup = "lycanroc_form"
match {
species = "lycanroc"
sugar { time = "dusk" }
}
apply {
aspect = "dusk"
boosts { spe = 1, atk = 1 }
message = "{name} resonates with the dusk light!"
}

The midday and midnight variants would be identical except for sugar.time, the aspect, and the message. The mutex group means even if more than one matched (which they shouldn’t, given the time buckets), only the highest-priority winner would fire.

Demonstrates: mutexGroup, sugar.time, forced aspects, multi-stat boosts, message templating.


Pattern: A property gate (the ability) plus a Molang weather check, ANDed.

id = "rainy_swift_swim"
displayName = "Rainy Swift Swim"
target = "SELF"
match {
ability = "swiftswim"
whenMolang = "q.world.is_raining && q.pokemon.level >= 30"
}
apply {
boosts { spe = 2 }
}

Could also be written entirely in sugar:

match {
ability = "swiftswim"
minLevel = 30
sugar { weather = "rain" }
}

The first form composes a property check with a hand-written Molang predicate; the second form uses two property checks plus the sugar weather field. Both compile down to roughly the same evaluation. Use the form that reads clearest for what you mean.

Demonstrates: whenMolang composing with property fields, q.world.is_raining, level-band gates.


Pattern: Catch-all friendship check — no species filter, just “any high-friendship Pokémon gets a small offensive boost on switch-in”.

id = "recipe_friendship_aura"
displayName = "Friendship Aura"
target = "SELF"
match {
minFriendship = 200
}
apply {
boosts { atk = 1, spa = 1 }
message = "{name} feels its bond - strength rises!"
}

This will fire for any Pokémon at friendship 200+. If you want it gated tighter, add a species or type filter to the match clause.

Demonstrates: minFriendship, multi-stat boosts, {name} templating.


Pattern: Held-item filter plus a percentage heal and status cure.

id = "recipe_wish_on_switch"
displayName = "Wishful Healing"
target = "SELF"
match {
heldItem = "cobblemon:lum_berry"
}
apply {
heal = "25%"
removeStatus = true
message = "{name}'s berry restores it!"
}

The removeStatus = true calls pokemon.cureStatus() before the heal. The heal = "25%" is interpreted as 25% of pokemon.maxhp, rounded down.

Demonstrates: heldItem, heal (percentage form), removeStatus.


Pattern: Pure Molang predicate, oncePerBattle so it doesn’t re-trigger on every switch-in.

id = "recipe_low_hp_desperation"
displayName = "Desperation Mode"
priority = 5
target = "SELF"
oncePerBattle = true
match {
whenMolang = "q.pokemon.hp_ratio < 0.25"
}
apply {
boosts { atk = 2 }
message = "{name} digs in - desperation fuels its attacks!"
}

q.pokemon.hp_ratio is a number in [0, 1] — current HP divided by max. The oncePerBattle flag is implemented via a battle-scoped flag map keyed by action id, so the boost only applies the first time the threshold is crossed in a given battle.

Demonstrates: Pure-Molang gate, oncePerBattle, q.pokemon.hp_ratio.


Pattern: Legendary check plus a probability gate and oncePerBattle. Variant fights.

id = "recipe_legendary_boss"
displayName = "Legendary Surge"
priority = 10
target = "SELF"
chance = 0.5
oncePerBattle = true
match {
isLegendary = true
}
apply {
boosts { atk = 1, spa = 1, spe = 1 }
message = "{name} radiates an overwhelming presence!"
}

isLegendary = true matches species labeled legendary. chance = 0.5 rolls 50/50 once per battle; failed rolls drop the action silently. oncePerBattle = true means even on a successful roll, switching the legendary out and back in won’t restack the boost.

Demonstrates: isLegendary, chance, oncePerBattle, multi-stat boosts.


Pattern: target = "FIELD" for weather/terrain. The matched Pokémon triggers it; the field is global.

id = "kyogre_rain"
displayName = "Kyogre - Primordial Sea"
target = "FIELD"
match {
species = "kyogre"
}
apply {
weather = "raindance"
message = "{name} commands the storm - Rain Dance is now active!"
}

target = "FIELD" skips the auto-applied species guard around the switch-in hook — field setters are idempotent, and re-applying rain when a teammate switches in afterwards is harmless.

Demonstrates: target = "FIELD", weather, message broadcast.


Pattern: target = "FOES" to land an effect on the opposing lead. Skipping the auto-guard is intentional — the foe’s identity isn’t yours to check.

id = "magmar_burn_foe"
displayName = "Magmar - Searing Aura"
target = "FOES"
match {
species = "magmar"
}
apply {
status = "brn"
message = "{name} radiates intense heat!"
}

pokemon.trySetStatus('brn', pokemon) respects type immunities — a fire-type lead won’t be burned. No special case needed for that.

Demonstrates: target = "FOES", status, the auto-guard skip rule.


Pattern: When sugar can’t reach the effect, drop a showdown {} block. The hook reads pokemon.side.foe.active[0] directly and applies a -2 Atk drop.

id = "custom_intimidate"
displayName = "Sigil Intimidate"
priority = 10
target = "SELF"
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 + '!');
}
}"""
}
}

Could you do this with target = "FOES" + boosts { atk = -2 }? Yes — but the tier-3 form gives you a custom message with the foe’s actual name, plus full control over the activation condition (e.g. only-if-foe-isn’t-fainted).

Demonstrates: showdown {} block, onStart hook, pokemon.side.foe.active[0], this.boost, this.add('-message', ...).


Pattern: Stat-modifier hook that multiplies a stat for the whole battle. Sugar can’t do this; stat boosts are stage deltas, stat multipliers are continuous.

id = "iron_fist_buff"
displayName = "Iron Fist Reinforcement"
target = "SELF"
match {
species = "machamp"
ability = "ironfist"
}
showdown {
conditionId = "showdownactionsmachampironfist"
conditionName = "Iron Fist Reinforcement"
scope = "volatile"
hooks {
onModifyAtk = """function(atk, pokemon) {
if (!pokemon.species || pokemon.species.id !== 'machamp') return;
return Math.floor(atk * 1.25);
}"""
}
}

The explicit identity guard inside the hook is essential — tier-3 blocks don’t auto-generate one, and the patched runSwitch in this environment keeps the volatile attached to the slot when a different Pokémon switches in.

Demonstrates: Tier-3 stat modifier, manual identity guard, multiplicative composition with the rest of the modifier chain.


Pattern: onResidual for per-turn ticks, with a turnDuration so it auto-expires.

id = "regenerative_field"
displayName = "Regenerative Field"
target = "SELF"
match {
species = "venusaur"
sugar { weather = "rain" }
}
showdown {
conditionId = "showdownactionsregenerativefield"
conditionName = "Regenerative Field"
scope = "volatile"
turnDuration = 5
hooks {
onStart = """function(pokemon) {
this.add('-message', pokemon.name + ' is regenerating!');
}"""
onResidual = """function(pokemon) {
if (pokemon.fainted) return;
this.heal(Math.floor(pokemon.maxhp / 16), pokemon);
}"""
onEnd = """function(pokemon) {
this.add('-message', 'The regenerative field fades.');
}"""
}
}

onResidual fires every turn during the residual phase, after moves resolve. Five turns in, onEnd fires and the volatile is removed.

Demonstrates: turnDuration, onStart / onResidual / onEnd lifecycle, this.heal, defensive fainted-check.


Pattern: Use apply { } for the visible effect (boosts, message) and a showdown { } block for an additional hook the sugar can’t express.

id = "techniques_sharpen"
target = "SELF"
match {
species = "kabutops"
}
apply {
boosts { atk = 1 }
message = "{name}'s technique sharpens!"
}
showdown {
conditionId = "showdownactionskabutopstechnique"
conditionName = "Sharpened Technique"
scope = "volatile"
hooks {
onModifyAtk = """function(atk, pokemon) {
if (!pokemon.species || pokemon.species.id !== 'kabutops') return;
return Math.floor(atk * 1.1);
}"""
}
}

Two volatiles attach at switch-in: the auto-applied one (which plays the +1 stat-up animation and broadcasts the message) and the custom one (which adds a 1.1× modifier). They’re independent — ordering between them isn’t guaranteed.

Demonstrates: Sugar and tier-3 coexisting, separation of “what the player sees” from “what the engine computes”.


The recipes above are drawn from the bundled actions/ directory. Open the bundled files directly — every one has inline comments explaining why it’s structured the way it is, and the bundled 00_bible.conf is a canonical reference for every option ShowdownActions supports.

For deeper customization: