Add starter, gift, and fossil encounters to seed data

Define special encounter data (starters, gifts, fossils) in a new
special_encounters module and merge them into route seed JSON during
generation. Add new route locations to ROUTE_ORDER for cities that
previously had no wild encounters (Saffron City, Pewter City, etc.).
Show colored method badges in the EncounterModal UI for special
encounter types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 13:50:49 +01:00
parent 73d4a1831c
commit d488c252b8
21 changed files with 7430 additions and 5384 deletions

View File

@@ -1,10 +1,11 @@
---
# nuzlocke-tracker-rxrt
title: Support gift/static encounter marking
status: todo
status: completed
type: feature
priority: normal
created_at: 2026-02-05T14:21:47Z
updated_at: 2026-02-05T14:21:47Z
updated_at: 2026-02-07T12:47:37Z
---
Add ability to distinguish gift/static encounters in the encounter tracking interface. Currently all encounters are treated the same regardless of encounter method. The backend RouteEncounter model already has an encounterMethod field from PokeAPI (gift, walk, etc.) — surface this in the UI and potentially allow logging encounters on routes that don't have PokeAPI encounter data (e.g. starter pokemon, in-game trades).

View File

@@ -1 +1,96 @@
[]
[
{
"name": "Route 101",
"order": 1,
"encounters": [
{
"national_dex": 252,
"pokemon_name": "treecko",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 255,
"pokemon_name": "torchic",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 258,
"pokemon_name": "mudkip",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
{
"name": "Rustboro City",
"order": 2,
"encounters": [
{
"national_dex": 345,
"pokemon_name": "lileep",
"method": "fossil",
"encounter_rate": 100,
"min_level": 20,
"max_level": 20
},
{
"national_dex": 347,
"pokemon_name": "anorith",
"method": "fossil",
"encounter_rate": 100,
"min_level": 20,
"max_level": 20
}
]
},
{
"name": "Lavaridge Town",
"order": 3,
"encounters": [
{
"national_dex": 360,
"pokemon_name": "wynaut",
"method": "gift",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
{
"name": "Route 119",
"order": 4,
"encounters": [
{
"national_dex": 351,
"pokemon_name": "castform",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Mossdeep City",
"order": 5,
"encounters": [
{
"national_dex": 374,
"pokemon_name": "beldum",
"method": "gift",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
}
]

View File

@@ -42,6 +42,30 @@
"encounter_rate": 50,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 1,
"pokemon_name": "bulbasaur",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 4,
"pokemon_name": "charmander",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 7,
"pokemon_name": "squirtle",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
@@ -445,6 +469,14 @@
"encounter_rate": 25,
"min_level": 6,
"max_level": 12
},
{
"national_dex": 129,
"pokemon_name": "magikarp",
"method": "gift",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
@@ -1338,12 +1370,50 @@
"encounter_rate": 50,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 133,
"pokemon_name": "eevee",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Saffron City",
"order": 31,
"encounters": [
{
"national_dex": 131,
"pokemon_name": "lapras",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
},
{
"national_dex": 106,
"pokemon_name": "hitmonlee",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
},
{
"national_dex": 107,
"pokemon_name": "hitmonchan",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Route 16",
"order": 31,
"order": 32,
"encounters": [
{
"national_dex": 21,
@@ -1381,7 +1451,7 @@
},
{
"name": "Route 17",
"order": 32,
"order": 33,
"encounters": [
{
"national_dex": 129,
@@ -1475,7 +1545,7 @@
},
{
"name": "Route 18",
"order": 33,
"order": 34,
"encounters": [
{
"national_dex": 129,
@@ -1569,7 +1639,7 @@
},
{
"name": "Fuchsia City",
"order": 34,
"order": 35,
"encounters": [
{
"national_dex": 129,
@@ -1631,12 +1701,12 @@
},
{
"name": "Safari Zone",
"order": 35,
"order": 36,
"encounters": [],
"children": [
{
"name": "Safari Zone (Middle)",
"order": 36,
"order": 37,
"encounters": [
{
"national_dex": 129,
@@ -1770,7 +1840,7 @@
},
{
"name": "Safari Zone (Area 1 East)",
"order": 37,
"order": 38,
"encounters": [
{
"national_dex": 129,
@@ -1904,7 +1974,7 @@
},
{
"name": "Safari Zone (Area 2 North)",
"order": 38,
"order": 39,
"encounters": [
{
"national_dex": 129,
@@ -2038,7 +2108,7 @@
},
{
"name": "Safari Zone (Area 3 West)",
"order": 39,
"order": 40,
"encounters": [
{
"national_dex": 129,
@@ -2174,7 +2244,7 @@
},
{
"name": "Route 15",
"order": 40,
"order": 41,
"encounters": [
{
"national_dex": 69,
@@ -2228,7 +2298,7 @@
},
{
"name": "Route 14",
"order": 41,
"order": 42,
"encounters": [
{
"national_dex": 69,
@@ -2282,7 +2352,7 @@
},
{
"name": "Route 13",
"order": 42,
"order": 43,
"encounters": [
{
"national_dex": 129,
@@ -2384,7 +2454,7 @@
},
{
"name": "Route 12",
"order": 43,
"order": 44,
"encounters": [
{
"national_dex": 129,
@@ -2478,12 +2548,12 @@
},
{
"name": "Pokemon Tower",
"order": 44,
"order": 45,
"encounters": [],
"children": [
{
"name": "Pokemon Tower (3F)",
"order": 45,
"order": 46,
"encounters": [
{
"national_dex": 92,
@@ -2513,36 +2583,6 @@
},
{
"name": "Pokemon Tower (4F)",
"order": 46,
"encounters": [
{
"national_dex": 92,
"pokemon_name": "gastly",
"method": "walk",
"encounter_rate": 86,
"min_level": 18,
"max_level": 24
},
{
"national_dex": 104,
"pokemon_name": "cubone",
"method": "walk",
"encounter_rate": 9,
"min_level": 20,
"max_level": 22
},
{
"national_dex": 93,
"pokemon_name": "haunter",
"method": "walk",
"encounter_rate": 5,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Pokemon Tower (5F)",
"order": 47,
"encounters": [
{
@@ -2572,8 +2612,38 @@
]
},
{
"name": "Pokemon Tower (6F)",
"name": "Pokemon Tower (5F)",
"order": 48,
"encounters": [
{
"national_dex": 92,
"pokemon_name": "gastly",
"method": "walk",
"encounter_rate": 86,
"min_level": 18,
"max_level": 24
},
{
"national_dex": 104,
"pokemon_name": "cubone",
"method": "walk",
"encounter_rate": 9,
"min_level": 20,
"max_level": 22
},
{
"national_dex": 93,
"pokemon_name": "haunter",
"method": "walk",
"encounter_rate": 5,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Pokemon Tower (6F)",
"order": 49,
"encounters": [
{
"national_dex": 92,
@@ -2603,7 +2673,7 @@
},
{
"name": "Pokemon Tower (7F)",
"order": 49,
"order": 50,
"encounters": [
{
"national_dex": 92,
@@ -2635,76 +2705,6 @@
},
{
"name": "Sea Route 19",
"order": 50,
"encounters": [
{
"national_dex": 129,
"pokemon_name": "magikarp",
"method": "old-rod",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 72,
"pokemon_name": "tentacool",
"method": "surf",
"encounter_rate": 100,
"min_level": 5,
"max_level": 40
},
{
"national_dex": 118,
"pokemon_name": "goldeen",
"method": "good-rod",
"encounter_rate": 50,
"min_level": 10,
"max_level": 10
},
{
"national_dex": 60,
"pokemon_name": "poliwag",
"method": "good-rod",
"encounter_rate": 50,
"min_level": 10,
"max_level": 10
},
{
"national_dex": 118,
"pokemon_name": "goldeen",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 116,
"pokemon_name": "horsea",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 90,
"pokemon_name": "shellder",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 120,
"pokemon_name": "staryu",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
}
]
},
{
"name": "Sea Route 20",
"order": 51,
"encounters": [
{
@@ -2774,13 +2774,83 @@
]
},
{
"name": "Seafoam Islands",
"name": "Sea Route 20",
"order": 52,
"encounters": [
{
"national_dex": 129,
"pokemon_name": "magikarp",
"method": "old-rod",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 72,
"pokemon_name": "tentacool",
"method": "surf",
"encounter_rate": 100,
"min_level": 5,
"max_level": 40
},
{
"national_dex": 118,
"pokemon_name": "goldeen",
"method": "good-rod",
"encounter_rate": 50,
"min_level": 10,
"max_level": 10
},
{
"national_dex": 60,
"pokemon_name": "poliwag",
"method": "good-rod",
"encounter_rate": 50,
"min_level": 10,
"max_level": 10
},
{
"national_dex": 118,
"pokemon_name": "goldeen",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 116,
"pokemon_name": "horsea",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 90,
"pokemon_name": "shellder",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 120,
"pokemon_name": "staryu",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
}
]
},
{
"name": "Seafoam Islands",
"order": 53,
"encounters": [],
"children": [
{
"name": "Seafoam Islands (1F)",
"order": 53,
"order": 54,
"encounters": [
{
"national_dex": 98,
@@ -2850,7 +2920,7 @@
},
{
"name": "Seafoam Islands (B1F)",
"order": 54,
"order": 55,
"encounters": [
{
"national_dex": 98,
@@ -2912,7 +2982,7 @@
},
{
"name": "Seafoam Islands (B2F)",
"order": 55,
"order": 56,
"encounters": [
{
"national_dex": 86,
@@ -2974,7 +3044,7 @@
},
{
"name": "Seafoam Islands (B3F)",
"order": 56,
"order": 57,
"encounters": [
{
"national_dex": 129,
@@ -3084,7 +3154,7 @@
},
{
"name": "Seafoam Islands (B4F)",
"order": 57,
"order": 58,
"encounters": [
{
"national_dex": 129,
@@ -3196,7 +3266,7 @@
},
{
"name": "Cinnabar Island",
"order": 58,
"order": 59,
"encounters": [
{
"national_dex": 129,
@@ -3253,17 +3323,41 @@
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 138,
"pokemon_name": "omanyte",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 140,
"pokemon_name": "kabuto",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 142,
"pokemon_name": "aerodactyl",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
{
"name": "Pokemon Mansion",
"order": 59,
"order": 60,
"encounters": [],
"children": [
{
"name": "Pokemon Mansion (1F)",
"order": 60,
"order": 61,
"encounters": [
{
"national_dex": 88,
@@ -3317,7 +3411,7 @@
},
{
"name": "Pokemon Mansion (2F)",
"order": 61,
"order": 62,
"encounters": [
{
"national_dex": 88,
@@ -3371,7 +3465,7 @@
},
{
"name": "Pokemon Mansion (3F)",
"order": 62,
"order": 63,
"encounters": [
{
"national_dex": 88,
@@ -3433,7 +3527,7 @@
},
{
"name": "Pokemon Mansion (B1F)",
"order": 63,
"order": 64,
"encounters": [
{
"national_dex": 88,
@@ -3497,7 +3591,7 @@
},
{
"name": "Sea Route 21",
"order": 64,
"order": 65,
"encounters": [
{
"national_dex": 129,
@@ -3607,7 +3701,7 @@
},
{
"name": "Route 23",
"order": 65,
"order": 66,
"encounters": [
{
"national_dex": 129,
@@ -3709,12 +3803,12 @@
},
{
"name": "Victory Road 2",
"order": 66,
"order": 67,
"encounters": [],
"children": [
{
"name": "Victory Road 2 (1F)",
"order": 67,
"order": 68,
"encounters": [
{
"national_dex": 95,
@@ -3784,7 +3878,7 @@
},
{
"name": "Victory Road 2 (2F)",
"order": 68,
"order": 69,
"encounters": [
{
"national_dex": 95,
@@ -3854,7 +3948,7 @@
},
{
"name": "Victory Road 2 (3F)",
"order": 69,
"order": 70,
"encounters": [
{
"national_dex": 74,
@@ -3926,12 +4020,12 @@
},
{
"name": "Cerulean Cave",
"order": 70,
"order": 71,
"encounters": [],
"children": [
{
"name": "Cerulean Cave (1F)",
"order": 71,
"order": 72,
"encounters": [
{
"national_dex": 129,
@@ -4073,7 +4167,7 @@
},
{
"name": "Cerulean Cave (2F)",
"order": 72,
"order": 73,
"encounters": [
{
"national_dex": 85,
@@ -4151,7 +4245,7 @@
},
{
"name": "Cerulean Cave (B1F)",
"order": 73,
"order": 74,
"encounters": [
{
"national_dex": 129,
@@ -4276,5 +4370,19 @@
]
}
]
},
{
"name": "Water Labyrinth",
"order": 75,
"encounters": [
{
"national_dex": 175,
"pokemon_name": "togepi",
"method": "gift",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
}
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,134 @@
[]
[
{
"name": "Pallet Town",
"order": 1,
"encounters": [
{
"national_dex": 1,
"pokemon_name": "bulbasaur",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 4,
"pokemon_name": "charmander",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 7,
"pokemon_name": "squirtle",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
{
"name": "Route 4",
"order": 2,
"encounters": [
{
"national_dex": 129,
"pokemon_name": "magikarp",
"method": "gift",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
{
"name": "Celadon City",
"order": 3,
"encounters": [
{
"national_dex": 133,
"pokemon_name": "eevee",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Saffron City",
"order": 4,
"encounters": [
{
"national_dex": 131,
"pokemon_name": "lapras",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
},
{
"national_dex": 106,
"pokemon_name": "hitmonlee",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
},
{
"national_dex": 107,
"pokemon_name": "hitmonchan",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Cinnabar Island",
"order": 5,
"encounters": [
{
"national_dex": 138,
"pokemon_name": "omanyte",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 140,
"pokemon_name": "kabuto",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 142,
"pokemon_name": "aerodactyl",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
{
"name": "Water Labyrinth",
"order": 6,
"encounters": [
{
"national_dex": 175,
"pokemon_name": "togepi",
"method": "gift",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
}
]

View File

@@ -1 +1,134 @@
[]
[
{
"name": "Pallet Town",
"order": 1,
"encounters": [
{
"national_dex": 1,
"pokemon_name": "bulbasaur",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 4,
"pokemon_name": "charmander",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 7,
"pokemon_name": "squirtle",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
{
"name": "Route 4",
"order": 2,
"encounters": [
{
"national_dex": 129,
"pokemon_name": "magikarp",
"method": "gift",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
{
"name": "Celadon City",
"order": 3,
"encounters": [
{
"national_dex": 133,
"pokemon_name": "eevee",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Saffron City",
"order": 4,
"encounters": [
{
"national_dex": 131,
"pokemon_name": "lapras",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
},
{
"national_dex": 106,
"pokemon_name": "hitmonlee",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
},
{
"national_dex": 107,
"pokemon_name": "hitmonchan",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Cinnabar Island",
"order": 5,
"encounters": [
{
"national_dex": 138,
"pokemon_name": "omanyte",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 140,
"pokemon_name": "kabuto",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 142,
"pokemon_name": "aerodactyl",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
{
"name": "Water Labyrinth",
"order": 6,
"encounters": [
{
"national_dex": 175,
"pokemon_name": "togepi",
"method": "gift",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
}
]

View File

@@ -1 +1,96 @@
[]
[
{
"name": "Route 101",
"order": 1,
"encounters": [
{
"national_dex": 252,
"pokemon_name": "treecko",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 255,
"pokemon_name": "torchic",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 258,
"pokemon_name": "mudkip",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
{
"name": "Rustboro City",
"order": 2,
"encounters": [
{
"national_dex": 345,
"pokemon_name": "lileep",
"method": "fossil",
"encounter_rate": 100,
"min_level": 20,
"max_level": 20
},
{
"national_dex": 347,
"pokemon_name": "anorith",
"method": "fossil",
"encounter_rate": 100,
"min_level": 20,
"max_level": 20
}
]
},
{
"name": "Lavaridge Town",
"order": 3,
"encounters": [
{
"national_dex": 360,
"pokemon_name": "wynaut",
"method": "gift",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
{
"name": "Route 119",
"order": 4,
"encounters": [
{
"national_dex": 351,
"pokemon_name": "castform",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Mossdeep City",
"order": 5,
"encounters": [
{
"national_dex": 374,
"pokemon_name": "beldum",
"method": "gift",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
}
]

View File

@@ -42,6 +42,30 @@
"encounter_rate": 50,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 1,
"pokemon_name": "bulbasaur",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 4,
"pokemon_name": "charmander",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 7,
"pokemon_name": "squirtle",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
@@ -445,6 +469,14 @@
"encounter_rate": 25,
"min_level": 6,
"max_level": 12
},
{
"national_dex": 129,
"pokemon_name": "magikarp",
"method": "gift",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
@@ -1338,12 +1370,50 @@
"encounter_rate": 50,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 133,
"pokemon_name": "eevee",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Saffron City",
"order": 31,
"encounters": [
{
"national_dex": 131,
"pokemon_name": "lapras",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
},
{
"national_dex": 106,
"pokemon_name": "hitmonlee",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
},
{
"national_dex": 107,
"pokemon_name": "hitmonchan",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Route 16",
"order": 31,
"order": 32,
"encounters": [
{
"national_dex": 21,
@@ -1381,7 +1451,7 @@
},
{
"name": "Route 17",
"order": 32,
"order": 33,
"encounters": [
{
"national_dex": 129,
@@ -1475,7 +1545,7 @@
},
{
"name": "Route 18",
"order": 33,
"order": 34,
"encounters": [
{
"national_dex": 129,
@@ -1569,7 +1639,7 @@
},
{
"name": "Fuchsia City",
"order": 34,
"order": 35,
"encounters": [
{
"national_dex": 129,
@@ -1631,12 +1701,12 @@
},
{
"name": "Safari Zone",
"order": 35,
"order": 36,
"encounters": [],
"children": [
{
"name": "Safari Zone (Middle)",
"order": 36,
"order": 37,
"encounters": [
{
"national_dex": 129,
@@ -1770,7 +1840,7 @@
},
{
"name": "Safari Zone (Area 1 East)",
"order": 37,
"order": 38,
"encounters": [
{
"national_dex": 129,
@@ -1904,7 +1974,7 @@
},
{
"name": "Safari Zone (Area 2 North)",
"order": 38,
"order": 39,
"encounters": [
{
"national_dex": 129,
@@ -2038,7 +2108,7 @@
},
{
"name": "Safari Zone (Area 3 West)",
"order": 39,
"order": 40,
"encounters": [
{
"national_dex": 129,
@@ -2174,7 +2244,7 @@
},
{
"name": "Route 15",
"order": 40,
"order": 41,
"encounters": [
{
"national_dex": 43,
@@ -2228,7 +2298,7 @@
},
{
"name": "Route 14",
"order": 41,
"order": 42,
"encounters": [
{
"national_dex": 43,
@@ -2282,7 +2352,7 @@
},
{
"name": "Route 13",
"order": 42,
"order": 43,
"encounters": [
{
"national_dex": 129,
@@ -2384,7 +2454,7 @@
},
{
"name": "Route 12",
"order": 43,
"order": 44,
"encounters": [
{
"national_dex": 129,
@@ -2478,12 +2548,12 @@
},
{
"name": "Pokemon Tower",
"order": 44,
"order": 45,
"encounters": [],
"children": [
{
"name": "Pokemon Tower (3F)",
"order": 45,
"order": 46,
"encounters": [
{
"national_dex": 92,
@@ -2513,36 +2583,6 @@
},
{
"name": "Pokemon Tower (4F)",
"order": 46,
"encounters": [
{
"national_dex": 92,
"pokemon_name": "gastly",
"method": "walk",
"encounter_rate": 86,
"min_level": 18,
"max_level": 24
},
{
"national_dex": 104,
"pokemon_name": "cubone",
"method": "walk",
"encounter_rate": 9,
"min_level": 20,
"max_level": 22
},
{
"national_dex": 93,
"pokemon_name": "haunter",
"method": "walk",
"encounter_rate": 5,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Pokemon Tower (5F)",
"order": 47,
"encounters": [
{
@@ -2572,8 +2612,38 @@
]
},
{
"name": "Pokemon Tower (6F)",
"name": "Pokemon Tower (5F)",
"order": 48,
"encounters": [
{
"national_dex": 92,
"pokemon_name": "gastly",
"method": "walk",
"encounter_rate": 86,
"min_level": 18,
"max_level": 24
},
{
"national_dex": 104,
"pokemon_name": "cubone",
"method": "walk",
"encounter_rate": 9,
"min_level": 20,
"max_level": 22
},
{
"national_dex": 93,
"pokemon_name": "haunter",
"method": "walk",
"encounter_rate": 5,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Pokemon Tower (6F)",
"order": 49,
"encounters": [
{
"national_dex": 92,
@@ -2603,7 +2673,7 @@
},
{
"name": "Pokemon Tower (7F)",
"order": 49,
"order": 50,
"encounters": [
{
"national_dex": 92,
@@ -2635,76 +2705,6 @@
},
{
"name": "Sea Route 19",
"order": 50,
"encounters": [
{
"national_dex": 129,
"pokemon_name": "magikarp",
"method": "old-rod",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 72,
"pokemon_name": "tentacool",
"method": "surf",
"encounter_rate": 100,
"min_level": 5,
"max_level": 40
},
{
"national_dex": 118,
"pokemon_name": "goldeen",
"method": "good-rod",
"encounter_rate": 50,
"min_level": 10,
"max_level": 10
},
{
"national_dex": 60,
"pokemon_name": "poliwag",
"method": "good-rod",
"encounter_rate": 50,
"min_level": 10,
"max_level": 10
},
{
"national_dex": 118,
"pokemon_name": "goldeen",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 116,
"pokemon_name": "horsea",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 90,
"pokemon_name": "shellder",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 120,
"pokemon_name": "staryu",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
}
]
},
{
"name": "Sea Route 20",
"order": 51,
"encounters": [
{
@@ -2774,13 +2774,83 @@
]
},
{
"name": "Seafoam Islands",
"name": "Sea Route 20",
"order": 52,
"encounters": [
{
"national_dex": 129,
"pokemon_name": "magikarp",
"method": "old-rod",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 72,
"pokemon_name": "tentacool",
"method": "surf",
"encounter_rate": 100,
"min_level": 5,
"max_level": 40
},
{
"national_dex": 118,
"pokemon_name": "goldeen",
"method": "good-rod",
"encounter_rate": 50,
"min_level": 10,
"max_level": 10
},
{
"national_dex": 60,
"pokemon_name": "poliwag",
"method": "good-rod",
"encounter_rate": 50,
"min_level": 10,
"max_level": 10
},
{
"national_dex": 118,
"pokemon_name": "goldeen",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 116,
"pokemon_name": "horsea",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 90,
"pokemon_name": "shellder",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 120,
"pokemon_name": "staryu",
"method": "super-rod",
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
}
]
},
{
"name": "Seafoam Islands",
"order": 53,
"encounters": [],
"children": [
{
"name": "Seafoam Islands (1F)",
"order": 53,
"order": 54,
"encounters": [
{
"national_dex": 116,
@@ -2850,7 +2920,7 @@
},
{
"name": "Seafoam Islands (B1F)",
"order": 54,
"order": 55,
"encounters": [
{
"national_dex": 116,
@@ -2912,7 +2982,7 @@
},
{
"name": "Seafoam Islands (B2F)",
"order": 55,
"order": 56,
"encounters": [
{
"national_dex": 86,
@@ -2974,7 +3044,7 @@
},
{
"name": "Seafoam Islands (B3F)",
"order": 56,
"order": 57,
"encounters": [
{
"national_dex": 129,
@@ -3084,7 +3154,7 @@
},
{
"name": "Seafoam Islands (B4F)",
"order": 57,
"order": 58,
"encounters": [
{
"national_dex": 129,
@@ -3196,7 +3266,7 @@
},
{
"name": "Cinnabar Island",
"order": 58,
"order": 59,
"encounters": [
{
"national_dex": 129,
@@ -3253,17 +3323,41 @@
"encounter_rate": 25,
"min_level": 15,
"max_level": 15
},
{
"national_dex": 138,
"pokemon_name": "omanyte",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 140,
"pokemon_name": "kabuto",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 142,
"pokemon_name": "aerodactyl",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
{
"name": "Pokemon Mansion",
"order": 59,
"order": 60,
"encounters": [],
"children": [
{
"name": "Pokemon Mansion (1F)",
"order": 60,
"order": 61,
"encounters": [
{
"national_dex": 109,
@@ -3317,7 +3411,7 @@
},
{
"name": "Pokemon Mansion (2F)",
"order": 61,
"order": 62,
"encounters": [
{
"national_dex": 109,
@@ -3371,7 +3465,7 @@
},
{
"name": "Pokemon Mansion (3F)",
"order": 62,
"order": 63,
"encounters": [
{
"national_dex": 109,
@@ -3425,7 +3519,7 @@
},
{
"name": "Pokemon Mansion (B1F)",
"order": 63,
"order": 64,
"encounters": [
{
"national_dex": 109,
@@ -3481,7 +3575,7 @@
},
{
"name": "Sea Route 21",
"order": 64,
"order": 65,
"encounters": [
{
"national_dex": 129,
@@ -3591,7 +3685,7 @@
},
{
"name": "Route 23",
"order": 65,
"order": 66,
"encounters": [
{
"national_dex": 129,
@@ -3693,12 +3787,12 @@
},
{
"name": "Victory Road 2",
"order": 66,
"order": 67,
"encounters": [],
"children": [
{
"name": "Victory Road 2 (1F)",
"order": 67,
"order": 68,
"encounters": [
{
"national_dex": 95,
@@ -3768,7 +3862,7 @@
},
{
"name": "Victory Road 2 (2F)",
"order": 68,
"order": 69,
"encounters": [
{
"national_dex": 95,
@@ -3838,7 +3932,7 @@
},
{
"name": "Victory Road 2 (3F)",
"order": 69,
"order": 70,
"encounters": [
{
"national_dex": 74,
@@ -3910,12 +4004,12 @@
},
{
"name": "Cerulean Cave",
"order": 70,
"order": 71,
"encounters": [],
"children": [
{
"name": "Cerulean Cave (1F)",
"order": 71,
"order": 72,
"encounters": [
{
"national_dex": 129,
@@ -4057,7 +4151,7 @@
},
{
"name": "Cerulean Cave (2F)",
"order": 72,
"order": 73,
"encounters": [
{
"national_dex": 85,
@@ -4135,7 +4229,7 @@
},
{
"name": "Cerulean Cave (B1F)",
"order": 73,
"order": 74,
"encounters": [
{
"national_dex": 129,
@@ -4260,5 +4354,19 @@
]
}
]
},
{
"name": "Water Labyrinth",
"order": 75,
"encounters": [
{
"national_dex": 175,
"pokemon_name": "togepi",
"method": "gift",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
}
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -42,6 +42,30 @@
"encounter_rate": 40,
"min_level": 10,
"max_level": 20
},
{
"national_dex": 1,
"pokemon_name": "bulbasaur",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 4,
"pokemon_name": "charmander",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 7,
"pokemon_name": "squirtle",
"method": "starter",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
@@ -461,6 +485,14 @@
"encounter_rate": 15,
"min_level": 8,
"max_level": 10
},
{
"national_dex": 129,
"pokemon_name": "magikarp",
"method": "gift",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
@@ -1434,12 +1466,50 @@
"encounter_rate": 50,
"min_level": 10,
"max_level": 10
},
{
"national_dex": 133,
"pokemon_name": "eevee",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Saffron City",
"order": 31,
"encounters": [
{
"national_dex": 131,
"pokemon_name": "lapras",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
},
{
"national_dex": 106,
"pokemon_name": "hitmonlee",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
},
{
"national_dex": 107,
"pokemon_name": "hitmonchan",
"method": "gift",
"encounter_rate": 100,
"min_level": 25,
"max_level": 25
}
]
},
{
"name": "Route 16",
"order": 31,
"order": 32,
"encounters": [
{
"national_dex": 84,
@@ -1485,7 +1555,7 @@
},
{
"name": "Route 17",
"order": 32,
"order": 33,
"encounters": [
{
"national_dex": 129,
@@ -1563,7 +1633,7 @@
},
{
"name": "Route 18",
"order": 33,
"order": 34,
"encounters": [
{
"national_dex": 129,
@@ -1649,7 +1719,7 @@
},
{
"name": "Fuchsia City",
"order": 34,
"order": 35,
"encounters": [
{
"national_dex": 129,
@@ -1695,12 +1765,12 @@
},
{
"name": "Safari Zone",
"order": 35,
"order": 36,
"encounters": [],
"children": [
{
"name": "Safari Zone (Middle)",
"order": 36,
"order": 37,
"encounters": [
{
"national_dex": 129,
@@ -1826,7 +1896,7 @@
},
{
"name": "Safari Zone (Area 1 East)",
"order": 37,
"order": 38,
"encounters": [
{
"national_dex": 129,
@@ -1944,7 +2014,7 @@
},
{
"name": "Safari Zone (Area 2 North)",
"order": 38,
"order": 39,
"encounters": [
{
"national_dex": 129,
@@ -2062,7 +2132,7 @@
},
{
"name": "Safari Zone (Area 3 West)",
"order": 39,
"order": 40,
"encounters": [
{
"national_dex": 129,
@@ -2182,7 +2252,7 @@
},
{
"name": "Route 15",
"order": 40,
"order": 41,
"encounters": [
{
"national_dex": 69,
@@ -2244,7 +2314,7 @@
},
{
"name": "Route 14",
"order": 41,
"order": 42,
"encounters": [
{
"national_dex": 69,
@@ -2306,7 +2376,7 @@
},
{
"name": "Route 13",
"order": 42,
"order": 43,
"encounters": [
{
"national_dex": 129,
@@ -2432,7 +2502,7 @@
},
{
"name": "Route 12",
"order": 43,
"order": 44,
"encounters": [
{
"national_dex": 129,
@@ -2550,33 +2620,11 @@
},
{
"name": "Pokemon Tower",
"order": 44,
"order": 45,
"encounters": [],
"children": [
{
"name": "Pokemon Tower (3F)",
"order": 45,
"encounters": [
{
"national_dex": 92,
"pokemon_name": "gastly",
"method": "walk",
"encounter_rate": 95,
"min_level": 18,
"max_level": 25
},
{
"national_dex": 93,
"pokemon_name": "haunter",
"method": "walk",
"encounter_rate": 5,
"min_level": 20,
"max_level": 25
}
]
},
{
"name": "Pokemon Tower (4F)",
"order": 46,
"encounters": [
{
@@ -2598,8 +2646,30 @@
]
},
{
"name": "Pokemon Tower (5F)",
"name": "Pokemon Tower (4F)",
"order": 47,
"encounters": [
{
"national_dex": 92,
"pokemon_name": "gastly",
"method": "walk",
"encounter_rate": 95,
"min_level": 18,
"max_level": 25
},
{
"national_dex": 93,
"pokemon_name": "haunter",
"method": "walk",
"encounter_rate": 5,
"min_level": 20,
"max_level": 25
}
]
},
{
"name": "Pokemon Tower (5F)",
"order": 48,
"encounters": [
{
"national_dex": 92,
@@ -2629,7 +2699,7 @@
},
{
"name": "Pokemon Tower (6F)",
"order": 48,
"order": 49,
"encounters": [
{
"national_dex": 92,
@@ -2659,7 +2729,7 @@
},
{
"name": "Pokemon Tower (7F)",
"order": 49,
"order": 50,
"encounters": [
{
"national_dex": 92,
@@ -2691,7 +2761,7 @@
},
{
"name": "Sea Route 19",
"order": 50,
"order": 51,
"encounters": [
{
"national_dex": 129,
@@ -2753,7 +2823,7 @@
},
{
"name": "Sea Route 20",
"order": 51,
"order": 52,
"encounters": [
{
"national_dex": 129,
@@ -2815,12 +2885,12 @@
},
{
"name": "Seafoam Islands",
"order": 52,
"order": 53,
"encounters": [],
"children": [
{
"name": "Seafoam Islands (1F)",
"order": 53,
"order": 54,
"encounters": [
{
"national_dex": 41,
@@ -2858,7 +2928,7 @@
},
{
"name": "Seafoam Islands (B1F)",
"order": 54,
"order": 55,
"encounters": [
{
"national_dex": 41,
@@ -2912,7 +2982,7 @@
},
{
"name": "Seafoam Islands (B2F)",
"order": 55,
"order": 56,
"encounters": [
{
"national_dex": 41,
@@ -2974,7 +3044,7 @@
},
{
"name": "Seafoam Islands (B3F)",
"order": 56,
"order": 57,
"encounters": [
{
"national_dex": 129,
@@ -3092,7 +3162,7 @@
},
{
"name": "Seafoam Islands (B4F)",
"order": 57,
"order": 58,
"encounters": [
{
"national_dex": 129,
@@ -3212,7 +3282,7 @@
},
{
"name": "Cinnabar Island",
"order": 58,
"order": 59,
"encounters": [
{
"national_dex": 129,
@@ -3253,17 +3323,41 @@
"encounter_rate": 40,
"min_level": 15,
"max_level": 30
},
{
"national_dex": 138,
"pokemon_name": "omanyte",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 140,
"pokemon_name": "kabuto",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
},
{
"national_dex": 142,
"pokemon_name": "aerodactyl",
"method": "fossil",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
},
{
"name": "Pokemon Mansion",
"order": 59,
"order": 60,
"encounters": [],
"children": [
{
"name": "Pokemon Mansion (1F)",
"order": 60,
"order": 61,
"encounters": [
{
"national_dex": 20,
@@ -3301,7 +3395,7 @@
},
{
"name": "Pokemon Mansion (2F)",
"order": 61,
"order": 62,
"encounters": [
{
"national_dex": 88,
@@ -3339,7 +3433,7 @@
},
{
"name": "Pokemon Mansion (3F)",
"order": 62,
"order": 63,
"encounters": [
{
"national_dex": 88,
@@ -3377,7 +3471,7 @@
},
{
"name": "Pokemon Mansion (B1F)",
"order": 63,
"order": 64,
"encounters": [
{
"national_dex": 88,
@@ -3417,7 +3511,7 @@
},
{
"name": "Sea Route 21",
"order": 64,
"order": 65,
"encounters": [
{
"national_dex": 129,
@@ -3511,7 +3605,7 @@
},
{
"name": "Route 23",
"order": 65,
"order": 66,
"encounters": [
{
"national_dex": 129,
@@ -3597,12 +3691,12 @@
},
{
"name": "Victory Road 2",
"order": 66,
"order": 67,
"encounters": [],
"children": [
{
"name": "Victory Road 2 (1F)",
"order": 67,
"order": 68,
"encounters": [
{
"national_dex": 74,
@@ -3640,7 +3734,7 @@
},
{
"name": "Victory Road 2 (2F)",
"order": 68,
"order": 69,
"encounters": [
{
"national_dex": 74,
@@ -3694,7 +3788,7 @@
},
{
"name": "Victory Road 2 (3F)",
"order": 69,
"order": 70,
"encounters": [
{
"national_dex": 74,
@@ -3742,12 +3836,12 @@
},
{
"name": "Cerulean Cave",
"order": 70,
"order": 71,
"encounters": [],
"children": [
{
"name": "Cerulean Cave (1F)",
"order": 71,
"order": 72,
"encounters": [
{
"national_dex": 129,
@@ -3857,7 +3951,7 @@
},
{
"name": "Cerulean Cave (2F)",
"order": 72,
"order": 73,
"encounters": [
{
"national_dex": 42,
@@ -3927,7 +4021,7 @@
},
{
"name": "Cerulean Cave (B1F)",
"order": 73,
"order": 74,
"encounters": [
{
"national_dex": 129,
@@ -4028,5 +4122,19 @@
]
}
]
},
{
"name": "Water Labyrinth",
"order": 75,
"encounters": [
{
"national_dex": 175,
"pokemon_name": "togepi",
"method": "gift",
"encounter_rate": 100,
"min_level": 5,
"max_level": 5
}
]
}
]

View File

@@ -15,6 +15,8 @@ import re
import sys
from pathlib import Path
from app.seeds.special_encounters import SPECIAL_ENCOUNTERS
REPO_ROOT = Path(__file__).parents[4] # backend/src/app/seeds -> repo root
POKEAPI_DIR = REPO_ROOT / "data" / "pokeapi" / "data" / "api" / "v2"
DATA_DIR = Path(__file__).parent / "data"
@@ -461,6 +463,7 @@ ROUTE_ORDER: dict[str, list[str]] = {
"Route 22",
"Route 2",
"Viridian Forest",
"Pewter City",
"Route 3",
"Mt Moon",
"Route 4",
@@ -480,6 +483,8 @@ ROUTE_ORDER: dict[str, list[str]] = {
"Route 8",
"Route 7",
"Celadon City",
"Saffron City",
"Lavender Town",
"Route 16",
"Route 17",
"Route 18",
@@ -553,6 +558,7 @@ ROUTE_ORDER: dict[str, list[str]] = {
"Ilex Forest",
"National Park",
"Route 35",
"Goldenrod City",
"Route 36",
"Route 37",
"Ecruteak City",
@@ -589,6 +595,7 @@ ROUTE_ORDER: dict[str, list[str]] = {
"Route 1",
"Viridian City",
"Viridian Forest",
"Pewter City",
"Route 2",
"Route 3",
"Mt Moon",
@@ -637,6 +644,7 @@ ROUTE_ORDER: dict[str, list[str]] = {
"Petalburg Woods",
"Rusturf Tunnel",
"Route 116",
"Rustboro City",
"Route 105",
"Route 106",
"Dewford Town",
@@ -653,6 +661,7 @@ ROUTE_ORDER: dict[str, list[str]] = {
"Route 112",
"Fiery Path",
"Jagged Pass",
"Lavaridge Town",
"Route 113",
"Route 114",
"Meteor Falls",
@@ -813,6 +822,29 @@ def aggregate_encounters(raw_encounters: list[dict]) -> list[dict]:
return sorted(result, key=lambda x: (-x["encounter_rate"], x["pokemon_name"]))
def merge_special_encounters(routes: list[dict], special_data: dict[str, list[dict]]) -> list[dict]:
"""Merge special encounters into existing routes or create new ones."""
# Build lookup: route name -> route dict (including children)
route_map: dict[str, dict] = {}
for r in routes:
route_map[r["name"]] = r
for child in r.get("children", []):
route_map[child["name"]] = child
for location_name, encounters in special_data.items():
for enc in encounters:
all_pokemon_dex.add(enc["national_dex"])
if location_name in route_map:
route_map[location_name]["encounters"].extend(encounters)
else:
new_route = {"name": location_name, "order": 0, "encounters": encounters}
routes.append(new_route)
route_map[location_name] = new_route
return routes
def process_version(version_name: str, vg_info: dict, vg_key: str) -> list[dict]:
"""Process all locations for a specific game version.
@@ -911,6 +943,11 @@ def process_version(version_name: str, vg_info: dict, vg_key: str) -> list[dict]
"encounters": aggregated,
})
# Merge special encounters (starters, gifts, fossils)
special_data = SPECIAL_ENCOUNTERS.get(vg_key, {})
if special_data:
routes = merge_special_encounters(routes, special_data)
# Sort routes by game progression order
routes = sort_routes_by_progression(routes, vg_key)

View File

@@ -0,0 +1,96 @@
"""Special encounter data not available from PokeAPI wild encounter tables.
Includes starters, gifts, fossils, and other guaranteed encounters.
Keyed by version group name (same keys as VERSION_GROUPS in fetch_pokeapi.py).
Each value maps route display names to lists of encounter dicts using the
same format as aggregated PokeAPI encounters.
"""
SPECIAL_ENCOUNTERS: dict[str, dict[str, list[dict]]] = {
"firered-leafgreen": {
"Pallet Town": [
{"national_dex": 1, "pokemon_name": "bulbasaur", "method": "starter", "encounter_rate": 100, "min_level": 5, "max_level": 5},
{"national_dex": 4, "pokemon_name": "charmander", "method": "starter", "encounter_rate": 100, "min_level": 5, "max_level": 5},
{"national_dex": 7, "pokemon_name": "squirtle", "method": "starter", "encounter_rate": 100, "min_level": 5, "max_level": 5},
],
"Route 4": [
{"national_dex": 129, "pokemon_name": "magikarp", "method": "gift", "encounter_rate": 100, "min_level": 5, "max_level": 5},
],
"Celadon City": [
{"national_dex": 133, "pokemon_name": "eevee", "method": "gift", "encounter_rate": 100, "min_level": 25, "max_level": 25},
],
"Saffron City": [
{"national_dex": 131, "pokemon_name": "lapras", "method": "gift", "encounter_rate": 100, "min_level": 25, "max_level": 25},
{"national_dex": 106, "pokemon_name": "hitmonlee", "method": "gift", "encounter_rate": 100, "min_level": 25, "max_level": 25},
{"national_dex": 107, "pokemon_name": "hitmonchan", "method": "gift", "encounter_rate": 100, "min_level": 25, "max_level": 25},
],
"Cinnabar Island": [
{"national_dex": 138, "pokemon_name": "omanyte", "method": "fossil", "encounter_rate": 100, "min_level": 5, "max_level": 5},
{"national_dex": 140, "pokemon_name": "kabuto", "method": "fossil", "encounter_rate": 100, "min_level": 5, "max_level": 5},
{"national_dex": 142, "pokemon_name": "aerodactyl", "method": "fossil", "encounter_rate": 100, "min_level": 5, "max_level": 5},
],
"Water Labyrinth": [
{"national_dex": 175, "pokemon_name": "togepi", "method": "gift", "encounter_rate": 100, "min_level": 5, "max_level": 5},
],
},
"heartgold-soulsilver": {
"New Bark Town": [
{"national_dex": 152, "pokemon_name": "chikorita", "method": "starter", "encounter_rate": 100, "min_level": 5, "max_level": 5},
{"national_dex": 155, "pokemon_name": "cyndaquil", "method": "starter", "encounter_rate": 100, "min_level": 5, "max_level": 5},
{"national_dex": 158, "pokemon_name": "totodile", "method": "starter", "encounter_rate": 100, "min_level": 5, "max_level": 5},
],
"Violet City": [
{"national_dex": 175, "pokemon_name": "togepi", "method": "gift", "encounter_rate": 100, "min_level": 1, "max_level": 1},
],
"Goldenrod City": [
{"national_dex": 133, "pokemon_name": "eevee", "method": "gift", "encounter_rate": 100, "min_level": 5, "max_level": 5},
],
"Cianwood City": [
{"national_dex": 213, "pokemon_name": "shuckle", "method": "gift", "encounter_rate": 100, "min_level": 20, "max_level": 20},
],
"Mt Mortar": [
{"national_dex": 236, "pokemon_name": "tyrogue", "method": "gift", "encounter_rate": 100, "min_level": 10, "max_level": 10},
],
"Dragons Den": [
{"national_dex": 147, "pokemon_name": "dratini", "method": "gift", "encounter_rate": 100, "min_level": 15, "max_level": 15},
],
"Pewter City": [
{"national_dex": 138, "pokemon_name": "omanyte", "method": "fossil", "encounter_rate": 100, "min_level": 20, "max_level": 20},
{"national_dex": 140, "pokemon_name": "kabuto", "method": "fossil", "encounter_rate": 100, "min_level": 20, "max_level": 20},
{"national_dex": 142, "pokemon_name": "aerodactyl", "method": "fossil", "encounter_rate": 100, "min_level": 20, "max_level": 20},
{"national_dex": 345, "pokemon_name": "lileep", "method": "fossil", "encounter_rate": 100, "min_level": 20, "max_level": 20},
{"national_dex": 347, "pokemon_name": "anorith", "method": "fossil", "encounter_rate": 100, "min_level": 20, "max_level": 20},
{"national_dex": 408, "pokemon_name": "cranidos", "method": "fossil", "encounter_rate": 100, "min_level": 20, "max_level": 20},
{"national_dex": 410, "pokemon_name": "shieldon", "method": "fossil", "encounter_rate": 100, "min_level": 20, "max_level": 20},
],
},
"emerald": {
"Route 101": [
{"national_dex": 252, "pokemon_name": "treecko", "method": "starter", "encounter_rate": 100, "min_level": 5, "max_level": 5},
{"national_dex": 255, "pokemon_name": "torchic", "method": "starter", "encounter_rate": 100, "min_level": 5, "max_level": 5},
{"national_dex": 258, "pokemon_name": "mudkip", "method": "starter", "encounter_rate": 100, "min_level": 5, "max_level": 5},
],
"Route 119": [
{"national_dex": 351, "pokemon_name": "castform", "method": "gift", "encounter_rate": 100, "min_level": 25, "max_level": 25},
],
"Lavaridge Town": [
{"national_dex": 360, "pokemon_name": "wynaut", "method": "gift", "encounter_rate": 100, "min_level": 5, "max_level": 5},
],
"Mossdeep City": [
{"national_dex": 374, "pokemon_name": "beldum", "method": "gift", "encounter_rate": 100, "min_level": 5, "max_level": 5},
],
"Rustboro City": [
{"national_dex": 345, "pokemon_name": "lileep", "method": "fossil", "encounter_rate": 100, "min_level": 20, "max_level": 20},
{"national_dex": 347, "pokemon_name": "anorith", "method": "fossil", "encounter_rate": 100, "min_level": 20, "max_level": 20},
],
},
}
# Aliases — version groups sharing the same special encounter data
SPECIAL_ENCOUNTERS["red-blue"] = SPECIAL_ENCOUNTERS["firered-leafgreen"]
SPECIAL_ENCOUNTERS["yellow"] = SPECIAL_ENCOUNTERS["firered-leafgreen"]
SPECIAL_ENCOUNTERS["lets-go"] = SPECIAL_ENCOUNTERS["firered-leafgreen"]
SPECIAL_ENCOUNTERS["gold-silver"] = SPECIAL_ENCOUNTERS["heartgold-soulsilver"]
SPECIAL_ENCOUNTERS["crystal"] = SPECIAL_ENCOUNTERS["heartgold-soulsilver"]
SPECIAL_ENCOUNTERS["ruby-sapphire"] = SPECIAL_ENCOUNTERS["emerald"]
SPECIAL_ENCOUNTERS["omega-ruby-alpha-sapphire"] = SPECIAL_ENCOUNTERS["emerald"]

View File

@@ -52,6 +52,40 @@ const statusOptions: { value: EncounterStatus; label: string; color: string }[]
},
]
const specialMethodStyles: Record<string, { label: string; color: string }> = {
starter: {
label: 'Starter',
color:
'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/40 dark:text-yellow-300',
},
gift: {
label: 'Gift',
color: 'bg-pink-100 text-pink-800 dark:bg-pink-900/40 dark:text-pink-300',
},
fossil: {
label: 'Fossil',
color:
'bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-300',
},
trade: {
label: 'Trade',
color:
'bg-emerald-100 text-emerald-800 dark:bg-emerald-900/40 dark:text-emerald-300',
},
}
function EncounterMethodBadge({ method }: { method: string }) {
const config = specialMethodStyles[method]
if (!config) return null
return (
<span
className={`text-[9px] font-medium px-1.5 py-0.5 rounded-full ${config.color}`}
>
{config.label}
</span>
)
}
export function EncounterModal({
route,
existing,
@@ -198,6 +232,7 @@ export function EncounterModal({
<span className="text-xs text-gray-700 dark:text-gray-300 mt-1 capitalize">
{rp.pokemon.name}
</span>
<EncounterMethodBadge method={rp.encounterMethod} />
<span className="text-[10px] text-gray-400">
Lv. {rp.minLevel}
{rp.maxLevel !== rp.minLevel && `${rp.maxLevel}`}