3 Cookbook Species And Moves
bryanthaboi edited this page 2026-10-20 17:01:40 -04:00

Cookbook: species and moves

These recipes add or extend creature and move records. New content also needs a way for the player to obtain or use it.

Setup: a working mod from Getting Started. Examples target Red/local single-player unless a recipe explicitly says otherwise. Use Modding Basics for syntax and Glossary for unfamiliar terms. Save and restart after content edits. A validation pass and an in-game checkpoint are different checks.

Where code goes: insert Lua fragments inside your existing return function(mod) ... end in main.lua. A complete-file label or named other file overrides that default. Do not paste a second entry function inside the first. Read the recipe's requirements before copying its code. For output, use Read log output; for IDs, use Find internal IDs.

Record shapes: Concepts: Data Model and Reference: Registries. Worked paths: Tutorial 03, Tutorial 05.

R11 - Add a new move (existing effect)

Basic Lua. Before you paste: A Red Rattata below level 15 that can gain a level. This defines the move and adds one learnset entry. Existing level-15-or-higher Pokemon do not replay an earlier level-up.

Complete walkthrough / required concept.

Insert in main.lua inside the entry function (unless a filename is shown).

mod.content.moves:register("MODSTRIKE", {
  id = "MODSTRIKE", name = "MODSTRIKE",
  type = "NORMAL", category = "physical",
  power = 70, accuracy = 100, pp = 15,
  effect = "NO_ADDITIONAL_EFFECT",
})
mod.content.pokemon:patch("RATTATA", {
  learnset = { __append = { { level = 15, move = "MODSTRIKE" } } },
})

Checkpoint: Rattata learns MODSTRIKE at 15 and it deals plain damage. Browse effect ids with mod.content.move_effects:each().

Gen 2 / Gen 3: with "api": 3 the same record registers on every game (Gen 3 allocates a free move slot) and effect maps to the native effect; an effect with no verified mapping is refused.

R12 - Add a custom move effect

Callbacks. Before you paste: Register a move that uses MODDRAIN_EFFECT; this effect alone is not a learnable move. The one-in-three check is random, so test several uses.

Complete walkthrough / required concept.

Insert in main.lua inside the entry function (unless a filename is shown).

mod.content.move_effects:register("MODDRAIN_EFFECT", {
  kind = "secondary",
  run = function(ctx)
    if ctx.rng(0, 255) < 85 then
      ctx.changeStage(ctx.target, "speed", -1)
    end
  end,
})

Checkpoint: a move with effect = "MODDRAIN_EFFECT" drops speed about a third of the time. The ctx facade (say, anim, damage, inflict, changeStage, rng) is the supported surface (src/battle/EffectRegistry.lua).

Gen 3: this record runs unchanged from an api 2 or api 3 mod (gen3MoveEffects). secondary and full run after a hit that dealt damage, primary runs for a power-0 move, ctx has the same members over Gen 3 battlers, and changeStage reads special as Special Attack. The Gen 1 damage-pipeline fields (hitCount, chooseDamage, onMiss, afterDamage, gate, perform, ...) are not read. Gold routes to gen2MoveEffects.

R13 - Add a new species

Basic Lua + assets. Before you paste: Supply original assets/front.png and assets/back.png, with the dimensions described in Art Pipeline. To encounter the new species, also follow Tutorial03's encounter step.

Complete walkthrough / required concept.

Insert in main.lua inside the entry function (unless a filename is shown).

mod.content.pokemon:register("MODMON", {
  id = "MODMON", name = "MODMON", dex = 152, types = { "NORMAL" },
  baseStats = { hp = 60, attack = 70, defense = 55, speed = 90, special = 65 },
  catchRate = 120, baseExp = 100, growthRate = "MEDIUM_FAST",
  level1Moves = { "TACKLE" }, learnset = {}, evolutions = {},
  spriteFront = mod.assets:path("assets/front.png"),
  spriteBack = mod.assets:path("assets/back.png"),
  frontSize = 5,
})

