Skip to content

Tutorial 04 New Item

bryanthaboi edited this page Jul 19, 2026 · 1 revision

Tutorial 04 — New item and ball

Goal. Add an item, put it on a mart shelf, and add a new Poke Ball variant with its own catch math.

Prerequisites. Tutorial 03; Concepts: Events and Hooks.

Files created.

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

Steps

  1. Register the item.

    return function(mod)
      mod.content.items:register("MODBERRY", {
        id = "MODBERRY", name = "MODBERRY", price = 250,
        tossable = true,
      })
    end

    id, name, price are the required fields. Outcome: modkit validate passes and the item is a known id — it can sit in the bag, be tossed, be sold.

  2. Stock a mart. Marts are data: each shop clerk's TEXT entry in the text_pointers deep registry carries a mart list. Add your item to Viridian's shelf:

    mod.content.text_pointers:patch("ViridianMart", {
      TEXT_VIRIDIANMART_CLERK = {
        mart = { __append = { "MODBERRY" } },
      },
    })

    Outcome: the Viridian clerk sells MODBERRY at its price. The __append wrapper extends the list; a bare list would replace the whole shelf.

  3. Register a ball. The balls registry drives the catch pipeline (src/battle/Catching.lua) — vanilla's five balls are records in it, registered by the engine:

    mod.content.items:register("MODBALL", {
      id = "MODBALL", name = "MODBALL", price = 500, ball = "MODBALL",
    })
    mod.content.balls:register("MODBALL", {
      randMax = 120, hpFactor = 10, wobbleFactor = 120,
      tossAnim = "ULTRATOSS_ANIM", flicker = true,
    })

    The record fields are the real Gen 1 catch math (src/battle/Catching.lua): randMax is the ceiling of the catch roll (lower = better; Poke 255, Great 200, Ultra 150), hpFactor the X of the HP term, wobbleFactor the divisor of the shake math, autoCatch skips the roll entirely (Master Ball), tossAnim picks the arc (TOSS_ANIM, GREATTOSS_ANIM, ULTRATOSS_ANIM), flicker the Master/Ultra palette strobe, and attempt replaces the whole roll with your function. Outcome: throwing MODBALL at a wild mon uses your numbers — better than an Ultra Ball.

  4. Watch it happen. Subscribe to the throw event to see the pipeline from the outside:

    mod.events:on("battle.ball_thrown", function(ev)
      mod.log:info("%s: caught=%s shakes=%d", ev.ball,
                   tostring(ev.caught), ev.shakes or 0)
    end)

    Outcome: a log line per throw naming ball, outcome, and shakes.

Checkpoint

Buy a MODBERRY and a MODBALL in Viridian; throw the MODBALL at a wild Pidgey and see the logged shake count. Disable the mod: the shelf, the items, and the catch math are vanilla again.

Common pitfalls

  • Forgetting ball = "MODBALL" on the item. The item record's ball field is what routes a bag throw into your balls record.
  • Guessing field names from other games. There is no catchModifier; the record is Gen 1's actual math (randMax, hpFactor, wobbleFactor). Check Reference: Registries before inventing a field — unknown top-level keys are preserved but do nothing.
  • Custom use-effects. The item_effects registry holds use handlers ({ use = fn, field = true, battle = true, needsTarget = true }) and the items.effect field names one. Vanilla item behavior lives in src/inventory/ItemEffects.lua, whose result verbs are "consumed", "kept", "failed", "ball", "learn" — match that contract in a custom handler.

Next: Tutorial 05 — New Move.

Clone this wiki locally