1
Tutorial 03 New Species
bryanthaboi edited this page 2026-07-19 16:19:15 -04:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Tutorial 03 — New species

Goal. Add a brand-new species that battles, is catchable, appears in the Pokedex, shows a party icon, and plays a cry.

Prerequisites. Tutorials 0102; Concepts: Data Model; Guide: Art Pipeline.

Files created.

mods/tutorial_03_species/
├── manifest.json
├── main.lua
└── assets/
    ├── front.png    -- your art: 4-shade grayscale, white = transparent
    ├── back.png
    ├── icon.png
    └── cry.wav

Steps

  1. Register the full record. A register (unlike patch) must carry every required field; modkit validate checks it against the schema before you ever boot:

    return function(mod)
      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", "GROWL" },
        learnset = {
          { level = 12, move = "QUICK_ATTACK" },
          { level = 20, move = "SLASH" },
        },
        evolutions = {},
        spriteFront = mod.assets:path("assets/front.png"),
        spriteBack = mod.assets:path("assets/back.png"),
        frontSize = 5,
        dexEntry = { kind = "TUTORIAL", heightFt = 2, heightIn = 4,
                     weight = 30.0, text = "Added by a mod.\nIt is very new." },
      })
    end
    

    Outcome: modkit validate tutorial_03_species confirms the record — a missing field is a mod-attributed load error, not a runtime crash. growthRate must name a growth_rates record (vanilla curves: MEDIUM_FAST, MEDIUM_SLOW, FAST, SLOW, SLIGHTLY_FAST, SLIGHTLY_SLOW).

  2. A dex number past 151 just works. The Pokedex scales from the merged data (constants.dexSize / the highest dex), not a hard-coded 151. Outcome: the dex lists 152 entries and shows your dexEntry text. For a sparse or padded dex, set dexSize/dexDigits explicitly (Concepts: Data Model).

  3. Give it a party icon. The icons registry is keyed by species id:

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

    Outcome: the party menu shows your icon instead of the fallback.

  4. Give it a cry. A file-based cry def:

    mod.content.cries:register("MODMON", {
      file = mod.assets:path("assets/cry.wav"),
    })
    

    Outcome: the cry plays on send-out and from the dex screen. (You can also derive from a vanilla cry — { base = "PIKACHU", pitch = 200 } — or author a chip cry; see Audio Authoring.)

  5. Meet it in the wild. Patch an encounter table so you can catch one:

    mod.content.encounters:patch("ROUTE_1", {
      grass = { slots = { __prepend = { { species = "MODMON", level = 5 } } } },
    })
    

    Outcome: Route 1 grass rolls MODMON in its top (most common) slot.

Checkpoint

Catch a MODMON, save, and reload: the mon survives the save validation pass, because a registered species is a known id. Then disable the mod and reload the same save: the mon moves to the LOST box with a report — and comes home when you re-enable the mod (Concepts: Save Model).

Common pitfalls

  • Forgetting frontSize. The battle layout reads it (17 tiles); validation requires it for exactly that reason.
  • Occupying a taken dex number. Two species on one dex slot fight over the list row; pick the next free number or renumber deliberately.
  • Skipping the cry. A species with no cry degrades to silence rather than crashing — but add one; the dex and send-out feel broken without it.
  • Art format. Sprites are 4-shade grayscale with white as transparency for battle pics; see Art Pipeline before drawing.

Next: Tutorial 04 — New Item and Ball.