Checkpoint: modkit validate passes; the mon battles and the dex counts 152. Full field list and the catchable checkpoint: Tutorial 03.

Gen 2 / Gen 3: an api 3 mod registers the same record on every game; Gen 3 allocates a free species slot. Gen 3 battle pictures are 64x64 (smaller art is recentred).

R14 - Give a species a party icon

Basic Lua + assets. Before you paste: MODMON must already exist (R13). Put its original icon at assets/icon.png; open the party menu with MODMON in your party.

Complete walkthrough / required concept.

Insert in main.lua inside the entry function (unless a filename is shown).

mod.content.icons:register("MODMON", {
  image = mod.assets:path("assets/icon.png"),
})

Checkpoint: the party menu shows the icon. The registry is keyed by species id, so a species past dex 151 gets an icon without touching any list.

Gen 2 / Gen 3: Gen 3 takes this record and replaces the party icon (a string value borrows another species' icon). Gold's icons have a Gen 2 shape; an api 3 mod writes that shape as a pass-through record there.

R15 - Give a species a cry

Basic Lua + audio. Before you paste: For the first alternative, MODMON and assets/cry.wav must exist. The second alternative retunes PIKACHU instead; choose the one you intend to change.

Complete walkthrough / required concept.

Insert in main.lua inside the entry function (unless a filename is shown).

mod.content.cries:register("MODMON", { file = mod.assets:path("assets/cry.wav") })
-- or derive from a vanilla cry:
mod.content.cries:patch("PIKACHU", { pitch = 200 })

Checkpoint: the cry plays on send-out and in the dex. Def shapes: a file, a { base, pitch, length } derivation, or a chip program.

Gen 3: cries rows keyed by species play file and chip defs through the Gen 3 audio engine. There is no vanilla cry row to patch, so the PIKACHU derivation does not apply.

R16 - Add a growth curve

Functions. Before you paste: Assign growthRate = "MODCUBIC" to a registered species before testing. The formula here is a simple cubic example, not a recreation of a later game's similarly named curve.

Complete walkthrough / required concept.

Insert in main.lua inside the entry function (unless a filename is shown).

mod.content.growth_rates:register("MODCUBIC", {
  expForLevel = function(level) return level * level * level end,
})

Checkpoint: a species with growthRate = "MODCUBIC" levels on your curve. The curve must strictly increase; validation calls it at levels 1 and 2 and rejects a flat one.

Gen 2 / Gen 3: the curve applies on both. Gen 3 seeds its six native curves (MEDIUM_FAST, ERRATIC, FLUCTUATING, MEDIUM_SLOW, FAST, SLOW), so registering one of those ids fails as already registered; pick a new id, or override a native one to replace it.

R17 - Add an evolution method

Advanced outline. Before you paste: You must define MODMON, MODMON2, and how friendship is maintained before this outline can produce an evolution. It does not implement a friendship system by itself.

Complete walkthrough / required concept.

Outline / alternative: read the requirements above; this is not a complete mod.

mod.content.evolution_methods:register("FRIENDSHIP", {
  check = function(game, mon, evo, trigger)
    return trigger.kind == "levelup" and (mon.friendship or 0) >= 220
  end,
})
mod.content.pokemon:patch("MODMON", {
  evolutions = { { method = "FRIENDSHIP", species = "MODMON2" } },
})

Checkpoint: the evolution fires when check returns true (vanilla methods: LEVEL, ITEM, TRADE; src/pokemon/Evolution.lua). The evolution.check hook wraps every check if you need to intercept globally.

Gen 2 / Gen 3: both games already evolve on friendship natively. Gen 3 seeds its EVO_* methods (EVO_FRIENDSHIP, EVO_LEVEL, ...) as records, a custom id is called as check(game, mon, evo, trigger) with trigger.kind "levelup", "trade" or "item", and mon is the native party record. On Gold an api 3 record is called this same way; an api 2 record gets Gold's own check(entry, mon, ctx).