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 07: New map
Make: Connect a small route to Pallet Town, then walk through its grass to meet wild Pokémon.
Before you start: Intermediate. Complete Tutorial 06. This is the next version of that same tutorial_world mod, including its original NPC.
Game: Red, local single-player. Use a practice save for experiments. If file creation is new to you, start with Tutorial 00; unfamiliar words are in the Glossary.
1. Set up the files
Tutorials 06, 07, 08, and 11 are versions of one mod, tutorial_world. Install exactly one snapshot. Begin with Tutorial 06's finished folder. Copy your current files somewhere outside the mods directory before replacing them. Keep the same mod ID and save slot as you progress.
For a fresh start, these complete starting files match this lesson's prerequisite: manifest.json, main.lua. Replace them with the finished files below as you complete the lesson.
This is a mod folder, not the engine root. Do not replace the engine's own main.lua. Preserve filenames and avoid an extra .txt extension.
Finished folder layout:
mods/tutorial_world/
main.lua
manifest.json
Each link opens a complete file you can save at the matching path. The JSON and Lua are also printed in full below; do not append them to another lesson's entry function.
| File in this mod | Complete example |
|---|---|
main.lua |
Download main.lua |
manifest.json |
Download manifest.json |
2. Describe the mod
Complete file: replace tutorial_world/manifest.json:
{
"id": "tutorial_world",
"name": "Tutorial World",
"version": "1.0.0",
"entry": "main.lua",
"api": 2,
"games": [
"red"
]
}
id is the stable internal name, name is the label in MODS, and entry tells the loader which Lua file to open. games limits these examples to Red. Keep commas between JSON fields and no comma after the last field.
3. Write the change
Complete file: replace tutorial_world/main.lua:
return function(mod)
local blocks = {}
for i = 1, 30 do blocks[i] = 10 end
blocks[8], blocks[9] = 11, 11 -- the two grass blocks
local pallet = mod.content.maps:get("PALLET_TOWN")
local palletBlocks = {}
for i, block in ipairs(pallet.blocks) do palletBlocks[i] = block end
palletBlocks[7 * pallet.width] = 10 -- block (9, 6), lower-right exit
mod.content.maps:register("TUTORIAL_ROUTE", {
id = "TUTORIAL_ROUTE", label = "TutorialRoute", index = 1000,
tileset = "OVERWORLD", width = 6, height = 5, blocks = blocks,
borderBlock = 10, connections = { west = { map = "PALLET_TOWN", offset = -2 } },
})
mod.content.maps:patch("PALLET_TOWN", {
connections = { east = { map = "TUTORIAL_ROUTE", offset = 2 } },
blocks = palletBlocks,
objects = { __append = {
{
index = 90, x = 8, y = 7, sprite = "SPRITE_BEAUTY",
movement = "STAY", range = "NONE", text = "TEXT_TUTORIAL_WORLD",
name = "TUTORIAL_WORLD_NPC",
},
} },
})
mod.content.map_scripts:register("PALLET_TOWN", {
talk = { TEXT_TUTORIAL_WORLD = {
{ "face_player" }, { "show_text", "Welcome to the\nTutorial World!" },
} },
})
mod.content.encounters:register("TUTORIAL_ROUTE", {
grass = { rate = 25, slots = {
{ species = "PIDGEY", level = 3 }, { species = "RATTATA", level = 3 },
{ species = "PIDGEY", level = 4 }, { species = "RATTATA", level = 4 },
{ species = "PIDGEY", level = 5 }, { species = "RATTATA", level = 5 },
{ species = "PIDGEY", level = 3 }, { species = "RATTATA", level = 3 },
{ species = "PIDGEY", level = 6 }, { species = "RATTATA", level = 6 },
} },
})
end
Understand what you wrote
A map has both a drawing grid and a movement grid. One tile is an 8×8 image square; a block is a 4×4 group of those tiles (32×32 pixels); a player cell is a 2×2 group (16×16 pixels). Thus one block covers 2×2 player cells.
one 32×32 block the same block's four player cells
[t][t][t][t] [ cell ][ cell ]
[t][t][t][t] [ ][ ]
[t][t][t][t] [ cell ][ cell ]
[t][t][t][t] [ ][ ]
width = 6 and height = 5 describe blocks: 30 entries in row order, making a 12×10-player-cell map. The Lua loop fills that list; blocks[8] and blocks[9] replace selected positions. Lua list positions start at 1 even though map x/y coordinates start at 0.
A block number chooses an arrangement from the OVERWORLD tileset, which also determines collision and grass behavior. Numbers from another tileset may mean something entirely different. Use the map recipes and the loaded tileset records in the developer console to inspect IDs before replacing them. The included layout is your starting comparison; do not choose numbers just because their names sound plausible.
The two connections entries join the east side of Pallet Town to the west side of the route. Both directions matter: registering a map alone does not give the player a way to enter or leave it. offset is measured in 32-pixel blocks. The east offset of 2 places the route two blocks (four player cells) lower than Pallet Town; the return connection uses -2. Thus Pallet cell y=13 connects to route cell y=9 in both directions.
Pallet Town originally has a blocked east edge. The example reads its loaded map with get, copies the block list, and changes only block (9, 6) to floor. This opens the entrance while keeping the rest of the imported layout. The copy matters: make edits through patch instead of mutating a record returned by get.
The encounter record names the same route ID and supplies a grass encounter rate plus ten slots. Grass appearance, walkability, and encounter registration are separate pieces; a loaded encounter table cannot turn a wall into walkable grass.
4. Load and check it
Save every file, restart the app, enable the mod for Red in the launcher's MODS panel, and launch. Resolve any load error before checking gameplay. Troubleshooting shows where errors appear. With default desktop bindings, A is Z/Enter/Space, B is X/Backspace, and Start is Escape/keypad Enter; use your configured controls if different.
Optional source-checkout validation
If you have Python 3, LuaJIT, and the engine source with Red imported, open a terminal in the engine root containing tools/. For a mod installed in that checkout's mods directory, run:
python3 tools/modkit.py validate tutorial_world --base imported
If you saved it elsewhere, replace the mod ID with the quoted full path to its folder. A validation error identifies a file or content rule to fix; a pass means you can continue to the in-game checks. It does not prove rendering, sound, or the entire playthrough works. Packaged-app users can use MODS errors and the checkpoints without setting up these command-line tools.
Checkpoint
- Start in Pallet Town with a usable party and confirm the Tutorial 06 greeting still works.
- From the greeting NPC, walk southeast to the opening in the lower-right edge of Pallet Town, at player cell (19, 13). Step east onto the route at (0, 9). Step west again from that same cell to return to Pallet (19, 13), then follow the open path back to the NPC.
- Step through the route's grass near the upper part of the small map until a battle occurs. Expect Pidgey or Rattata from the listed slots; an encounter is random, not guaranteed on the first step.
- Before moving an NPC, translate the desired block position to player cells. Keep the tested entry path clear while changing one block at a time.
Common pitfalls
| What you see | What to check |
|---|---|
| Map registers but I cannot enter it | Check both connection IDs and offsets, then collision at the entry cells. Map registration alone creates no door or path. |
| No battle on the first grass step | Grass encounters are random. Check that the party is usable, the tiles are encounter grass, and the encounters ID matches TUTORIAL_ROUTE. |
For syntax, missing-file, and restart problems, use Troubleshooting. Compare the whole finished file before changing several things at once.
Undo and continue
Keep the practice save and its matching mod files together. Saves can contain this example's custom content or progress. To compare with vanilla Red, disable the mod, restart, and open a separate unmodified practice slot. Do not overwrite the only copy of a modded save while its content is disabled. Save recovery explains backups.
Return to the tutorial index to choose the next lesson.
Start here
Playing
Tutorials
- Choose a tutorial
- 01 Sprite and Text Tweak
- 02 Balance Patch
- 03 New Species
- 04 New Item and Ball
- 05 New Move
- 06 NPC and Dialogue
- 07 New Map
- 08 Quest
- 09 Custom Music
- 10 New Mechanic
- 11 Custom UI Screen
- 12 Mini Total Conversion
Creative guides
Concepts
Recipes and reference
Author and developer tools
Home · Start Playing · Glossary · Troubleshooting · Repository · Community
Game data is imported locally from a supported ROM supplied by the player. Shared mods contain original work or transform recipes, never extracted game content. Check each guide's target game before using its examples.