Compare commits

...

5 Commits

Author SHA1 Message Date
22d72e8a34 Restrict workflow permissions to contents: read
All checks were successful
CI / backend-lint (pull_request) Successful in 9s
CI / actions-lint (pull_request) Successful in 15s
CI / frontend-lint (pull_request) Successful in 20s
All CI jobs and the deploy workflow only need to read repo contents.
Adding explicit top-level permissions satisfies zizmor's
excessive-permissions audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:34:07 +01:00
2675491216 Fix zizmor PATH issue in CI by using pipx run
Some checks failed
CI / backend-lint (pull_request) Successful in 9s
CI / actions-lint (pull_request) Failing after 15s
CI / frontend-lint (pull_request) Successful in 20s
pipx run executes directly without needing the binary on PATH.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:31:39 +01:00
e9100fa826 Fix zizmor install in CI by using pipx
Some checks failed
CI / backend-lint (pull_request) Successful in 9s
CI / actions-lint (pull_request) Failing after 15s
CI / frontend-lint (pull_request) Successful in 20s
Bare pip install fails on newer Ubuntu runners due to PEP 668
(externally-managed-environment). Use pipx which is pre-installed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:28:24 +01:00
acfe767214 Fix actionlint install in CI workflow
Some checks failed
CI / backend-lint (pull_request) Successful in 9s
CI / actions-lint (pull_request) Failing after 8s
CI / frontend-lint (pull_request) Successful in 20s
The download URL was missing the version number in the asset filename.
Use the official download script which handles version resolution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:19:11 +01:00
5240236759 Add per-condition encounter rates to seed data
Some checks failed
CI / backend-lint (pull_request) Successful in 9s
CI / actions-lint (pull_request) Failing after 6s
CI / frontend-lint (pull_request) Successful in 21s
Add a `condition` column to RouteEncounter so encounters can store
per-condition rates (time of day, season, weather) instead of flattening
to max(). Update the seed loader, API schemas, and frontend to support
the new `conditions` dict format in seed JSON.

Port the PoC branch's condition-aware EncounterModal UI with filter
tabs that let players see encounter rates for specific conditions.
Add horde/SOS as distinct encounter methods with their own badges.

Update the import tool to extract per-condition rates instead of
flattening, and add a merge script (tools/merge-conditions.py) that
enriches existing curated seed files with condition data from PokeDB.

Seed data updated for 22 games (5,684 encounters):
- Gen 2: Gold, Silver, Crystal (morning/day/night)
- Gen 4: HG, SS, Diamond, Pearl, Platinum, BD, SP (morning/day/night)
- Gen 5: Black, White, Black 2, White 2 (spring/summer/autumn/winter)
- Gen 7: Sun, Moon, Ultra Sun, Ultra Moon (day/night)
- Gen 8: Sword, Shield (weather)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 18:52:35 +01:00
38 changed files with 36723 additions and 11591 deletions

View File

