Recipe Cookbook
Recipe Cookbook
Section titled “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.
Form Rotation by Time of Day
Section titled “Form Rotation by Time of Day”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 = 5target = "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.
Weather-Gated Ability Buff
Section titled “Weather-Gated Ability Buff”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.
Friendship-Gated Aura
Section titled “Friendship-Gated Aura”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.
Held-Item Heal on Switch-In
Section titled “Held-Item Heal on Switch-In”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.
Low-HP Desperation Mode
Section titled “Low-HP Desperation Mode”Pattern: Pure Molang predicate, oncePerBattle so it doesn’t re-trigger on every switch-in.
id = "recipe_low_hp_desperation"displayName = "Desperation Mode"priority = 5target = "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.
Legendary Boss Buff
Section titled “Legendary Boss Buff”Pattern: Legendary check plus a probability gate and oncePerBattle. Variant fights.
id = "recipe_legendary_boss"displayName = "Legendary Surge"priority = 10target = "SELF"chance = 0.5oncePerBattle = 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.
Field-State Setter (Weather)
Section titled “Field-State Setter (Weather)”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.
Foe-Targeting Status
Section titled “Foe-Targeting Status”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.
Custom Intimidate (Tier 3)
Section titled “Custom Intimidate (Tier 3)”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 = 10target = "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', ...).
Permanent Stat Multiplier (Tier 3)
Section titled “Permanent Stat Multiplier (Tier 3)”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.
Per-Turn Regeneration (Tier 3)
Section titled “Per-Turn Regeneration (Tier 3)”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.
Mixing Sugar and Tier-3
Section titled “Mixing Sugar and Tier-3”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”.
Where to Go From Here
Section titled “Where to Go From Here”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:
- Match Clause — every gate field and how it composes.
- Apply Clause — every sugar effect and what it compiles to.
- Molang Predicates — the full
whenMolangsurface. - Showdown Research — when sugar can’t reach the effect.
- Raw Showdown Hooks — writing your own
showdown { hooks { } }bodies.