Skip to content

Tutorial 05 New Move

bryanthaboi edited this page Jul 19, 2026 · 1 revision

Tutorial 05 — New move

Goal. Add a damaging special move with a custom secondary effect and a sound.

Prerequisites. Tutorial 04; Reference: Registries (moves, move_effects).

Files created.

mods/tutorial_05_move/
├── manifest.json
└── main.lua

Steps

  1. Register the effect first. Move effects are records the battle engine dispatches on — vanilla's own effects are registrations in the same registry (src/battle/MoveEffects.lua):

    return function(mod)
      mod.content.move_effects:register("MODBEAM_EFFECT", {
        kind = "secondary",
        run = function(ctx)
          -- ~1 in 3: drop the target's defense a stage
          if ctx.rng(0, 255) < 85 then
            ctx.changeStage(ctx.target, "defense", -1)
          end
        end,
      })
    end

    kind is "primary" (replaces the damage step), "secondary" (runs after damage lands), or "full" (steered from inside the damage pipeline). The ctx facade is the supported surface: ctx.user, ctx.target, ctx.move, ctx.rng, ctx.say(text), ctx.anim(name), ctx.damage(who, amount), ctx.inflict(who, statusId, opts), ctx.changeStage(who, stat, delta) — everything else in the battle state is engine-internal and may break. Outcome: modkit validate accepts the record.

  2. Register the move.

    mod.content.moves:register("MODBEAM", {
      id = "MODBEAM", name = "MODBEAM",
      type = "PSYCHIC_TYPE", category = "special",
      power = 80, accuracy = 90, pp = 10,
      effect = "MODBEAM_EFFECT",
    })

    category states the physical/special/status split explicitly instead of relying on the Gen 1 type-based guess. Note the type id: the psychic type is PSYCHIC_TYPE (the move named PSYCHIC owns the bare name). Outcome: the move validates, and its effect reference resolves in the post-merge pass.

  3. Teach it. Give your species from tutorial 03 the move, or patch a vanilla learnset with a wrapper:

    mod.content.pokemon:patch("ABRA", {
      learnset = { __append = { { level = 10, move = "MODBEAM" } } },
    })

    Outcome: Abra learns MODBEAM at 10 and it deals special-side damage in battle; roughly one hit in three drops defense.

  4. Give it a sound. Register an sfx def and name it from the move's anim field, or simply reuse a vanilla battle animation id. Sound defs are shape-dispatched — { file = ... } for an audio file, a chip program for authenticity (Audio Authoring).

Checkpoint

MODBEAM lands special-category damage (check against a high-Defense, low-Special target), and the defense drop message appears across several uses. Disable the mod: Abra's learnset and the move list are vanilla.

Common pitfalls

  • Omitting category. The engine falls back to the type-based Gen 1 split; be explicit so a later type addition cannot reclassify your move.
  • Touching BattleState internals from run. The ctx facade is the promise; raw battle fields are not. A handler that throws is pcall-isolated and logged against your mod — the battle continues.
  • Effect id collisions. register on an existing effect id (SLEEP_EFFECT, ...) collides with the engine's own registration; use a new id, or override deliberately.

Next: Tutorial 06 — NPC and Dialogue.

Clone this wiki locally