@@ -1,11 +1,11 @@
---
# nuzlocke-tracker-4ni4
title: Fix seed data with encounter conditions
status: todo
status: completed
type: task
priority: high
created_at: 2026-02-17T07:37:25Z
updated_at: 2026-02-17T07:37:28Z
updated_at: 2026-02-17T17:52:29Z
parent: oqfo
---
@@ -15,94 +15,30 @@ Some Pokémon games have different encounter tables depending on time of day, we
The existing seed data has curated route ordering and normalized route names (from beans r48e, qvww, j28y) that must be preserved — only encounter condition data should be added/changed.
## Reference branch
**Branch:** \`feature/encounter-conditions\` (PoC)
Key changes on the PoC branch:
- **Backend model:** \`RouteEncounter\` gains a \`condition\` field (String(30), default \`""\`)
- **Migration:** \`c0d1e2f3a4_add_condition_to_route_encounters.py\` — adds condition column + updated unique constraint
- **Seed loader:** handles \`conditions\` dict format: \`{"morning": 50, "day": 20, "night": 0}\` per encounter
- **API/schema:** \`condition\` field exposed in route encounter responses
- **Frontend types:** \`RouteEncounter\` type gains \`condition: string\`
- **Frontend UI:** condition selector tabs and badges in \`RunEncounters\` and \`EncounterModal\`
## Seed data format
When an encounter has per-condition rates, the JSON uses a \`conditions\` dict instead of a flat \`encounter_rate\`:
\`\`\`json
{
"pokeapi_id": 163,
"pokemon_name": "Hoothoot",
"method": "walk",
"encounter_rate": null,
"conditions": {
"night": 50,
"morning": 10,
"day": 0
},
"min_level": 2,
"max_level": 5
}
\`\`\`
For encounters without variant rates, the existing flat \`encounter_rate\` field remains unchanged.
## Approach
### Phase 1: HeartGold (reference game)
Create complete encounter condition data for HeartGold first. This serves as the reference implementation and validates the full pipeline (seed → DB → API → UI).
HeartGold uses **morning/day/night** conditions for walking encounters. Source: PokeDB data (the import tool at \`tools/import-pokedb\` already has the raw per-condition rates, but \`extract_encounter_rate()\` currently flattens them to \`max()\`).
### Phase 2: All other games with conditions
Update the remaining games' seed data with encounter conditions, without changing route order or route names. Match encounters by route name + Pokémon + method and add the \`conditions\` dict.
## Condition types by game group
- **morning/day/night**: Gold, Silver, Crystal, HeartGold, SoulSilver, Diamond, Pearl, Platinum, Brilliant Diamond, Shining Pearl
- **spring/summer/autumn/winter**: Black, White, Black 2, White 2
- **weather (clear, overcast, rain, thunderstorm, snow, snowstorm, sandstorm, intense-sun, heavy-rain, fog)**: Sword, Shield
- **SOS calls**: Sun, Moon, Ultra Sun, Ultra Moon
- **No conditions (flat rates)**: Red, Blue, Yellow, Ruby, Sapphire, Emerald, FireRed, LeafGreen, X, Y, Omega Ruby, Alpha Sapphire, Let's Go Pikachu, Let's Go Eevee, Legends: Arceus, Scarlet, Violet, Legends: Z-A
## Checklist
### Infrastructure (merge from PoC)
- [ ] Merge backend model + migration for \`condition\` field on \`RouteEncounter\`
- [ ] Merge seed loader changes to handle \`conditions\` dict format
- [ ] Merge API/schema changes to expose \`condition\` field
- [ ] Merge frontend type updates (\`RouteEncounter.condition\`)
- [ ] Merge frontend UI (condition selector tabs/badges in RunEncounters & EncounterModal)
- [x] Merge backend model + migration for `condition` field on `RouteEncounter`
- [x] Merge seed loader changes to handle `conditions` dict format
- [x] Merge API/schema changes to expose `condition` field
- [x] Merge frontend type updates (`RouteEncounter.condition`)
- [x] Merge frontend UI (condition selector tabs/badges in EncounterModal)
- [x] Add horde/SOS method badges to EncounterMethodBadge
- [x] Add condition column to AdminRouteDetail
### Phase 1: HeartGold
- [ ] Update \`tools/import-pokedb\` to extract per-condition rates instead of flattening to \`max()\`
- [ ] Write a merge script that adds condition data to existing seed files without touching route names/order
- [ ] Generate and merge condition data for HeartGold
- [ ] Verify HeartGold seed data loads correctly and conditions display in the frontend
### Import tool updates
- [x] Update `tools/import-pokedb` to extract per-condition rates instead of flattening to `max()`
- [x] Fix encounter method mappings (horde, SOS as distinct methods)
- [x] Write merge script (`tools/merge-conditions.py`)
### Phase 2: Remaining games
- [ ] Gen 2: Gold, Silver, Crystal (morning/day/night)
- [ ] Gen 4: SoulSilver, Diamond, Pearl, Platinum, Brilliant Diamond, Shining Pearl (morning/day/night)
- [ ] Gen 5: Black, White, Black 2, White 2 (spring/summer/autumn/winter)
- [ ] Gen 7: Sun, Moon, Ultra Sun, Ultra Moon (SOS calls)
- [ ] Gen 8: Sword, Shield (weather conditions)
- [ ] Verify all updated games load correctly and show conditions in the UI
### Seed data updates
- [x] Gen 2: Gold, Silver, Crystal (morning/day/night)
- [x] Gen 4: HeartGold, SoulSilver, Diamond, Pearl, Platinum, Brilliant Diamond, Shining Pearl (morning/day/night)
- [x] Gen 5: Black, White, Black 2, White 2 (spring/summer/autumn/winter)
- [x] Gen 7: Sun, Moon, Ultra Sun, Ultra Moon (day/night)
- [x] Gen 8: Sword, Shield (weather conditions)
- [x] Verify all hooks pass (`prek run --all-files`)
## Success criteria
All games that have condition-dependent encounters show those conditions in the UI, so players can see what they can actually catch given their current game state (time of day, season, weather, etc.).
## Key files
- \`backend/src/app/models/route_encounter.py\` — RouteEncounter model
- \`backend/src/app/seeds/loader.py\` — seed loading logic
- \`backend/src/app/seeds/data/*.json\` — game encounter seed files
- \`tools/import-pokedb/import_pokedb/processing.py\` — \`extract_encounter_rate()\` flattens conditions
- \`tools/import-pokedb/import_pokedb/models.py\` — Encounter dataclass
- \`frontend/src/types/game.ts\` — RouteEncounter type
- \`frontend/src/pages/RunEncounters.tsx\` — encounter display with conditions
- \`frontend/src/components/EncounterModal.tsx\` — encounter registration with condition context
## Notes
- X/Y had no condition data in PokeDB (horde encounters already tracked as separate method)
- 5,684 encounters updated across 22 games

View File

@@ -18,6 +18,9 @@ on:
- ".gitignore"
- ".github/workflows/deploy.yml"
permissions:
contents: read
jobs:
backend-lint:
runs-on: ubuntu-latest
@@ -45,14 +48,12 @@ jobs:
persist-credentials: false
- name: Install actionlint
run: |
curl -sL https://github.com/rhysd/actionlint/releases/latest/download/actionlint_linux_amd64.tar.gz | tar xz
bash <(curl -sL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
sudo mv actionlint /usr/local/bin/
- name: Lint GitHub Actions
run: actionlint
- name: Install zizmor
run: pip install zizmor
- name: Audit GitHub Actions security
run: zizmor .github/workflows/
run: pipx run zizmor .github/workflows/
frontend-lint:
runs-on: ubuntu-latest

View File

@@ -3,6 +3,9 @@ name: Deploy
on:
workflow_dispatch:
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest

View File

@@ -0,0 +1,54 @@
"""add condition to route encounters
Revision ID: h9c0d1e2f3a4
Revises: g8b9c0d1e2f3
Create Date: 2026-02-17 12:00:00.000000
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "h9c0d1e2f3a4"
down_revision: str | Sequence[str] | None = "g8b9c0d1e2f3"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.add_column(
"route_encounters",
sa.Column(
"condition",
sa.String(30),
nullable=False,
server_default="",
),
)
op.drop_constraint(
"uq_route_pokemon_method_game",
"route_encounters",
type_="unique",
)
op.create_unique_constraint(
"uq_route_pokemon_method_game_condition",
"route_encounters",
["route_id", "pokemon_id", "encounter_method", "game_id", "condition"],
)
def downgrade() -> None:
op.drop_constraint(
"uq_route_pokemon_method_game_condition",
"route_encounters",
type_="unique",
)
op.create_unique_constraint(
"uq_route_pokemon_method_game",
"route_encounters",
["route_id", "pokemon_id", "encounter_method", "game_id"],
)
op.drop_column("route_encounters", "condition")

View File

@@ -69,8 +69,9 @@ async def export_game_routes(
game_encounters = [
enc for enc in route.route_encounters if enc.game_id == game_id
]
return [
{
result = []
for enc in sorted(game_encounters, key=lambda e: -e.encounter_rate):
entry: dict = {
"pokeapi_id": enc.pokemon.pokeapi_id,
"pokemon_name": enc.pokemon.name,
"method": enc.encounter_method,
@@ -78,8 +79,10 @@ async def export_game_routes(
"min_level": enc.min_level,
"max_level": enc.max_level,
}
for enc in sorted(game_encounters, key=lambda e: -e.encounter_rate)
]
if enc.condition:
entry["condition"] = enc.condition
result.append(entry)
return result
def format_route(route: Route) -> dict:
data: dict = {

View File

@@ -213,6 +213,7 @@ async def get_pokemon_encounter_locations(
route_name=enc.route.name,
encounter_method=enc.encounter_method,
encounter_rate=enc.encounter_rate,
condition=enc.condition,
min_level=enc.min_level,
max_level=enc.max_level,
)

View File

@@ -12,7 +12,8 @@ class RouteEncounter(Base):
"pokemon_id",
"encounter_method",
"game_id",
name="uq_route_pokemon_method_game",
"condition",
name="uq_route_pokemon_method_game_condition",
),
)
@@ -22,6 +23,7 @@ class RouteEncounter(Base):
game_id: Mapped[int] = mapped_column(ForeignKey("games.id"), index=True)
encounter_method: Mapped[str] = mapped_column(String(30))
encounter_rate: Mapped[int] = mapped_column(SmallInteger)
condition: Mapped[str] = mapped_column(String(30), default="", server_default="")
min_level: Mapped[int] = mapped_column(SmallInteger)
max_level: Mapped[int] = mapped_column(SmallInteger)

View File

@@ -42,6 +42,7 @@ class RouteEncounterResponse(CamelModel):
game_id: int
encounter_method: str
encounter_rate: int
condition: str = ""
min_level: int
max_level: int
@@ -55,6 +56,7 @@ class PokemonEncounterLocationItem(CamelModel):
route_name: str
encounter_method: str
encounter_rate: int
condition: str = ""
min_level: int
max_level: int
@@ -89,6 +91,7 @@ class RouteEncounterCreate(CamelModel):
game_id: int
encounter_method: str
encounter_rate: int
condition: str = ""
min_level: int
max_level: int
@@ -96,6 +99,7 @@ class RouteEncounterCreate(CamelModel):
class RouteEncounterUpdate(CamelModel):
encounter_method: str | None = None
encounter_rate: int | None = None
condition: str | None = None
min_level: int | None = None
max_level: int | None = None
@@ -178,6 +182,7 @@ class BulkRouteEncounterItem(BaseModel):
pokeapi_id: int
method: str
encounter_rate: int
condition: str = ""
min_level: int
max_level: int

View File

@@ -844,17 +844,25 @@
"pokeapi_id": 88,
"pokemon_name": "Grimer",
"method": "surf",
"encounter_rate": 100,
"encounter_rate": null,
"min_level": 5,
"max_level": 20
"max_level": 20,
"conditions": {
"spring": 100,
"summer": 100
}
},
{
"pokeapi_id": 88,
"pokemon_name": "Grimer",
"method": "fishing",
"encounter_rate": 100,
"encounter_rate": null,
"min_level": 40,
"max_level": 70
"max_level": 70,
"conditions": {
"spring": 100,
"summer": 100
}
},
{
"pokeapi_id": 19,
@@ -884,17 +892,25 @@
"pokeapi_id": 89,
"pokemon_name": "Muk",
"method": "fishing",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 50,
"max_level": 70
"max_level": 70,
"conditions": {
"spring": 10,
"summer": 10
}
},
{
"pokeapi_id": 89,
"pokemon_name": "Muk",
"method": "surf",
"encounter_rate": 5,
"encounter_rate": null,
"min_level": 5,
"max_level": 20
"max_level": 20,
"conditions": {
"spring": 5,
"summer": 5
}
}
]
},
@@ -3294,41 +3310,63 @@
"pokeapi_id": 593,
"pokemon_name": "Jellicent",
"method": "surf",
"encounter_rate": 60,
"encounter_rate": null,
"min_level": 25,
"max_level": 40
"max_level": 40,
"conditions": {
"spring": 60,
"summer": 60,
"autumn": 60
}
},
{
"pokeapi_id": 320,
"pokemon_name": "Wailmer",
"method": "surf",
"encounter_rate": 60,
"encounter_rate": null,
"min_level": 25,
"max_level": 40
"max_level": 40,
"conditions": {
"spring": 30,
"summer": 30,
"autumn": 30,
"winter": 60
}
},
{
"pokeapi_id": 458,
"pokemon_name": "Mantyke",
"method": "surf",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 25,
"max_level": 40
"max_level": 40,
"conditions": {
"spring": 30,
"summer": 30,
"autumn": 30
}
},
{
"pokeapi_id": 364,
"pokemon_name": "Sealeo",
"method": "surf",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 25,
"max_level": 40
"max_level": 40,
"conditions": {
"winter": 30
}
},
{
"pokeapi_id": 363,
"pokemon_name": "Spheal",
"method": "surf",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 25,
"max_level": 40
"max_level": 40,
"conditions": {
"winter": 30
}
},
{
"pokeapi_id": 171,
@@ -3342,9 +3380,14 @@
"pokeapi_id": 226,
"pokemon_name": "Mantine",
"method": "surf",
"encounter_rate": 5,
"encounter_rate": null,
"min_level": 30,
"max_level": 40
"max_level": 40,
"conditions": {
"spring": 5,
"summer": 5,
"autumn": 5
}
},
{
"pokeapi_id": 224,
@@ -3374,9 +3417,12 @@
"pokeapi_id": 365,
"pokemon_name": "Walrein",
"method": "surf",
"encounter_rate": 5,
"encounter_rate": null,
"min_level": 30,
"max_level": 40
"max_level": 40,
"conditions": {
"winter": 5
}
}
]
},
@@ -7642,33 +7688,53 @@
"pokeapi_id": 536,
"pokemon_name": "Palpitoad",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"spring": 40,
"summer": 40,
"autumn": 40
}
},
{
"pokeapi_id": 616,
"pokemon_name": "Shelmet",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 54,
"max_level": 54
"max_level": 54,
"conditions": {
"spring": 20,
"summer": 20,
"autumn": 20
}
},
{
"pokeapi_id": 618,
"pokemon_name": "Stunfisk",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 55,
"max_level": 56
"max_level": 56,
"conditions": {
"spring": 20,
"summer": 20,
"autumn": 20
}
},
{
"pokeapi_id": 453,
"pokemon_name": "Croagunk",
"method": "walk",
"encounter_rate": 15,
"encounter_rate": null,
"min_level": 55,
"max_level": 56
"max_level": 56,
"conditions": {
"spring": 15,
"summer": 15,
"autumn": 15
}
},
{
"pokeapi_id": 340,
@@ -7682,9 +7748,14 @@
"pokeapi_id": 588,
"pokemon_name": "Karrablast",
"method": "walk",
"encounter_rate": 5,
"encounter_rate": null,
"min_level": 57,
"max_level": 57
"max_level": 57,
"conditions": {
"spring": 5,
"summer": 5,
"autumn": 5
}
},
{
"pokeapi_id": 537,
@@ -7728,33 +7799,53 @@
"pokeapi_id": 536,
"pokemon_name": "Palpitoad",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"spring": 40,
"summer": 40,
"autumn": 40
}
},
{
"pokeapi_id": 616,
"pokemon_name": "Shelmet",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 54,
"max_level": 54
"max_level": 54,
"conditions": {
"spring": 20,
"summer": 20,
"autumn": 20
}
},
{
"pokeapi_id": 618,
"pokemon_name": "Stunfisk",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 55,
"max_level": 56
"max_level": 56,
"conditions": {
"spring": 20,
"summer": 20,
"autumn": 20
}
},
{
"pokeapi_id": 453,
"pokemon_name": "Croagunk",
"method": "walk",
"encounter_rate": 15,
"encounter_rate": null,
"min_level": 55,
"max_level": 56
"max_level": 56,
"conditions": {
"spring": 15,
"summer": 15,
"autumn": 15
}
},
{
"pokeapi_id": 340,
@@ -7768,9 +7859,14 @@
"pokeapi_id": 588,
"pokemon_name": "Karrablast",
"method": "walk",
"encounter_rate": 5,
"encounter_rate": null,
"min_level": 57,
"max_level": 57
"max_level": 57,
"conditions": {
"spring": 5,
"summer": 5,
"autumn": 5
}
},
{
"pokeapi_id": 537,

View File

@@ -1486,7 +1486,7 @@
]
},
{
"name": "Relic Castle (Volcarona\u2019s Room and Room Outside)",
"name": "Relic Castle (Volcaronas Room and Room Outside)",
"order": 30,
"encounters": [
{
@@ -2971,25 +2971,40 @@
"pokeapi_id": 536,
"pokemon_name": "Palpitoad",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 30,
"max_level": 33
"max_level": 33,
"conditions": {
"spring": 40,
"summer": 40,
"autumn": 40
}
},
{
"pokeapi_id": 616,
"pokemon_name": "Shelmet",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 30,
"max_level": 33
"max_level": 33,
"conditions": {
"spring": 40,
"summer": 40,
"autumn": 40
}
},
{
"pokeapi_id": 618,
"pokemon_name": "Stunfisk",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 31,
"max_level": 32
"max_level": 32,
"conditions": {
"spring": 20,
"summer": 20,
"autumn": 20
}
},
{
"pokeapi_id": 340,
@@ -3439,25 +3454,40 @@
"pokeapi_id": 536,
"pokemon_name": "Palpitoad",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 30,
"max_level": 33
"max_level": 33,
"conditions": {
"spring": 40,
"summer": 40,
"autumn": 40
}
},
{
"pokeapi_id": 616,
"pokemon_name": "Shelmet",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 30,
"max_level": 33
"max_level": 33,
"conditions": {
"spring": 40,
"summer": 40,
"autumn": 40
}
},
{
"pokeapi_id": 618,
"pokemon_name": "Stunfisk",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 31,
"max_level": 32
"max_level": 32,
"conditions": {
"spring": 20,
"summer": 20,
"autumn": 20
}
},
{
"pokeapi_id": 340,
@@ -5630,9 +5660,12 @@
"pokeapi_id": 446,
"pokemon_name": "Munchlax",
"method": "trade",
"encounter_rate": 100,
"encounter_rate": null,
"min_level": 60,
"max_level": 60
"max_level": 60,
"conditions": {
"summer": 100
}
},
{
"pokeapi_id": 90,
@@ -5740,9 +5773,15 @@
"pokeapi_id": 320,
"pokemon_name": "Wailmer",
"method": "surf",
"encounter_rate": 90,
"encounter_rate": null,
"min_level": 25,
"max_level": 60
"max_level": 60,
"conditions": {
"spring": 90,
"summer": 90,
"autumn": 90,
"winter": 60
}
},
{
"pokeapi_id": 223,
@@ -5772,25 +5811,36 @@
"pokeapi_id": 458,
"pokemon_name": "Mantyke",
"method": "surf",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 25,
"max_level": 55
"max_level": 55,
"conditions": {
"spring": 30,
"summer": 30,
"autumn": 30
}
},
{
"pokeapi_id": 364,
"pokemon_name": "Sealeo",
"method": "surf",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 25,
"max_level": 60
"max_level": 60,
"conditions": {
"winter": 30
}
},
{
"pokeapi_id": 363,
"pokemon_name": "Spheal",
"method": "surf",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 25,
"max_level": 55
"max_level": 55,
"conditions": {
"winter": 30
}
},
{
"pokeapi_id": 279,
@@ -5812,9 +5862,14 @@
"pokeapi_id": 226,
"pokemon_name": "Mantine",
"method": "surf",
"encounter_rate": 5,
"encounter_rate": null,
"min_level": 25,
"max_level": 60
"max_level": 60,
"conditions": {
"spring": 5,
"summer": 5,
"autumn": 5
}
},
{
"pokeapi_id": 224,
@@ -5836,9 +5891,12 @@
"pokeapi_id": 365,
"pokemon_name": "Walrein",
"method": "surf",
"encounter_rate": 5,
"encounter_rate": null,
"min_level": 25,
"max_level": 70
"max_level": 70,
"conditions": {
"winter": 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

View File

@@ -35,7 +35,7 @@
"encounters": [],
"children": [
{
"name": "Alola Route 1 (First two fields east of the player\u2019s house)",
"name": "Alola Route 1 (First two fields east of the players house)",
"order": 3,
"encounters": [
{
@@ -368,7 +368,7 @@
]
},
{
"name": "Trainer\u2019s School (Alola)",
"name": "Trainers School (Alola)",
"order": 8,
"encounters": [
{
@@ -709,17 +709,23 @@
"pokeapi_id": 425,
"pokemon_name": "Drifloon",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 7,
"max_level": 10
"max_level": 10,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 200,
"pokemon_name": "Misdreavus",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 7,
"max_level": 10
"max_level": 10,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 41,
@@ -1089,17 +1095,23 @@
"pokeapi_id": 10091,
"pokemon_name": "Rattata (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 15,
"max_level": 18
"max_level": 18,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 734,
"pokemon_name": "Yungoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 15,
"max_level": 18
"max_level": 18,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 79,
@@ -1686,9 +1698,12 @@
"pokeapi_id": 751,
"pokemon_name": "Dewpider",
"method": "surf",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 40
}
},
{
"pokeapi_id": 60,
@@ -1702,9 +1717,12 @@
"pokeapi_id": 283,
"pokemon_name": "Surskit",
"method": "surf",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"night": 40
}
},
{
"pokeapi_id": 54,
@@ -1740,25 +1758,34 @@
"pokeapi_id": 755,
"pokemon_name": "Morelull",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 46,
"pokemon_name": "Paras",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 751,
"pokemon_name": "Dewpider",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 10
}
},
{
"pokeapi_id": 60,
@@ -1772,9 +1799,12 @@
"pokeapi_id": 283,
"pokemon_name": "Surskit",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"night": 10
}
},
{
"pokeapi_id": 278,
@@ -1810,9 +1840,12 @@
"pokeapi_id": 751,
"pokemon_name": "Dewpider",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 60,
@@ -1826,9 +1859,12 @@
"pokeapi_id": 283,
"pokemon_name": "Surskit",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"night": 20
}
}
]
},
@@ -2742,17 +2778,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 20,
"max_level": 23
"max_level": 23,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 20,
"max_level": 23
"max_level": 23,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 299,
@@ -2920,33 +2962,45 @@
"pokeapi_id": 752,
"pokemon_name": "Araquanid",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 24,
"max_level": 27
"max_level": 27,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 168,
"pokemon_name": "Ariados",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 24,
"max_level": 27
"max_level": 27,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 166,
"pokemon_name": "Ledian",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 24,
"max_level": 27
"max_level": 27,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 284,
"pokemon_name": "Masquerain",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 24,
"max_level": 27
"max_level": 27,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 10107,
@@ -3076,9 +3130,13 @@
"pokeapi_id": 22,
"pokemon_name": "Fearow",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 25,
"max_level": 28
"max_level": 28,
"conditions": {
"day": 40,
"night": 30
}
},
{
"pokeapi_id": 10136,
@@ -3100,9 +3158,12 @@
"pokeapi_id": 173,
"pokemon_name": "Cleffa",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 25,
"max_level": 28
"max_level": 28,
"conditions": {
"night": 10
}
},
{
"pokeapi_id": 132,
@@ -3531,9 +3592,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 28,
"max_level": 31
"max_level": 31,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 279,
@@ -3547,9 +3611,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 28,
"max_level": 31
"max_level": 31,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 361,
@@ -3871,17 +3938,23 @@
"pokeapi_id": 168,
"pokemon_name": "Ariados",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 31,
"max_level": 34
"max_level": 34,
"conditions": {
"night": 40
}
},
{
"pokeapi_id": 166,
"pokemon_name": "Ledian",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 31,
"max_level": 34
"max_level": 34,
"conditions": {
"day": 40
}
},
{
"pokeapi_id": 741,
@@ -4103,9 +4176,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 40,
"max_level": 43
"max_level": 43,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 279,
@@ -4119,9 +4195,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 40,
"max_level": 43
"max_level": 43,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 73,
@@ -4213,9 +4292,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 40,
"max_level": 43
"max_level": 43,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 279,
@@ -4229,9 +4311,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 40,
"max_level": 43
"max_level": 43,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 210,
@@ -4706,17 +4791,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 52,
"max_level": 55
"max_level": 55,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 52,
"max_level": 55
"max_level": 55,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 732,
@@ -4792,9 +4883,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 70,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 70
}
},
{
"pokeapi_id": 548,
@@ -4808,9 +4902,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 70,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 70
}
},
{
"pokeapi_id": 297,
@@ -4854,17 +4951,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 732,
@@ -4908,17 +5011,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 97,
@@ -4994,9 +5103,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 10
}
},
{
"pokeapi_id": 241,
@@ -5010,9 +5122,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 10
}
},
{
"pokeapi_id": 128,
@@ -5040,17 +5155,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 279,
@@ -5220,9 +5341,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 56,
"max_level": 59
"max_level": 59,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 279,
@@ -5236,9 +5360,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 56,
"max_level": 59
"max_level": 59,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 210,

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

@@ -35,7 +35,7 @@
"encounters": [],
"children": [
{
"name": "Alola Route 1 (First two fields east of the player\u2019s house)",
"name": "Alola Route 1 (First two fields east of the players house)",
"order": 3,
"encounters": [
{
@@ -368,7 +368,7 @@
]
},
{
"name": "Trainer\u2019s School (Alola)",
"name": "Trainers School (Alola)",
"order": 8,
"encounters": [
{
@@ -709,17 +709,23 @@
"pokeapi_id": 425,
"pokemon_name": "Drifloon",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 7,
"max_level": 10
"max_level": 10,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 200,
"pokemon_name": "Misdreavus",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 7,
"max_level": 10
"max_level": 10,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 41,
@@ -1105,17 +1111,23 @@
"pokeapi_id": 10091,
"pokemon_name": "Rattata (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 15,
"max_level": 18
"max_level": 18,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 734,
"pokemon_name": "Yungoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 15,
"max_level": 18
"max_level": 18,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 79,
@@ -1702,9 +1714,12 @@
"pokeapi_id": 751,
"pokemon_name": "Dewpider",
"method": "surf",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 40
}
},
{
"pokeapi_id": 60,
@@ -1718,9 +1733,12 @@
"pokeapi_id": 283,
"pokemon_name": "Surskit",
"method": "surf",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"night": 40
}
},
{
"pokeapi_id": 54,
@@ -1756,25 +1774,34 @@
"pokeapi_id": 755,
"pokemon_name": "Morelull",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 46,
"pokemon_name": "Paras",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 751,
"pokemon_name": "Dewpider",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 10
}
},
{
"pokeapi_id": 60,
@@ -1788,9 +1815,12 @@
"pokeapi_id": 283,
"pokemon_name": "Surskit",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"night": 10
}
},
{
"pokeapi_id": 278,
@@ -1826,9 +1856,12 @@
"pokeapi_id": 751,
"pokemon_name": "Dewpider",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 60,
@@ -1842,9 +1875,12 @@
"pokeapi_id": 283,
"pokemon_name": "Surskit",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"night": 20
}
}
]
},
@@ -2758,17 +2794,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 20,
"max_level": 23
"max_level": 23,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 20,
"max_level": 23
"max_level": 23,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 299,
@@ -2944,33 +2986,45 @@
"pokeapi_id": 752,
"pokemon_name": "Araquanid",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 24,
"max_level": 27
"max_level": 27,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 168,
"pokemon_name": "Ariados",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 24,
"max_level": 27
"max_level": 27,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 166,
"pokemon_name": "Ledian",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 24,
"max_level": 27
"max_level": 27,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 284,
"pokemon_name": "Masquerain",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 24,
"max_level": 27
"max_level": 27,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 10107,
@@ -3100,9 +3154,13 @@
"pokeapi_id": 22,
"pokemon_name": "Fearow",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 25,
"max_level": 28
"max_level": 28,
"conditions": {
"day": 40,
"night": 30
}
},
{
"pokeapi_id": 10136,
@@ -3124,9 +3182,12 @@
"pokeapi_id": 173,
"pokemon_name": "Cleffa",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 25,
"max_level": 28
"max_level": 28,
"conditions": {
"night": 10
}
},
{
"pokeapi_id": 132,
@@ -3571,9 +3632,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 28,
"max_level": 31
"max_level": 31,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 279,
@@ -3587,9 +3651,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 28,
"max_level": 31
"max_level": 31,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 361,
@@ -3911,17 +3978,23 @@
"pokeapi_id": 168,
"pokemon_name": "Ariados",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 31,
"max_level": 34
"max_level": 34,
"conditions": {
"night": 40
}
},
{
"pokeapi_id": 166,
"pokemon_name": "Ledian",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 31,
"max_level": 34
"max_level": 34,
"conditions": {
"day": 40
}
},
{
"pokeapi_id": 741,
@@ -4143,9 +4216,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 40,
"max_level": 43
"max_level": 43,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 279,
@@ -4159,9 +4235,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 40,
"max_level": 43
"max_level": 43,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 73,
@@ -4253,9 +4332,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 40,
"max_level": 43
"max_level": 43,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 279,
@@ -4269,9 +4351,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 40,
"max_level": 43
"max_level": 43,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 210,
@@ -4729,17 +4814,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 52,
"max_level": 55
"max_level": 55,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 52,
"max_level": 55
"max_level": 55,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 732,
@@ -4823,17 +4914,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 70,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 70
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 70,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 70
}
},
{
"pokeapi_id": 628,
@@ -4877,17 +4974,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 732,
@@ -4931,17 +5034,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 546,
@@ -5017,9 +5126,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 10
}
},
{
"pokeapi_id": 241,
@@ -5033,9 +5145,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 10
}
},
{
"pokeapi_id": 128,
@@ -5063,17 +5178,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 546,
@@ -5243,9 +5364,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 56,
"max_level": 59
"max_level": 59,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 279,
@@ -5259,9 +5383,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 56,
"max_level": 59
"max_level": 59,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 210,

File diff suppressed because it is too large Load Diff

View File

@@ -60,7 +60,7 @@
],
"children": [
{
"name": "Alola Route 1 (First two fields east of the player\u2019s house)",
"name": "Alola Route 1 (First two fields east of the players house)",
"order": 3,
"encounters": [
{
@@ -377,7 +377,7 @@
]
},
{
"name": "Trainer\u2019s School (Alola)",
"name": "Trainers School (Alola)",
"order": 8,
"encounters": [
{
@@ -773,7 +773,7 @@
]
},
{
"name": "Alola Route 2 (Two patches of grass southwest of the Pok\u00e9mon Center)",
"name": "Alola Route 2 (Two patches of grass southwest of the Pokémon Center)",
"order": 15,
"encounters": [
{
@@ -922,9 +922,12 @@
"pokeapi_id": 425,
"pokemon_name": "Drifloon",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 6,
"max_level": 9
"max_level": 9,
"conditions": {
"day": 40
}
},
{
"pokeapi_id": 92,
@@ -938,9 +941,12 @@
"pokeapi_id": 198,
"pokemon_name": "Murkrow",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 6,
"max_level": 9
"max_level": 9,
"conditions": {
"night": 40
}
},
{
"pokeapi_id": 41,
@@ -1342,17 +1348,23 @@
"pokeapi_id": 10091,
"pokemon_name": "Rattata (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 15,
"max_level": 18
"max_level": 18,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 734,
"pokemon_name": "Yungoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 15,
"max_level": 18
"max_level": 18,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 456,
@@ -1591,9 +1603,13 @@
"pokeapi_id": 366,
"pokemon_name": "Clamperl",
"method": "fishing",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 10,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 5,
"night": 5
}
},
{
"pokeapi_id": 222,
@@ -1613,17 +1629,25 @@
"pokeapi_id": 366,
"pokemon_name": "Clamperl",
"method": "fishing",
"encounter_rate": 55,
"encounter_rate": null,
"min_level": 10,
"max_level": 22
"max_level": 22,
"conditions": {
"day": 35,
"night": 20
}
},
{
"pokeapi_id": 222,
"pokemon_name": "Corsola",
"method": "fishing",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 10,
"max_level": 22
"max_level": 22,
"conditions": {
"day": 5,
"night": 20
}
},
{
"pokeapi_id": 370,
@@ -1707,9 +1731,13 @@
"pokeapi_id": 366,
"pokemon_name": "Clamperl",
"method": "fishing",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 10,
"max_level": 22
"max_level": 22,
"conditions": {
"day": 5,
"night": 5
}
}
]
}
@@ -2096,9 +2124,12 @@
"pokeapi_id": 751,
"pokemon_name": "Dewpider",
"method": "surf",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 40
}
},
{
"pokeapi_id": 60,
@@ -2112,33 +2143,45 @@
"pokeapi_id": 283,
"pokemon_name": "Surskit",
"method": "surf",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"night": 40
}
},
{
"pokeapi_id": 751,
"pokemon_name": "Dewpider",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 755,
"pokemon_name": "Morelull",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 46,
"pokemon_name": "Paras",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 60,
@@ -2168,9 +2211,12 @@
"pokeapi_id": 283,
"pokemon_name": "Surskit",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 278,
@@ -3263,9 +3309,13 @@
"pokeapi_id": 278,
"pokemon_name": "Wingull",
"method": "walk",
"encounter_rate": 50,
"encounter_rate": null,
"min_level": 21,
"max_level": 24
"max_level": 24,
"conditions": {
"day": 30,
"night": 50
}
},
{
"pokeapi_id": 170,
@@ -3287,17 +3337,23 @@
"pokeapi_id": 177,
"pokemon_name": "Natu",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 21,
"max_level": 24
"max_level": 24,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 21,
"max_level": 24
"max_level": 24,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 299,
@@ -3341,9 +3397,13 @@
"pokeapi_id": 771,
"pokemon_name": "Pyukumuku",
"method": "surf",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 22,
"max_level": 25
"max_level": 25,
"conditions": {
"day": 30,
"night": 30
}
},
{
"pokeapi_id": 456,
@@ -3457,33 +3517,45 @@
"pokeapi_id": 752,
"pokemon_name": "Araquanid",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 25,
"max_level": 28
"max_level": 28,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 168,
"pokemon_name": "Ariados",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 25,
"max_level": 28
"max_level": 28,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 166,
"pokemon_name": "Ledian",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 25,
"max_level": 28
"max_level": 28,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 284,
"pokemon_name": "Masquerain",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 25,
"max_level": 28
"max_level": 28,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 10107,
@@ -3861,9 +3933,13 @@
"pokeapi_id": 737,
"pokemon_name": "Charjabug",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 29,
"max_level": 32
"max_level": 32,
"conditions": {
"day": 10,
"night": 10
}
},
{
"pokeapi_id": 10110,
@@ -4013,9 +4089,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 30,
"max_level": 33
"max_level": 33,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 279,
@@ -4029,9 +4108,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 30,
"max_level": 33
"max_level": 33,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 361,
@@ -4415,9 +4497,12 @@
"pokeapi_id": 168,
"pokemon_name": "Ariados",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 33,
"max_level": 36
"max_level": 36,
"conditions": {
"night": 40
}
},
{
"pokeapi_id": 670,
@@ -4431,9 +4516,12 @@
"pokeapi_id": 166,
"pokemon_name": "Ledian",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 33,
"max_level": 36
"max_level": 36,
"conditions": {
"day": 40
}
},
{
"pokeapi_id": 741,
@@ -5418,9 +5506,12 @@
"pokeapi_id": 427,
"pokemon_name": "Buneary",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 52,
"max_level": 55
"max_level": 55,
"conditions": {
"night": 10
}
},
{
"pokeapi_id": 732,
@@ -5458,9 +5549,12 @@
"pokeapi_id": 447,
"pokemon_name": "Riolu",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 52,
"max_level": 55
"max_level": 55,
"conditions": {
"day": 10
}
}
]
},
@@ -5559,9 +5653,13 @@
"pokeapi_id": 743,
"pokemon_name": "Ribombee",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 30,
"night": 20
}
},
{
"pokeapi_id": 670,
@@ -5583,9 +5681,12 @@
"pokeapi_id": 200,
"pokemon_name": "Misdreavus",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 10
}
}
]
},
@@ -5621,17 +5722,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 70,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 70
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 70,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 70
}
},
{
"pokeapi_id": 57,
@@ -5707,17 +5814,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 732,
@@ -5761,33 +5874,47 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 297,
"pokemon_name": "Hariyama",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 20,
"night": 10
}
},
{
"pokeapi_id": 97,
"pokemon_name": "Hypno",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 10,
"night": 20
}
},
{
"pokeapi_id": 241,
@@ -5855,9 +5982,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 10
}
},
{
"pokeapi_id": 241,
@@ -5871,9 +6001,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 10
}
},
{
"pokeapi_id": 128,
@@ -5901,17 +6034,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 279,
@@ -6696,7 +6835,7 @@
]
},
{
"name": "Team Rocket\u2019s Castle",
"name": "Team Rockets Castle",
"order": 124,
"encounters": [
{

View File

@@ -60,7 +60,7 @@
],
"children": [
{
"name": "Alola Route 1 (First two fields east of the player\u2019s house)",
"name": "Alola Route 1 (First two fields east of the players house)",
"order": 3,
"encounters": [
{
@@ -377,7 +377,7 @@
]
},
{
"name": "Trainer\u2019s School (Alola)",
"name": "Trainers School (Alola)",
"order": 8,
"encounters": [
{
@@ -773,7 +773,7 @@
]
},
{
"name": "Alola Route 2 (Two patches of grass southwest of the Pok\u00e9mon Center)",
"name": "Alola Route 2 (Two patches of grass southwest of the Pokémon Center)",
"order": 15,
"encounters": [
{
@@ -922,9 +922,12 @@
"pokeapi_id": 425,
"pokemon_name": "Drifloon",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 6,
"max_level": 9
"max_level": 9,
"conditions": {
"day": 40
}
},
{
"pokeapi_id": 92,
@@ -938,9 +941,12 @@
"pokeapi_id": 198,
"pokemon_name": "Murkrow",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 6,
"max_level": 9
"max_level": 9,
"conditions": {
"night": 40
}
},
{
"pokeapi_id": 41,
@@ -1342,17 +1348,23 @@
"pokeapi_id": 10091,
"pokemon_name": "Rattata (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 15,
"max_level": 18
"max_level": 18,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 734,
"pokemon_name": "Yungoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 15,
"max_level": 18
"max_level": 18,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 456,
@@ -1591,9 +1603,13 @@
"pokeapi_id": 366,
"pokemon_name": "Clamperl",
"method": "fishing",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 10,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 5,
"night": 5
}
},
{
"pokeapi_id": 222,
@@ -1613,17 +1629,25 @@
"pokeapi_id": 366,
"pokemon_name": "Clamperl",
"method": "fishing",
"encounter_rate": 55,
"encounter_rate": null,
"min_level": 10,
"max_level": 22
"max_level": 22,
"conditions": {
"day": 35,
"night": 20
}
},
{
"pokeapi_id": 222,
"pokemon_name": "Corsola",
"method": "fishing",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 10,
"max_level": 22
"max_level": 22,
"conditions": {
"day": 5,
"night": 20
}
},
{
"pokeapi_id": 370,
@@ -1707,9 +1731,13 @@
"pokeapi_id": 366,
"pokemon_name": "Clamperl",
"method": "fishing",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 10,
"max_level": 22
"max_level": 22,
"conditions": {
"day": 5,
"night": 5
}
}
]
}
@@ -2096,9 +2124,12 @@
"pokeapi_id": 751,
"pokemon_name": "Dewpider",
"method": "surf",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 40
}
},
{
"pokeapi_id": 60,
@@ -2112,33 +2143,45 @@
"pokeapi_id": 283,
"pokemon_name": "Surskit",
"method": "surf",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"night": 40
}
},
{
"pokeapi_id": 751,
"pokemon_name": "Dewpider",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 755,
"pokemon_name": "Morelull",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 46,
"pokemon_name": "Paras",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 60,
@@ -2168,9 +2211,12 @@
"pokeapi_id": 283,
"pokemon_name": "Surskit",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 14,
"max_level": 17
"max_level": 17,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 278,
@@ -3263,9 +3309,13 @@
"pokeapi_id": 278,
"pokemon_name": "Wingull",
"method": "walk",
"encounter_rate": 50,
"encounter_rate": null,
"min_level": 21,
"max_level": 24
"max_level": 24,
"conditions": {
"day": 30,
"night": 50
}
},
{
"pokeapi_id": 170,
@@ -3287,17 +3337,23 @@
"pokeapi_id": 177,
"pokemon_name": "Natu",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 21,
"max_level": 24
"max_level": 24,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 21,
"max_level": 24
"max_level": 24,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 299,
@@ -3341,9 +3397,13 @@
"pokeapi_id": 771,
"pokemon_name": "Pyukumuku",
"method": "surf",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 22,
"max_level": 25
"max_level": 25,
"conditions": {
"day": 30,
"night": 30
}
},
{
"pokeapi_id": 456,
@@ -3457,33 +3517,45 @@
"pokeapi_id": 752,
"pokemon_name": "Araquanid",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 25,
"max_level": 28
"max_level": 28,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 168,
"pokemon_name": "Ariados",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 25,
"max_level": 28
"max_level": 28,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 166,
"pokemon_name": "Ledian",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 25,
"max_level": 28
"max_level": 28,
"conditions": {
"day": 20
}
},
{
"pokeapi_id": 284,
"pokemon_name": "Masquerain",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 25,
"max_level": 28
"max_level": 28,
"conditions": {
"night": 20
}
},
{
"pokeapi_id": 10107,
@@ -3861,9 +3933,13 @@
"pokeapi_id": 737,
"pokemon_name": "Charjabug",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 29,
"max_level": 32
"max_level": 32,
"conditions": {
"day": 10,
"night": 10
}
},
{
"pokeapi_id": 10110,
@@ -4021,9 +4097,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 30,
"max_level": 33
"max_level": 33,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 279,
@@ -4037,9 +4116,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 30,
"max_level": 33
"max_level": 33,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 361,
@@ -4423,9 +4505,12 @@
"pokeapi_id": 168,
"pokemon_name": "Ariados",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 33,
"max_level": 36
"max_level": 36,
"conditions": {
"night": 40
}
},
{
"pokeapi_id": 546,
@@ -4447,9 +4532,12 @@
"pokeapi_id": 166,
"pokemon_name": "Ledian",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 33,
"max_level": 36
"max_level": 36,
"conditions": {
"day": 40
}
},
{
"pokeapi_id": 741,
@@ -5427,9 +5515,12 @@
"pokeapi_id": 427,
"pokemon_name": "Buneary",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 52,
"max_level": 55
"max_level": 55,
"conditions": {
"night": 10
}
},
{
"pokeapi_id": 732,
@@ -5467,9 +5558,12 @@
"pokeapi_id": 447,
"pokemon_name": "Riolu",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 52,
"max_level": 55
"max_level": 55,
"conditions": {
"day": 10
}
}
]
},
@@ -5568,9 +5662,13 @@
"pokeapi_id": 743,
"pokemon_name": "Ribombee",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 30,
"night": 20
}
},
{
"pokeapi_id": 670,
@@ -5592,9 +5690,12 @@
"pokeapi_id": 200,
"pokemon_name": "Misdreavus",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 10
}
}
]
},
@@ -5630,17 +5731,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 70,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 70
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 70,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 70
}
},
{
"pokeapi_id": 57,
@@ -5716,17 +5823,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 732,
@@ -5770,33 +5883,47 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 297,
"pokemon_name": "Hariyama",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 20,
"night": 10
}
},
{
"pokeapi_id": 97,
"pokemon_name": "Hypno",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 10,
"night": 20
}
},
{
"pokeapi_id": 546,
@@ -5864,9 +5991,12 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 10
}
},
{
"pokeapi_id": 241,
@@ -5880,9 +6010,12 @@
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 10
}
},
{
"pokeapi_id": 128,
@@ -5910,17 +6043,23 @@
"pokeapi_id": 735,
"pokemon_name": "Gumshoos",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"day": 30
}
},
{
"pokeapi_id": 10092,
"pokemon_name": "Raticate (Alola)",
"method": "walk",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"night": 30
}
},
{
"pokeapi_id": 546,
@@ -6705,7 +6844,7 @@
]
},
{
"name": "Team Rocket\u2019s Castle",
"name": "Team Rockets Castle",
"order": 124,
"encounters": [
{

View File

@@ -844,17 +844,25 @@
"pokeapi_id": 88,
"pokemon_name": "Grimer",
"method": "surf",
"encounter_rate": 100,
"encounter_rate": null,
"min_level": 5,
"max_level": 20
"max_level": 20,
"conditions": {
"spring": 100,
"summer": 100
}
},
{
"pokeapi_id": 88,
"pokemon_name": "Grimer",
"method": "fishing",
"encounter_rate": 100,
"encounter_rate": null,
"min_level": 40,
"max_level": 70
"max_level": 70,
"conditions": {
"spring": 100,
"summer": 100
}
},
{
"pokeapi_id": 19,
@@ -884,17 +892,25 @@
"pokeapi_id": 89,
"pokemon_name": "Muk",
"method": "fishing",
"encounter_rate": 10,
"encounter_rate": null,
"min_level": 50,
"max_level": 70
"max_level": 70,
"conditions": {
"spring": 10,
"summer": 10
}
},
{
"pokeapi_id": 89,
"pokemon_name": "Muk",
"method": "surf",
"encounter_rate": 5,
"encounter_rate": null,
"min_level": 5,
"max_level": 20
"max_level": 20,
"conditions": {
"spring": 5,
"summer": 5
}
}
]
},
@@ -3294,41 +3310,63 @@
"pokeapi_id": 593,
"pokemon_name": "Jellicent",
"method": "surf",
"encounter_rate": 60,
"encounter_rate": null,
"min_level": 25,
"max_level": 40
"max_level": 40,
"conditions": {
"spring": 60,
"summer": 60,
"autumn": 60
}
},
{
"pokeapi_id": 320,
"pokemon_name": "Wailmer",
"method": "surf",
"encounter_rate": 60,
"encounter_rate": null,
"min_level": 25,
"max_level": 40
"max_level": 40,
"conditions": {
"spring": 30,
"summer": 30,
"autumn": 30,
"winter": 60
}
},
{
"pokeapi_id": 458,
"pokemon_name": "Mantyke",
"method": "surf",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 25,
"max_level": 40
"max_level": 40,
"conditions": {
"spring": 30,
"summer": 30,
"autumn": 30
}
},
{
"pokeapi_id": 364,
"pokemon_name": "Sealeo",
"method": "surf",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 25,
"max_level": 40
"max_level": 40,
"conditions": {
"winter": 30
}
},
{
"pokeapi_id": 363,
"pokemon_name": "Spheal",
"method": "surf",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 25,
"max_level": 40
"max_level": 40,
"conditions": {
"winter": 30
}
},
{
"pokeapi_id": 171,
@@ -3342,9 +3380,14 @@
"pokeapi_id": 226,
"pokemon_name": "Mantine",
"method": "surf",
"encounter_rate": 5,
"encounter_rate": null,
"min_level": 30,
"max_level": 40
"max_level": 40,
"conditions": {
"spring": 5,
"summer": 5,
"autumn": 5
}
},
{
"pokeapi_id": 224,
@@ -3374,9 +3417,12 @@
"pokeapi_id": 365,
"pokemon_name": "Walrein",
"method": "surf",
"encounter_rate": 5,
"encounter_rate": null,
"min_level": 30,
"max_level": 40
"max_level": 40,
"conditions": {
"winter": 5
}
}
]
},
@@ -7642,33 +7688,53 @@
"pokeapi_id": 536,
"pokemon_name": "Palpitoad",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"spring": 40,
"summer": 40,
"autumn": 40
}
},
{
"pokeapi_id": 588,
"pokemon_name": "Karrablast",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 57,
"max_level": 57
"max_level": 57,
"conditions": {
"spring": 20,
"summer": 20,
"autumn": 20
}
},
{
"pokeapi_id": 618,
"pokemon_name": "Stunfisk",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 55,
"max_level": 56
"max_level": 56,
"conditions": {
"spring": 20,
"summer": 20,
"autumn": 20
}
},
{
"pokeapi_id": 453,
"pokemon_name": "Croagunk",
"method": "walk",
"encounter_rate": 15,
"encounter_rate": null,
"min_level": 55,
"max_level": 56
"max_level": 56,
"conditions": {
"spring": 15,
"summer": 15,
"autumn": 15
}
},
{
"pokeapi_id": 340,
@@ -7690,9 +7756,14 @@
"pokeapi_id": 616,
"pokemon_name": "Shelmet",
"method": "walk",
"encounter_rate": 5,
"encounter_rate": null,
"min_level": 54,
"max_level": 54
"max_level": 54,
"conditions": {
"spring": 5,
"summer": 5,
"autumn": 5
}
}
]
},
@@ -7728,33 +7799,53 @@
"pokeapi_id": 536,
"pokemon_name": "Palpitoad",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 54,
"max_level": 57
"max_level": 57,
"conditions": {
"spring": 40,
"summer": 40,
"autumn": 40
}
},
{
"pokeapi_id": 588,
"pokemon_name": "Karrablast",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 54,
"max_level": 54
"max_level": 54,
"conditions": {
"spring": 20,
"summer": 20,
"autumn": 20
}
},
{
"pokeapi_id": 618,
"pokemon_name": "Stunfisk",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 55,
"max_level": 56
"max_level": 56,
"conditions": {
"spring": 20,
"summer": 20,
"autumn": 20
}
},
{
"pokeapi_id": 453,
"pokemon_name": "Croagunk",
"method": "walk",
"encounter_rate": 15,
"encounter_rate": null,
"min_level": 55,
"max_level": 56
"max_level": 56,
"conditions": {
"spring": 15,
"summer": 15,
"autumn": 15
}
},
{
"pokeapi_id": 340,
@@ -7776,9 +7867,14 @@
"pokeapi_id": 616,
"pokemon_name": "Shelmet",
"method": "walk",
"encounter_rate": 5,
"encounter_rate": null,
"min_level": 57,
"max_level": 57
"max_level": 57,
"conditions": {
"spring": 5,
"summer": 5,
"autumn": 5
}
}
]
},

View File

@@ -1486,7 +1486,7 @@
]
},
{
"name": "Relic Castle (Volcarona\u2019s Room and Room Outside)",
"name": "Relic Castle (Volcaronas Room and Room Outside)",
"order": 30,
"encounters": [
{
@@ -2971,25 +2971,40 @@
"pokeapi_id": 536,
"pokemon_name": "Palpitoad",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 30,
"max_level": 33
"max_level": 33,
"conditions": {
"spring": 40,
"summer": 40,
"autumn": 40
}
},
{
"pokeapi_id": 616,
"pokemon_name": "Shelmet",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 30,
"max_level": 33
"max_level": 33,
"conditions": {
"spring": 40,
"summer": 40,
"autumn": 40
}
},
{
"pokeapi_id": 618,
"pokemon_name": "Stunfisk",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 31,
"max_level": 32
"max_level": 32,
"conditions": {
"spring": 20,
"summer": 20,
"autumn": 20
}
},
{
"pokeapi_id": 340,
@@ -3439,25 +3454,40 @@
"pokeapi_id": 536,
"pokemon_name": "Palpitoad",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 30,
"max_level": 33
"max_level": 33,
"conditions": {
"spring": 40,
"summer": 40,
"autumn": 40
}
},
{
"pokeapi_id": 616,
"pokemon_name": "Shelmet",
"method": "walk",
"encounter_rate": 40,
"encounter_rate": null,
"min_level": 30,
"max_level": 33
"max_level": 33,
"conditions": {
"spring": 40,
"summer": 40,
"autumn": 40
}
},
{
"pokeapi_id": 618,
"pokemon_name": "Stunfisk",
"method": "walk",
"encounter_rate": 20,
"encounter_rate": null,
"min_level": 31,
"max_level": 32
"max_level": 32,
"conditions": {
"spring": 20,
"summer": 20,
"autumn": 20
}
},
{
"pokeapi_id": 340,
@@ -5630,9 +5660,12 @@
"pokeapi_id": 446,
"pokemon_name": "Munchlax",
"method": "trade",
"encounter_rate": 100,
"encounter_rate": null,
"min_level": 60,
"max_level": 60
"max_level": 60,
"conditions": {
"summer": 100
}
},
{
"pokeapi_id": 90,
@@ -5740,9 +5773,15 @@
"pokeapi_id": 320,
"pokemon_name": "Wailmer",
"method": "surf",
"encounter_rate": 90,
"encounter_rate": null,
"min_level": 25,
"max_level": 60
"max_level": 60,
"conditions": {
"spring": 90,
"summer": 90,
"autumn": 90,
"winter": 60
}
},
{
"pokeapi_id": 223,
@@ -5772,25 +5811,36 @@
"pokeapi_id": 458,
"pokemon_name": "Mantyke",
"method": "surf",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 25,
"max_level": 55
"max_level": 55,
"conditions": {
"spring": 30,
"summer": 30,
"autumn": 30
}
},
{
"pokeapi_id": 364,
"pokemon_name": "Sealeo",
"method": "surf",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 25,
"max_level": 60
"max_level": 60,
"conditions": {
"winter": 30
}
},
{
"pokeapi_id": 363,
"pokemon_name": "Spheal",
"method": "surf",
"encounter_rate": 30,
"encounter_rate": null,
"min_level": 25,
"max_level": 55
"max_level": 55,
"conditions": {
"winter": 30
}
},
{
"pokeapi_id": 279,
@@ -5812,9 +5862,14 @@
"pokeapi_id": 226,
"pokemon_name": "Mantine",
"method": "surf",
"encounter_rate": 5,
"encounter_rate": null,
"min_level": 25,
"max_level": 60
"max_level": 60,
"conditions": {
"spring": 5,
"summer": 5,
"autumn": 5
}
},
{
"pokeapi_id": 224,
@@ -5836,9 +5891,12 @@
"pokeapi_id": 365,
"pokemon_name": "Walrein",
"method": "surf",
"encounter_rate": 5,
"encounter_rate": null,
"min_level": 25,
"max_level": 70
"max_level": 70,
"conditions": {
"winter": 5
}
}
]
},

View File

@@ -192,6 +192,41 @@ async def upsert_routes(
return {row.name: row.id for row in result}
async def _upsert_single_encounter(
session: AsyncSession,
route_id: int,
pokemon_id: int,
game_id: int,
method: str,
encounter_rate: int,
min_level: int,
max_level: int,
condition: str = "",
) -> None:
stmt = (
insert(RouteEncounter)
.values(
route_id=route_id,
pokemon_id=pokemon_id,
game_id=game_id,
encounter_method=method,
encounter_rate=encounter_rate,
condition=condition,
min_level=min_level,
max_level=max_level,
)
.on_conflict_do_update(
constraint="uq_route_pokemon_method_game_condition",
set_={
"encounter_rate": encounter_rate,
"min_level": min_level,
"max_level": max_level,
},
)
)
await session.execute(stmt)
async def upsert_route_encounters(
session: AsyncSession,
route_id: int,
@@ -207,28 +242,33 @@ async def upsert_route_encounters(
print(f" Warning: no pokemon_id for pokeapi_id {enc['pokeapi_id']}")
continue
stmt = (
insert(RouteEncounter)
.values(
route_id=route_id,
pokemon_id=pokemon_id,
game_id=game_id,
encounter_method=enc["method"],
encounter_rate=enc["encounter_rate"],
min_level=enc["min_level"],
max_level=enc["max_level"],
conditions = enc.get("conditions")
if conditions:
for condition_name, rate in conditions.items():
await _upsert_single_encounter(
session,
route_id,
pokemon_id,
game_id,
enc["method"],
rate,
enc["min_level"],
enc["max_level"],
condition=condition_name,
)
count += 1
else:
await _upsert_single_encounter(
session,
route_id,
pokemon_id,
game_id,
enc["method"],
enc["encounter_rate"],
enc["min_level"],
enc["max_level"],
)
.on_conflict_do_update(
constraint="uq_route_pokemon_method_game",
set_={
"encounter_rate": enc["encounter_rate"],
"min_level": enc["min_level"],
"max_level": enc["max_level"],
},
)
)
await session.execute(stmt)
count += 1
count += 1
return count

View File

@@ -43,6 +43,14 @@ export const METHOD_CONFIG: Record<string, { label: string; color: string }> = {
label: 'Super Rod',
color: 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900/40 dark:text-indigo-300',
},
horde: {
label: 'Horde',
color: 'bg-rose-100 text-rose-800 dark:bg-rose-900/40 dark:text-rose-300',
},
sos: {
label: 'SOS',
color: 'bg-violet-100 text-violet-800 dark:bg-violet-900/40 dark:text-violet-300',
},
}
/** Display order for encounter method groups */
@@ -58,6 +66,8 @@ export const METHOD_ORDER = [
'old-rod',
'good-rod',
'super-rod',
'horde',
'sos',
]
export function getMethodLabel(method: string): string {

View File

@@ -62,14 +62,90 @@ const statusOptions: {
const SPECIAL_METHODS = ['starter', 'gift', 'fossil', 'trade']
function groupByMethod(
pokemon: RouteEncounterDetail[]
): { method: string; pokemon: RouteEncounterDetail[] }[] {
const groups = new Map<string, RouteEncounterDetail[]>()
interface GroupedEncounter {
encounter: RouteEncounterDetail
conditions: string[]
displayRate: number | null
}
function getUniqueConditions(pokemon: RouteEncounterDetail[]): string[] {
const conditions = new Set<string>()
for (const rp of pokemon) {
const list = groups.get(rp.encounterMethod) ?? []
list.push(rp)
groups.set(rp.encounterMethod, list)
if (rp.condition) conditions.add(rp.condition)
}
return [...conditions].sort()
}
function groupByMethod(
pokemon: RouteEncounterDetail[],
selectedCondition: string | null
): { method: string; pokemon: GroupedEncounter[] }[] {
const groups = new Map<string, Map<number, GroupedEncounter>>()
// Build a lookup: pokemonId+method -> condition -> rate
const rateByCondition = new Map<string, Map<string, number>>()
for (const rp of pokemon) {
if (rp.condition) {
const key = `${rp.pokemonId}:${rp.encounterMethod}`
let condMap = rateByCondition.get(key)
if (!condMap) {
condMap = new Map()
rateByCondition.set(key, condMap)
}
condMap.set(rp.condition, rp.encounterRate)
}
}
for (const rp of pokemon) {
// When a specific condition is selected, skip pokemon with 0% under that condition
if (selectedCondition) {
const key = `${rp.pokemonId}:${rp.encounterMethod}`
const condMap = rateByCondition.get(key)
if (condMap) {
const rate = condMap.get(selectedCondition)
if (rate === 0) continue
// Skip entries for other conditions (we only want one entry per pokemon)
if (rp.condition && rp.condition !== selectedCondition) continue
}
} else {
// "All" mode: skip 0% entries
if (rp.encounterRate === 0 && rp.condition) continue
}
let methodGroup = groups.get(rp.encounterMethod)
if (!methodGroup) {
methodGroup = new Map()
groups.set(rp.encounterMethod, methodGroup)
}
const existing = methodGroup.get(rp.pokemonId)
if (existing) {
if (rp.condition) existing.conditions.push(rp.condition)
} else {
// Determine the display rate
let displayRate: number | null = null
const isSpecial = SPECIAL_METHODS.includes(rp.encounterMethod)
if (!isSpecial) {
if (selectedCondition) {
const key = `${rp.pokemonId}:${rp.encounterMethod}`
const condMap = rateByCondition.get(key)
if (condMap) {
displayRate = condMap.get(selectedCondition) ?? null
} else {
displayRate = rp.encounterRate
}
} else if (!rp.condition) {
// "All" mode: show the base rate for non-condition entries
displayRate = rp.encounterRate
}
}
methodGroup.set(rp.pokemonId, {
encounter: rp,
conditions: rp.condition ? [rp.condition] : [],
displayRate,
})
}
}
return [...groups.entries()]
.sort(([a], [b]) => {
@@ -77,14 +153,25 @@ function groupByMethod(
const bi = METHOD_ORDER.indexOf(b)
return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi)
})
.map(([method, pokemon]) => ({ method, pokemon }))
.map(([method, pokemonMap]) => ({
method,
pokemon: [...pokemonMap.values()].sort((a, b) => (b.displayRate ?? 0) - (a.displayRate ?? 0)),
}))
}
function pickRandomPokemon(
pokemon: RouteEncounterDetail[],
dupedIds?: Set<number>
): RouteEncounterDetail | null {
const eligible = dupedIds ? pokemon.filter((rp) => !dupedIds.has(rp.pokemonId)) : pokemon
// Deduplicate by pokemonId (conditions may create multiple entries)
const seen = new Set<number>()
const unique = pokemon.filter((rp) => {
if (rp.encounterRate === 0) return false
if (seen.has(rp.pokemonId)) return false
seen.add(rp.pokemonId)
return true
})
const eligible = dupedIds ? unique.filter((rp) => !dupedIds.has(rp.pokemonId)) : unique
if (eligible.length === 0) return null
return eligible[Math.floor(Math.random() * eligible.length)] ?? null
}
@@ -112,6 +199,7 @@ export function EncounterModal({
const [faintLevel, setFaintLevel] = useState<string>('')
const [deathCause, setDeathCause] = useState('')
const [search, setSearch] = useState('')
const [selectedCondition, setSelectedCondition] = useState<string | null>(null)
const isEditing = !!existing
@@ -131,13 +219,18 @@ export function EncounterModal({
}
}, [existing, routePokemon])
const availableConditions = useMemo(
() => (routePokemon ? getUniqueConditions(routePokemon) : []),
[routePokemon]
)
const filteredPokemon = routePokemon?.filter((rp) =>
rp.pokemon.name.toLowerCase().includes(search.toLowerCase())
)
const groupedPokemon = useMemo(
() => (filteredPokemon ? groupByMethod(filteredPokemon) : []),
[filteredPokemon]
() => (filteredPokemon ? groupByMethod(filteredPokemon, selectedCondition) : []),
[filteredPokemon, selectedCondition]
)
const hasMultipleGroups = groupedPokemon.length > 1
@@ -235,6 +328,35 @@ export function EncounterModal({
className="w-full px-3 py-1.5 mb-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
)}
{availableConditions.length > 0 && (
<div className="flex flex-wrap gap-1 mb-2">
<button
type="button"
onClick={() => setSelectedCondition(null)}
className={`px-2.5 py-1 text-xs font-medium rounded-full border transition-colors ${
selectedCondition === null
? 'bg-purple-100 border-purple-300 text-purple-800 dark:bg-purple-900/40 dark:border-purple-600 dark:text-purple-300'
: 'border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:border-purple-300 dark:hover:border-purple-600'
}`}
>
All
</button>
{availableConditions.map((cond) => (
<button
key={cond}
type="button"
onClick={() => setSelectedCondition(cond)}
className={`px-2.5 py-1 text-xs font-medium rounded-full border transition-colors capitalize ${
selectedCondition === cond
? 'bg-purple-100 border-purple-300 text-purple-800 dark:bg-purple-900/40 dark:border-purple-600 dark:text-purple-300'
: 'border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:border-purple-300 dark:hover:border-purple-600'
}`}
>
{cond}
</button>
))}
</div>
)}
<div className="max-h-64 overflow-y-auto space-y-3">
{groupedPokemon.map(({ method, pokemon }, groupIdx) => (
<div key={method}>
@@ -247,18 +369,21 @@ export function EncounterModal({
</div>
)}
<div className="grid grid-cols-3 gap-2">
{pokemon.map((rp) => {
{pokemon.map(({ encounter: rp, conditions, displayRate }) => {
const isDuped = dupedPokemonIds?.has(rp.pokemonId) ?? false
const isSelected =
selectedPokemon?.pokemonId === rp.pokemonId &&
selectedPokemon?.encounterMethod === rp.encounterMethod
return (
<button
key={rp.id}
key={`${rp.encounterMethod}-${rp.pokemonId}`}
type="button"
onClick={() => !isDuped && setSelectedPokemon(rp)}
disabled={isDuped}
className={`flex flex-col items-center p-2 rounded-lg border text-center transition-colors ${
isDuped
? 'opacity-40 cursor-not-allowed border-gray-200 dark:border-gray-700'
: selectedPokemon?.id === rp.id
: isSelected
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/30'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
}`}
@@ -287,6 +412,18 @@ export function EncounterModal({
{!isDuped && SPECIAL_METHODS.includes(rp.encounterMethod) && (
<EncounterMethodBadge method={rp.encounterMethod} />
)}
{!isDuped && displayRate !== null && displayRate !== undefined && (
<span className="text-[10px] text-purple-500 dark:text-purple-400 font-medium">
{displayRate}%
</span>
)}
{!isDuped &&
selectedCondition === null &&
conditions.length > 0 && (
<span className="text-[10px] text-purple-500 dark:text-purple-400">
{conditions.join(', ')}
</span>
)}
{!isDuped && (
<span className="text-[10px] text-gray-400">
Lv. {rp.minLevel}

View File

@@ -63,6 +63,8 @@ export function AdminRouteDetail() {
? Math.max(...childRoutes.map((r) => r.order)) + 1
: (route?.order ?? 0) * 10 + 1
const hasConditions = encounters.some((e) => e.condition !== '')
const columns: Column<RouteEncounterDetail>[] = [
{
header: 'Pokemon',
@@ -79,6 +81,14 @@ export function AdminRouteDetail() {
},
{ header: 'Method', accessor: (e) => e.encounterMethod },
{ header: 'Rate', accessor: (e) => `${e.encounterRate}%` },
...(hasConditions
? [
{
header: 'Condition',
accessor: (e: RouteEncounterDetail) => e.condition || '\u2014',
} as Column<RouteEncounterDetail>,
]
: []),
{
header: 'Levels',
accessor: (e) =>

View File

@@ -68,6 +68,7 @@ export interface CreateRouteEncounterInput {
gameId: number
encounterMethod: string
encounterRate: number
condition?: string
minLevel: number
maxLevel: number
}
@@ -75,6 +76,7 @@ export interface CreateRouteEncounterInput {
export interface UpdateRouteEncounterInput {
encounterMethod?: string
encounterRate?: number
condition?: string
minLevel?: number
maxLevel?: number
}
@@ -128,6 +130,7 @@ export interface PokemonEncounterLocationItem {
routeName: string
encounterMethod: string
encounterRate: number
condition: string
minLevel: number
maxLevel: number
}

View File

@@ -56,6 +56,7 @@ export interface RouteEncounter {
gameId: number
encounterMethod: string
encounterRate: number
condition: string
minLevel: number
maxLevel: number
}

View File

@@ -84,8 +84,8 @@ ENCOUNTER_METHOD_MAP: dict[str, str] = {
"cave-spot": "walk",
"bubble-spot": "surf",
"sand-spot": "walk",
"horde": "walk",
"sos-encounter": "walk",
"horde": "horde",
"sos-encounter": "sos",
"ambush": "walk",
# Seaweed / diving
"diving": "surf",
@@ -105,7 +105,7 @@ ENCOUNTER_METHOD_MAP: dict[str, str] = {
"dust-cloud": "walk",
"hidden-grotto": "static",
"hidden-encounter": "walk",
"horde-encounter": "walk",
"horde-encounter": "horde",
"shaking-trees": "walk",
"shaking-ore-deposits": "walk",
"island-scan": "static",

View File

@@ -13,16 +13,22 @@ class Encounter:
encounter_rate: int
min_level: int
max_level: int
conditions: dict[str, int] | None = None
def to_dict(self) -> dict:
return {
d: dict = {
"pokeapi_id": self.pokeapi_id,
"pokemon_name": self.pokemon_name,
"method": self.method,
"encounter_rate": self.encounter_rate,
"min_level": self.min_level,
"max_level": self.max_level,
}
if self.conditions:
d["encounter_rate"] = None
d["conditions"] = self.conditions
else:
d["encounter_rate"] = self.encounter_rate
return d
@dataclass

View File

@@ -65,61 +65,75 @@ def parse_rate(value: str | None) -> int | None:
return None
def extract_encounter_rate(record: dict[str, Any], generation: int) -> int:
"""Extract a single encounter_rate from a PokeDB encounter record.
def extract_encounter_data(
record: dict[str, Any],
generation: int,
) -> tuple[int, dict[str, int] | None]:
"""Extract encounter rate and per-condition rates from a PokeDB record.
Flattens generation-specific rate variants into a single value.
Returns (rate, conditions) where:
- rate is the max/overall rate (used for sorting and backward compat)
- conditions is a dict of {condition_name: rate} or None for flat rates
"""
# Gen 1/3/6: rate_overall
# Gen 1/3/6: rate_overall — flat rate, no conditions
rate_overall = parse_rate(record.get("rate_overall"))
if rate_overall is not None:
return rate_overall
return rate_overall, None
# Gen 2/4: time-of-day rates — take the max
time_rates = [
parse_rate(record.get("rate_morning")),
parse_rate(record.get("rate_day")),
parse_rate(record.get("rate_night")),
]
time_rates = [r for r in time_rates if r is not None]
if time_rates:
return max(time_rates)
# Gen 2/4/7: time-of-day rates
time_fields = {
"morning": parse_rate(record.get("rate_morning")),
"day": parse_rate(record.get("rate_day")),
"night": parse_rate(record.get("rate_night")),
}
time_conditions = {k: v for k, v in time_fields.items() if v is not None}
if time_conditions:
rate = max(time_conditions.values())
return rate, time_conditions
# Gen 5: seasonal rates — take the max
season_rates = [
parse_rate(record.get("rate_spring")),
parse_rate(record.get("rate_summer")),
parse_rate(record.get("rate_autumn")),
parse_rate(record.get("rate_winter")),
]
season_rates = [r for r in season_rates if r is not None]
if season_rates:
return max(season_rates)
# Gen 5: seasonal rates
season_fields = {
"spring": parse_rate(record.get("rate_spring")),
"summer": parse_rate(record.get("rate_summer")),
"autumn": parse_rate(record.get("rate_autumn")),
"winter": parse_rate(record.get("rate_winter")),
}
season_conditions = {
k: v for k, v in season_fields.items() if v is not None
}
if season_conditions:
rate = max(season_conditions.values())
return rate, season_conditions
# Gen 8 Sw/Sh: weather rates — take the max
weather_rates = []
# Gen 8 Sw/Sh: weather rates
weather_conditions: dict[str, int] = {}
for key, val in record.items():
if key.startswith("weather_") and key.endswith("_rate") and val:
parsed = parse_rate(val)
if parsed is not None:
weather_rates.append(parsed)
if weather_rates:
return max(weather_rates)
# "weather_clear_rate" -> "clear"
condition_name = key[len("weather_"):-len("_rate")]
weather_conditions[condition_name] = parsed
if weather_conditions:
rate = max(weather_conditions.values())
return rate, weather_conditions
# Gen 8 Legends Arceus: boolean conditions presence-based
if record.get("during_any_time") or record.get("during_morning") or \
record.get("during_day") or record.get("during_evening") or record.get("during_night"):
return 100 # Present under conditions
# Gen 8 Legends Arceus: boolean conditions presence-based
if (
record.get("during_any_time")
or record.get("during_morning")
or record.get("during_day")
or record.get("during_evening")
or record.get("during_night")
):
return 100, None
# Gen 9 Sc/Vi: probability weights normalize
# Gen 9 Sc/Vi: probability weights normalize
prob_overall = record.get("probability_overall")
if prob_overall:
parsed = parse_rate(prob_overall)
if parsed is not None:
# These are spawn weights (e.g. "20", "300"), not percentages.
# We'll normalize them later during aggregation when we have
# all encounters for a location. For now, store the raw weight.
return parsed
return parsed, None
# Check time-based probability variants
prob_rates = [
@@ -130,10 +144,10 @@ def extract_encounter_rate(record: dict[str, Any], generation: int) -> int:
]
prob_rates = [r for r in prob_rates if r is not None]
if prob_rates:
return max(prob_rates)
return max(prob_rates), None
# Fallback: gift/trade/static encounters with no rate
return 100
return 100, None
# ---------------------------------------------------------------------------
@@ -212,8 +226,8 @@ def process_encounters(
# Parse levels
min_level, max_level = parse_levels(record.get("levels"))
# Extract rate
encounter_rate = extract_encounter_rate(record, generation)
# Extract rate and conditions
encounter_rate, conditions = extract_encounter_data(record, generation)
# Location area
area_id = record.get("location_area_identifier", "")
@@ -227,6 +241,7 @@ def process_encounters(
encounter_rate=encounter_rate,
min_level=min_level,
max_level=max_level,
conditions=conditions,
)
by_area.setdefault(area_id, []).append(enc)
@@ -234,10 +249,28 @@ def process_encounters(
return by_area
def _merge_conditions(
a: dict[str, int] | None,
b: dict[str, int] | None,
) -> dict[str, int] | None:
"""Merge two condition dicts by summing rates per key."""
if a is None and b is None:
return None
merged = dict(a or {})
for k, v in (b or {}).items():
merged[k] = merged.get(k, 0) + v
return merged
def _cap_conditions(conditions: dict[str, int]) -> dict[str, int]:
"""Cap each condition rate at 100."""
return {k: min(v, 100) for k, v in conditions.items()}
def aggregate_encounters(encounters: list[Encounter]) -> list[Encounter]:
"""Aggregate encounters by (pokeapi_id, method), merging level ranges and summing rates.
Replicates the Go tool's aggregation logic.
Preserves per-condition rates through aggregation.
"""
key_type = tuple[int, str]
agg: dict[key_type, Encounter] = {}
@@ -250,8 +283,10 @@ def aggregate_encounters(encounters: list[Encounter]) -> list[Encounter]:
existing.encounter_rate += enc.encounter_rate
existing.min_level = min(existing.min_level, enc.min_level)
existing.max_level = max(existing.max_level, enc.max_level)
existing.conditions = _merge_conditions(
existing.conditions, enc.conditions
)
else:
# Copy so we don't mutate the original
agg[k] = Encounter(
pokeapi_id=enc.pokeapi_id,
pokemon_name=enc.pokemon_name,
@@ -259,6 +294,7 @@ def aggregate_encounters(encounters: list[Encounter]) -> list[Encounter]:
encounter_rate=enc.encounter_rate,
min_level=enc.min_level,
max_level=enc.max_level,
conditions=dict(enc.conditions) if enc.conditions else None,
)
order.append(k)
@@ -266,6 +302,9 @@ def aggregate_encounters(encounters: list[Encounter]) -> list[Encounter]:
for k in order:
e = agg[k]
e.encounter_rate = min(e.encounter_rate, 100)
if e.conditions:
e.conditions = _cap_conditions(e.conditions)
e.encounter_rate = max(e.conditions.values())
result.append(e)
# Sort by rate descending, then name ascending

322
tools/merge-conditions.py Normal file
View File

@@ -0,0 +1,322 @@
#!/usr/bin/env python3
"""Merge per-condition encounter rates from a fresh import into curated seed files.
Usage:
# From repo root (requires PokeDB cache):
python tools/merge-conditions.py --game heartgold
# Process all games that have conditions:
python tools/merge-conditions.py --all
# Dry run (print what would change, don't write):
python tools/merge-conditions.py --game heartgold --dry-run
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
# Add tools/import-pokedb to sys.path so we can import the library
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "tools" / "import-pokedb"))
from import_pokedb.loader import load_pokedb_data, load_seed_config # noqa: E402
from import_pokedb.mappings import ( # noqa: E402
LocationMapper,
PokemonMapper,
build_version_map,
)
from import_pokedb.processing import ( # noqa: E402
build_routes,
filter_den_routes,
filter_encounters_for_game,
process_encounters,
)
from import_pokedb.output import merge_special_encounters, sort_routes # noqa: E402
SEEDS_DIR = REPO_ROOT / "backend" / "src" / "app" / "seeds"
DATA_DIR = SEEDS_DIR / "data"
# Games that have per-condition encounter rates
CONDITION_GAMES: dict[str, str] = {
# Gen 2: morning/day/night
"gold": "gold-silver",
"silver": "gold-silver",
"crystal": "crystal",
# Gen 4: morning/day/night
"heartgold": "heartgold-soulsilver",
"soulsilver": "heartgold-soulsilver",
"diamond": "diamond-pearl",
"pearl": "diamond-pearl",
"platinum": "platinum",
"brilliant-diamond": "brilliant-diamond-shining-pearl",
"shining-pearl": "brilliant-diamond-shining-pearl",
# Gen 5: spring/summer/autumn/winter
"black": "black-white",
"white": "black-white",
"black-2": "black-2-white-2",
"white-2": "black-2-white-2",
# Gen 6: horde encounters
"x": "x-y",
"y": "x-y",
# Gen 7: day/night + SOS
"sun": "sun-moon",
"moon": "sun-moon",
"ultra-sun": "ultra-sun-ultra-moon",
"ultra-moon": "ultra-sun-ultra-moon",
# Gen 8: weather
"sword": "sword-shield",
"shield": "sword-shield",
}
def normalize_route_name(name: str) -> str:
"""Normalize a route name for fuzzy matching."""
return name.lower().strip()
def build_fresh_lookup(
game_slug: str,
vg_key: str,
generation: int,
pokedb: object,
config: object,
pokemon_mapper: PokemonMapper,
location_mapper: LocationMapper,
) -> dict[str, dict[tuple[int, str], dict[str, int]]]:
"""Run the import pipeline and build a conditions lookup.
Returns: {normalized_route_name: {(pokeapi_id, method): conditions_dict}}
"""
game_encounters = filter_encounters_for_game(
pokedb.encounters, game_slug
)
if not game_encounters:
return {}
encounters_by_area = process_encounters(
game_encounters, generation, pokemon_mapper, location_mapper
)
routes = build_routes(encounters_by_area, location_mapper)
if vg_key == "sword-shield":
routes = filter_den_routes(routes)
routes = merge_special_encounters(
routes, config, vg_key, pokemon_mapper
)
routes = sort_routes(routes, config, vg_key)
lookup: dict[str, dict[tuple[int, str], dict[str, int]]] = {}
def index_route(route):
key = normalize_route_name(route.name)
enc_map: dict[tuple[int, str], dict[str, int]] = {}
for enc in route.encounters:
if enc.conditions:
enc_map[(enc.pokeapi_id, enc.method)] = enc.conditions
if enc_map:
lookup[key] = enc_map
for route in routes:
index_route(route)
for child in route.children:
index_route(child)
return lookup
def merge_conditions_into_seed(
seed_data: list[dict],
lookup: dict[str, dict[tuple[int, str], dict[str, int]]],
game_slug: str,
dry_run: bool = False,
) -> tuple[list[dict], int]:
"""Merge conditions from lookup into seed data, return (updated_data, count)."""
merged_count = 0
def process_route(route: dict) -> None:
nonlocal merged_count
route_key = normalize_route_name(route["name"])
route_lookup = lookup.get(route_key)
if route_lookup is None:
return
for enc in route.get("encounters", []):
key = (enc["pokeapi_id"], enc["method"])
conditions = route_lookup.get(key)
if conditions:
if dry_run:
print(
f" {route['name']}: "
f"{enc.get('pokemon_name', '?')} ({enc['method']}) "
f"-> {conditions}"
)
enc["conditions"] = conditions
enc["encounter_rate"] = None
merged_count += 1
for child in route.get("children", []):
process_route(child)
for route in seed_data:
process_route(route)
return seed_data, merged_count
def process_game(
game_slug: str,
pokedb,
config,
pokemon_mapper: PokemonMapper,
location_mapper: LocationMapper,
version_map: dict[str, str],
dry_run: bool = False,
) -> int:
"""Process a single game. Returns number of encounters merged."""
vg_key = CONDITION_GAMES.get(game_slug)
if vg_key is None:
print(f" Skipping {game_slug}: not a condition game")
return 0
# Find generation
vg_info = config.version_groups.get(vg_key)
if vg_info is None:
print(f" Warning: version group '{vg_key}' not found")
return 0
generation = vg_info.get("generation", 0)
# Build fresh import lookup
lookup = build_fresh_lookup(
game_slug,
vg_key,
generation,
pokedb,
config,
pokemon_mapper,
location_mapper,
)
if not lookup:
print(" No conditions found in fresh import")
return 0
total_conditions = sum(len(v) for v in lookup.values())
print(
f" Fresh import: {len(lookup)} routes with conditions, "
f"{total_conditions} encounter+condition pairs"
)
# Load existing seed file
seed_path = DATA_DIR / f"{game_slug}.json"
if not seed_path.exists():
print(f" Warning: seed file not found: {seed_path}")
return 0
with open(seed_path) as f:
seed_data = json.load(f)
# Merge
updated_data, merged_count = merge_conditions_into_seed(
seed_data, lookup, game_slug, dry_run=dry_run
)
if merged_count == 0:
print(" No encounters matched for merging")
return 0
print(f" Merged conditions into {merged_count} encounters")
if not dry_run:
with open(seed_path, "w") as f:
json.dump(updated_data, f, indent=2, ensure_ascii=False)
f.write("\n")
print(f" Wrote {seed_path}")
return merged_count
def main() -> None:
parser = argparse.ArgumentParser(
description="Merge per-condition encounter rates into seed files."
)
parser.add_argument(
"--game", type=str, help="Process a specific game slug"
)
parser.add_argument(
"--all",
action="store_true",
help="Process all games with conditions",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Print what would change without writing files",
)
parser.add_argument(
"--pokedb-dir",
type=Path,
default=None,
help="Path to PokeDB data directory",
)
args = parser.parse_args()
if not args.game and not args.all:
parser.error("Specify --game SLUG or --all")
pokedb_dir = args.pokedb_dir or (SEEDS_DIR / ".pokedb_cache")
print(f"PokeDB data: {pokedb_dir}")
print(f"Seed data: {DATA_DIR}")
print()
# Load PokeDB data
pokedb = load_pokedb_data(pokedb_dir)
print(pokedb.summary())
print()
# Load seed config
config = load_seed_config(SEEDS_DIR)
print(f"Loaded {len(config.version_groups)} version groups")
print()
# Build mappings
pokemon_json = DATA_DIR / "pokemon.json"
pokemon_mapper = PokemonMapper(pokemon_json, pokedb)
location_mapper = LocationMapper(pokedb)
version_map = build_version_map(pokedb, config.version_groups)
# Determine games to process
if args.game:
games = [args.game]
else:
games = list(CONDITION_GAMES.keys())
total_merged = 0
for game_slug in games:
print(f"\n--- {game_slug} ---")
count = process_game(
game_slug,
pokedb,
config,
pokemon_mapper,
location_mapper,
version_map,
dry_run=args.dry_run,
)
total_merged += count
print(f"\nTotal: {total_merged} encounters updated across {len(games)} games")
if args.dry_run:
print("(dry run — no files written)")
if __name__ == "__main__":
main()