1
Tutorial 08 Quest
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 08 — Quest

Goal. Build a multi-map fetch quest: an NPC asks for an item, private state tracks progress across saves, and completion plays a scripted cutscene.

Prerequisites. Tutorials 0607; Concepts: Save Model; Reference: Script Commands.

Files created.

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

Steps

  1. Quest state in mod.save, not global flags. Flags are a shared namespace every mod and the story can see; mod.save is yours alone and survives saves. Script rows reach it through set_field with the mod: prefix:

    -- stage 0 = not started, 1 = asked, 2 = done
    
  2. The quest giver. A talk script in Pallet Town that branches on stage — reading state via a check the script sets up:

    return function(mod)
      mod.content.map_scripts:register("PALLET_TOWN", {
        talk = {
          TEXT_TUT8_GIVER = {
            { "check_flag", "TUT8_DONE" },
            { "jump_if_true", "thanks" },
            { "check_item", "NUGGET" },
            { "jump_if_true", "complete" },
            { "show_text", "Fetch me a NUGGET\nfrom MODROUTE!" },
            { "jump", "end" },
    
            { "label", "complete" },
            { "take_item", "NUGGET", 1 },
            { "set_flag", "TUT8_DONE" },
            { "jump", "cutscene" },
    
            { "label", "thanks" },
            { "show_text", "Thanks again!" },
            { "jump", "end" },
    
            { "label", "cutscene" },
            { "play_music", "Music_Surfing", { keep = false } },
            { "emote", "player", "happy", 40 },
            { "walk_npc", "player", { "down", "down" } },
            { "fade", "out", 30 },
            { "warp", "PALLET_TOWN", 10, 8, "down" },
            { "fade", "in", 30 },
            { "show_text", "You did it!\nQuest complete!" },
          },
        },
      })
      mod.content.maps:patch("PALLET_TOWN", {
        objects = { __append = {
          { index = 92, x = 12, y = 8, sprite = "SPRITE_SCIENTIST",
            movement = "STAY", range = "NONE", text = "TEXT_TUT8_GIVER" },
        } },
      })
    end
    

    Outcome: the giver asks, acknowledges the fetched item, runs a cutscene in pure command rows — no raw Lua — and thanks you ever after. (The quest flag here is a story flag so the talk rows can branch on it; keep bulk private state in mod.save.)

  3. The item on the far map. Hide a Nugget on tutorial 07's MODROUTE through the field deep registry:

    mod.content.field:patch("hiddenItems", {
      MODROUTE = { { x = 4, y = 2, item = "NUGGET" } },
    })
    

    Outcome: pressing A on that cell finds the Nugget.

  4. An onStep nudge. A compose contribution can add a step trigger beside the map's own scripts:

    mod.content.map_scripts:register("MODROUTE", {
      onStep = function(game, ow, x, y)
        if x == 4 and y == 2 and not game.save.flags.TUT8_HINTED then
          game.save.flags.TUT8_HINTED = true
        end
        -- return true to consume the step (stop later handlers)
      end,
    })
    

    The handler is onStep(game, overworld, cellX, cellY); contributions chain with first-truthy-return-consumes semantics (src/script/MapScripts.lua). Outcome: stepping on the spot marks a hint flag once — and the map's own onStep (if any) still runs, because compose contributions stack instead of replacing.

  5. A migration for the future. When v1.1 renames its state, old saves upgrade themselves:

    mod.migrations:add("1.1.0", function(save)
      local bucket = save.modData[mod.id]
      if bucket and bucket.stage == nil then bucket.stage = 0 end
    end)
    

    Outcome: a save written by mod 1.0.0 loads cleanly under 1.1.0 (Save Model).

Checkpoint

Fresh game: giver asks → find the Nugget on MODROUTE → return → cutscene plays → subsequent talks thank you. Save and reload at each stage; the quest resumes where it stood. Disable the mod: Pallet Town and the flags' effects are gone, and the save loads clean.

Common pitfalls

  • Global flags for private state. Another mod's TUT8_DONE collides with yours; prefix flags with your mod id, or better, keep state in mod.save and use flags only where script rows must branch.
  • Blocking verbs in parallel scripts. run_parallel scripts may not use foreground commands (show_text, ...) or move the player — the runner errors, attributed to your mod.
  • Two cutscenes at once. Queued scripts are FIFO through mod.world:queueScript; a second queueScript while one runs returns an error rather than interleaving.

Next: Tutorial 09 — Custom Music.