diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bb5b18d..35533cd1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,10 +12,11 @@ name: ci # on: push: - # Integration branch + release branch. PRs already run via pull_request - # (any base); this list is only for post-merge push runs. branches: [dev, main] + # PRs into dev only: a dev -> main ship PR reuses the required checks the + # dev push already put on the same head SHA, so it needs no second run. pull_request: + branches: [dev] # a force-push while CI is mid-run should cancel the stale run, not queue concurrency: @@ -318,52 +319,10 @@ jobs: run: | set -euo pipefail scripts/build_linux_arm64.sh --version 0.0.0 + # Shared with the release workflow so shipped images get the same + # self-contained / glibc-floor checks as PR builds. - name: Verify the AppImage is self-contained and bullseye-compatible - run: | - set -euo pipefail - image="dist/linux-arm64/gen1recomp-0.0.0-linux-arm64.AppImage" - - # --appimage-extract needs no FUSE, so this works on a runner - # without /dev/fuse and still exercises the real payload. - "$image" --appimage-extract >/dev/null - for required in AppRun bin/love game.love lib/liblove-11.5.so; do - [ -e "squashfs-root/$required" ] \ - || { echo "::error::AppImage is missing $required"; exit 1; } - done - - # Every bundled object must resolve once AppRun's LD_LIBRARY_PATH is - # applied; an unresolved soname here is a user-visible launch crash. - # - # This runs on a HEADLESS runner on purpose, and that is the point. - # The first version of this build bundled Debian's SDL2, which - # hard-links libpulse/libasound/libX11/libwayland, so it only ever - # started on a full desktop -- a bare runner is what exposed it. - missing="$(LD_LIBRARY_PATH="$PWD/squashfs-root/lib" \ - ldd squashfs-root/bin/love squashfs-root/lib/*.so* 2>/dev/null \ - | grep 'not found' || true)" - [ -z "$missing" ] || { echo "::error::unresolved deps:"; echo "$missing"; exit 1; } - - # Nothing may hard-link a driver, session or audio-stack library: - # those must be reached through dlopen so the AppImage runs on a box - # with only ALSA, only Wayland, or only KMSDRM. - linked="$(for f in squashfs-root/bin/love squashfs-root/lib/*.so*; do - objdump -p "$f" 2>/dev/null | awk '/NEEDED/{print $2}' - done | sort -u | grep -E '^lib(pulse|asound|X11|wayland|GL|EGL|drm|gbm|xcb|cairo|sndio|dbus)' || true)" - [ -z "$linked" ] \ - || { echo "::error::these must be dlopened, not linked:"; echo "$linked"; exit 1; } - - # The whole point of compiling on bullseye. If a future change moves - # the builder to a newer base, the glibc floor silently rises and - # every user on an older distro gets "GLIBC_2.xx not found" -- catch - # it here instead of in a release. - floor="$(objdump -T squashfs-root/bin/love squashfs-root/lib/*.so* 2>/dev/null \ - | grep -o 'GLIBC_[0-9.]*' | sort -V | tail -1)" - echo "highest required glibc symbol version: $floor" - [ -n "$floor" ] \ - || { echo "::error::found no versioned glibc symbols -- objdump read nothing"; exit 1; } - highest="$(printf '%s\n' "$floor" "GLIBC_2.31" | sort -V | tail -1)" - [ "$highest" = "GLIBC_2.31" ] \ - || { echo "::error::AppImage requires $floor, above the bullseye 2.31 floor"; exit 1; } + run: bash scripts/linux-arm64/verify_appimage.sh dist/linux-arm64/gen1recomp-0.0.0-linux-arm64.AppImage - name: Upload the AppImage uses: actions/upload-artifact@v7 with: @@ -400,7 +359,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - run: sudo apt-get update && sudo apt-get install -y luajit - run: python3 -m pip install --upgrade pillow # the fixture PNGs are committed (they are 8x8 placeholders, not @@ -421,13 +379,8 @@ jobs: print(f"\n{len(paths)} fixture assets valid") PY - # the fingerprint golden is the parity tripwire; prove it still - # matches the dataset on a clean checkout - - name: fingerprint gate - run: luajit tests/engine/gate_fingerprint.lua - - - name: parity-guarantee meta-test - run: luajit tests/engine/gate_meta_coverage.lua + # the fingerprint parity gates (gate_fingerprint / gate_meta_coverage) + # run in the headless job via run_engine; this job only guards the PNGs # Only the differ is under test here, and the job is named for that. The # capture half of the golden pipeline does not exist: a POKEPORT_DRIVER diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f7384709..b41bc888 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -161,6 +161,8 @@ jobs: scripts/build_linux_arm64.sh \ --version "${{ needs.version.outputs.version }}" \ --game-love .bazinga/work/game.love + - name: Verify the AppImage is self-contained and bullseye-compatible + run: bash scripts/linux-arm64/verify_appimage.sh "dist/linux-arm64/gen1recomp-${{ needs.version.outputs.version }}-linux-arm64.AppImage" - name: Upload Linux arm64 release uses: actions/upload-artifact@v7 with: @@ -285,7 +287,7 @@ jobs: retention-days: 1 release: - needs: [version, xbox-uwp, linux-arm64, native-tls-win] + needs: [version, love-payload, xbox-uwp, linux-arm64, native-tls-win] runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }} steps: @@ -307,6 +309,15 @@ jobs: name: gen1tls-win-x64 path: dist/native/win-x64 + # The same game.love the arm64 AppImage and Xbox UWP builds fused, so + # every release asset ships one identical payload (build.sh's own pack + # would omit PATCH_NOTES.md and mobile/ios/app-repo.json). + - name: Download shared payload + uses: actions/download-artifact@v8 + with: + name: gen1recomp-release-love + path: dist/payload + - name: Import signing certificate into a temporary keychain if: github.repository == 'bryanthaboi/gen1recomp' run: | @@ -357,7 +368,8 @@ jobs: echo "::error::gen1tls.dll missing at $GEN1TLS_DLL (native-tls-win job)" exit 1 fi - scripts/build.sh all --version "${{ needs.version.outputs.version }}" --no-notarize + scripts/build.sh all --version "${{ needs.version.outputs.version }}" --no-notarize \ + --game-love dist/payload/game.love unzip -l dist/win/gen1recomp-win64.zip | grep -F gen1tls.dll \ || { echo "::error::Windows zip is missing gen1tls.dll"; exit 1; } diff --git a/CONTRIBUTING-mods.md b/CONTRIBUTING-mods.md index 4eb92f16..40147fae 100644 --- a/CONTRIBUTING-mods.md +++ b/CONTRIBUTING-mods.md @@ -140,17 +140,17 @@ ship text. ### 4. `games` (and the legacy `gen2compat`) -Pokemon Gold is Gen 2, and it runs its own battle engine, overworld, script -VM and save format. The mod API is shared across both generations (same hook -names, same event names, same registry names) but Gold cannot serve all of it -yet, so Gen 2 is opt-in. Say which games the mod is for: +Pokemon Gold and Silver are Gen 2, and they run their own battle engine, +overworld, script VM and save format. The mod API is shared across both +generations (same hook names, same event names, same registry names) but Gen 2 +cannot serve all of it yet, so it is opt-in. Say which games the mod is for: ```json "games": ["gen1", "gen2"] ``` -Each entry is a version id (`"red"`, `"blue"`, `"yellow"`, `"gold"`), a -generation (`"gen1"`, `"gen2"`) or `"all"`; +Each entry is a version id (`"red"`, `"blue"`, `"yellow"`, `"gold"`, +`"silver"`), a generation (`"gen1"`, `"gen2"`) or `"all"`; `src/mods/ModTargets.lua` resolves them off `GameVersion.ORDER` so nothing restates the game list. `python3 tools/modkit.py scaffold my_mod --games gen1,gen2` writes the key for you. The mod still installs to one directory, @@ -159,10 +159,10 @@ gen1,gen2` writes the key for you. The mod still installs to one directory, Absent means Gen 1 only, which is what every mod written before the key existed was tested as. `"gen2compat": true` is the legacy spelling, still accepted and purely additive (it *adds* the Gen 2 games), so no manifest can lose a game it -already ran on. On a Gold boot a mod claiming no Gen 2 game is not loaded at -all: the manager lists it as `ENABLED (NOT THIS GAME)` and says why, because a -mod that half-applies reads as a broken mod. Claim Gen 2 once you have actually -run your mod on Gold. +already ran on. On a Gold or Silver boot a mod claiming no Gen 2 game is not +loaded at all: the manager lists it as `ENABLED (NOT THIS GAME)` and says why, +because a mod that half-applies reads as a broken mod. Claim Gen 2 once you +have actually run your mod on Gold or Silver. Every token is enforced, per game: the loader gates on the same `ModTargets.supports` answer both mod surfaces draw, so `"games": ["blue"]` @@ -173,7 +173,7 @@ before the key existed changes behavior; list both generations or say `"all"` when you mean everywhere. `docs/mod-api-gen2-compat.md` is the compatibility matrix: what works on Gold -today (40 of the 46 registries, 40 event and 43 hook names shared with Gen 1, +and Silver today (40 of the 46 registries, 40 event and 43 hook names shared with Gen 1, and 24 Gen 2-only ones), which registries have no Gen 2 home and drop their writes with a report, and which hooks and events are still to come. `docs/preparing-your-mod-for-gen2.md` is the step-by-step migration guide for a diff --git a/README.md b/README.md index c7640938..f213eb27 100644 --- a/README.md +++ b/README.md @@ -55,16 +55,15 @@ And before you say, "that's not a recomp", you're wrong. Recomp is an acronym. * [![Watch the latest update video](https://img.youtube.com/vi/yi7LkWQPKKM/maxresdefault.jpg)](https://youtu.be/yi7LkWQPKKM) - This project does not include a ROM, emulate the Game Boy, transpile assembly, -or download a disassembly. A canonical US Poke Red, Blue, Yellow, or Gold ROM -is the only game content input. +or download a disassembly. A canonical US Poke Red, Blue, Yellow, Gold, or +Silver ROM is the only game content input. The ROM is verified, used during import, and then released from memory. It is not copied into the cache. Later launches load the private generated cache and -do not ask for the ROM again. Red, Blue, Yellow, and Gold can all be imported -side by side. Gold is Gen 2 Phase 1 (import + launcher; see -`docs/gold-phase1.md`): the Gen 2 engine is still under construction. +do not ask for the ROM again. Red, Blue, Yellow, Gold, and Silver can all be +imported side by side. Gold and Silver are Gen 2 Phase 1 (import + launcher; +see `docs/gold-phase1.md`): the Gen 2 engine is still under construction. ## Quick Start @@ -72,13 +71,14 @@ Open the desktop app. On first boot, choose your legally obtained `.gb` / `.gbc` file or drop it onto the window. Import takes a few seconds and the game starts automatically. -Only the canonical US Red, Blue, Yellow (1 MiB), and Gold (2 MiB) ROMs are -accepted. The importer verifies SHA-1 before creating any game data: +Only the canonical US Red, Blue, Yellow (1 MiB), Gold, and Silver (2 MiB) +ROMs are accepted. The importer verifies SHA-1 before creating any game data: - Red: `ea9bcae617fdf159b045185467ae58b2e4a48b9a` - Blue: `d7037c83e1ae5b39bde3c30787637ba1d4c48ce2` - Yellow: `cc7d03262ebfaf2f06772c1a480c7d9d5f4a38e1` - Gold: `d8b8a3600a465308c9953dfa04f0081c05bdcb94` +- Silver: `49b163f7e57702bc939d642a18f591de55d92dae` The packaged app contains neither a ROM nor pre-extracted game data. Music, sound effects, and cries are synthesized while the game runs from compact @@ -219,7 +219,7 @@ entry: a desktop shortcut per game, a Steam entry, or a handheld frontend. | Option | Effect | | --- | --- | -| `--game=red` | boot Red, skipping the launcher (`blue` and `yellow` too, or just `r` / `b` / `y`) | +| `--game=red` | boot Red, skipping the launcher (`blue`, `yellow`, `gold` and `silver` too, or just `r` / `b` / `y` / `g` / `s`) | | `--slot=2` | load that save slot; takes a slot number or a slot id | | `--launcher` | open the launcher anyway, so you can edit a shortcut you already made | diff --git a/assets/launcher/bug.png b/assets/launcher/bug.png index 6c7bc24d..263ca0d3 100644 Binary files a/assets/launcher/bug.png and b/assets/launcher/bug.png differ diff --git a/build-linux-arm-sbc.sh b/build-linux-arm-sbc.sh index 3da19638..0884f27d 100755 --- a/build-linux-arm-sbc.sh +++ b/build-linux-arm-sbc.sh @@ -133,6 +133,8 @@ mkdir -p "$GAME_SRC" (cd "$SOURCE_DIR" && zip -q -9 -r "$WORK/game-payload.zip" \ main.lua conf.lua src libs data assets tools/save-editor \ tools/rom_manifest.json tools/rom_manifest_blue.json \ + tools/rom_manifest_yellow.json tools/rom_manifest_gold.json \ + tools/rom_manifest_silver.json \ -x '*.DS_Store' 'data/generated/*' 'assets/generated/*') if unzip -Z1 "$WORK/game-payload.zip" \ | grep -Eq '^(data|assets)/generated/[^/]+|^(data|assets)/generated/.+/'; then diff --git a/build-rg34xxsp.sh b/build-rg34xxsp.sh index 01a62b53..3702d226 100755 --- a/build-rg34xxsp.sh +++ b/build-rg34xxsp.sh @@ -92,6 +92,7 @@ mkdir -p "$GAME_SRC" main.lua conf.lua src libs data assets tools/save-editor \ tools/rom_manifest.json tools/rom_manifest_blue.json \ tools/rom_manifest_yellow.json tools/rom_manifest_gold.json \ + tools/rom_manifest_silver.json \ -x '*.DS_Store' 'data/generated/*' 'assets/generated/*') payload_list="$(unzip -Z1 "$WORK/game-payload.zip")" printf '%s\n' "$payload_list" \ @@ -99,6 +100,8 @@ printf '%s\n' "$payload_list" \ && fail "payload unexpectedly contains generated ROM data" printf '%s\n' "$payload_list" | grep -qxF "tools/rom_manifest_gold.json" \ || fail "payload is missing tools/rom_manifest_gold.json" +printf '%s\n' "$payload_list" | grep -qxF "tools/rom_manifest_silver.json" \ + || fail "payload is missing tools/rom_manifest_silver.json" unzip -q "$WORK/game-payload.zip" -d "$GAME_SRC" rm -f "$WORK/game-payload.zip" @@ -193,6 +196,26 @@ get_controls [ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt" GAMEDIR="$SHDIR/gen1recomp" +# Anbernic stock keeps the launcher and the game folder side by side, so the +# SHDIR-relative path above is correct there and is tried first. +# +# Other firmwares (muOS, and PortMaster's layout on several devices) keep +# launcher scripts and port data in SEPARATE trees -- scripts under roms/ports, +# data under ports -- so the sibling folder holds no game. +# +# Probe for the BINARY, not the directory: on a split layout this script has +# usually already created "$SHDIR/gen1recomp/conf" and log.txt on an earlier +# failed run (see mkdir/tee below), so an existence test matches a decoy of our +# own making. Stock is unaffected -- its sibling holds the real binary and wins +# on the first test. +if [ ! -f "$GAMEDIR/bin/love.aarch64" ]; then + for candidate in "/$directory/ports/gen1recomp" \ + "/mnt/sdcard/ports/gen1recomp" \ + "/mnt/mmc/ports/gen1recomp" \ + "/roms/ports/gen1recomp"; do + if [ -f "$candidate/bin/love.aarch64" ]; then GAMEDIR="$candidate"; break; fi + done +fi CONFDIR="$GAMEDIR/conf" mkdir -p "$CONFDIR" diff --git a/data/scripts/celadon_eevee.lua b/data/scripts/celadon_eevee.lua index 4b6a78de..d7982e97 100644 --- a/data/scripts/celadon_eevee.lua +++ b/data/scripts/celadon_eevee.lua @@ -25,8 +25,10 @@ local Menu = require("src.ui.Menu") local TextBox = require("src.render.TextBox") --- TMNotebookText (data/text/text_2.asm) has no leading underscore, so the --- extractor never collects it and the pamphlet's text is inlined. +-- TMNotebookText (data/text/text_2.asm) has no leading underscore, but the +-- extractor now collects any top-level label in a dedicated text file +-- regardless (tools/extract/text.py), so this is the real ROM label -- +-- the literal below is only the fallback for a catalog without it. local TM_NOTEBOOK_TEXT = "It's a pamphlet\non TMs.\f...\f" .. "There are 50 TMs\nin all.\f" .. "There are also 5\nHMs that can be\vused repeatedly.\f" @@ -70,7 +72,8 @@ return { return true end if fx == 3 and fy == 4 then - game.stack:push(TextBox.new(game, TM_NOTEBOOK_TEXT)) + local text = game.data.text or {} + game.stack:push(TextBox.new(game, text.TMNotebookText or TM_NOTEBOOK_TEXT)) return true end return false diff --git a/data/scripts/flavor/ss_anne_kitchen.lua b/data/scripts/flavor/ss_anne_kitchen.lua index f3180ce6..981c8dc5 100644 --- a/data/scripts/flavor/ss_anne_kitchen.lua +++ b/data/scripts/flavor/ss_anne_kitchen.lua @@ -15,10 +15,10 @@ return { -- pick the dish: bit 7 set (~50%) -> Salmon du Salad, else bit 4 -- set (~25%) -> Eels au Barbecue, else (~25%) -> Prime Beef Steak. -- The three dish texts (SSAnneKitchenCook7SalmonDuSaladText / - -- ...EelsAuBarbecueText / ...PrimeBeefSteakText) aren't extracted - -- into data/generated/text.lua (no leading underscore in - -- pokered/text/SSAnneKitchen.asm), so their literal strings are - -- ported here verbatim. + -- ...EelsAuBarbecueText / ...PrimeBeefSteakText) have no leading + -- underscore in pokered/text/SSAnneKitchen.asm, but the extractor + -- collects them regardless (tools/extract/text.py); the literals + -- below are only the fallback for a catalog without them. TEXT_SSANNEKITCHEN_COOK7 = function(game, ow, npc, done) local t = game.data.text push(game, t._SSAnneKitchenCook7MainCourseIsText @@ -27,13 +27,16 @@ return { local dish if roll <= 2 then -- bit 7 of hRandomAdd set (~50%) - dish = "Salmon du Salad!\fLes guests may\ngripe it's fish\vagain, however!" + dish = t.SSAnneKitchenCook7SalmonDuSaladText + or "Salmon du Salad!\fLes guests may\ngripe it's fish\vagain, however!" elseif roll == 3 then -- bit 4 set, bit 7 clear (~25%) - dish = "Eels au Barbecue!\fLes guests will\nmutiny, I fear." + dish = t.SSAnneKitchenCook7EelsAuBarbecueText + or "Eels au Barbecue!\fLes guests will\nmutiny, I fear." else -- neither bit set (~25%) - dish = "Prime Beef Steak!\fBut, have I enough\nfillets du beef?" + dish = t.SSAnneKitchenCook7PrimeBeefSteakText + or "Prime Beef Steak!\fBut, have I enough\nfillets du beef?" end push(game, dish, done) end) diff --git a/data/scripts/flavor/viridian_city.lua b/data/scripts/flavor/viridian_city.lua index f78626f8..9213bb6c 100644 --- a/data/scripts/flavor/viridian_city.lua +++ b/data/scripts/flavor/viridian_city.lua @@ -60,22 +60,23 @@ M.VIRIDIAN_CITY = { -- you want to know about the two kinds of caterpillar Pokemon; -- YES -> CATERPIE/WEEDLE description, NO -> "Oh, OK then!". -- ViridianCityYoungster2OkThenText and - -- ViridianCityYoungster2CaterpieAndWeedleDescriptionText are - -- defined without a leading underscore in pokered/text/ViridianCity.asm - -- and aren't present in data/generated/text.lua, so we fall back to - -- the literal strings from pokered. Those fallbacks have to carry the - -- extractor's markers, not plain newlines: line -> \n, cont -> \v, - -- para -> \f. Spelling cont/para as \n and \n\n put all six lines on - -- one page with nothing to wait on, so the whole speech scrolled past - -- without a button press (#250). + -- ViridianCityYoungster2CaterpieAndWeedleDescriptionText are defined + -- without a leading underscore in pokered/text/ViridianCity.asm, but + -- tools/extract/text.py now collects them regardless -- the literal + -- strings below are only the fallback for a catalog without them. + -- Those fallbacks have to carry the extractor's markers, not plain + -- newlines: line -> \n, cont -> \v, para -> \f. Spelling cont/para as + -- \n and \n\n put all six lines on one page with nothing to wait on, + -- so the whole speech scrolled past without a button press (#250). TEXT_VIRIDIANCITY_YOUNGSTER2 = function(game, ow, npc, done) local t = text(game) ask(game, t._ViridianCityYoungster2YouWantToKnowAboutText or "You want to know\nabout the 2 kinds\vof caterpillar\vPOKéMON?", function(yes) if yes then - push(game, "CATERPIE has no\npoison, but\vWEEDLE does.\fWatch out for its\nPOISON STING!", done) + push(game, t.ViridianCityYoungster2CaterpieAndWeedleDescriptionText + or "CATERPIE has no\npoison, but\vWEEDLE does.\fWatch out for its\nPOISON STING!", done) else - push(game, "Oh, OK then!", done) + push(game, t.ViridianCityYoungster2OkThenText or "Oh, OK then!", done) end end) end, diff --git a/data/scripts/story5.lua b/data/scripts/story5.lua index 67002652..6f42068c 100644 --- a/data/scripts/story5.lua +++ b/data/scripts/story5.lua @@ -122,9 +122,8 @@ M.CINNABAR_LAB_METRONOME_ROOM = { -- TM42 Dream Eater (scripts/ViridianCity.asm, the fisher). The fisher's -- YouCanHaveThisText prints before GiveItem, so this gift needs a pre -- text (#775). Like the SilphCo2F worker (#393) that label carries no --- leading underscore, and on Red it sits outside the extractor's symbol --- set, so the literal from text/ViridianCity.asm rides along as the --- fallback; Yellow resolves the ROM string instead. +-- leading underscore; tools/extract/text.py now collects it regardless, +-- so preFallback below is just the safety net for a catalog without it. M.VIRIDIAN_CITY = { talk = { TEXT_VIRIDIANCITY_FISHER = gift({ @@ -146,9 +145,11 @@ M.SILPH_CO_2F = { talk = { TEXT_SILPHCO2F_SILPH_WORKER_F = gift({ flag = "EVENT_GOT_TM36", item = "TM_SELFDESTRUCT", - -- the label carries no leading underscore: pokered keeps this one in - -- the script bank, not the far-text bank (#393) + -- the label carries no leading underscore (#393); collected like any + -- other text/*.asm label now, preFallback is just the safety net pre = "SilphCo2FSilphWorkerFPleaseTakeThisText", + preFallback = "Eeek!\nNo! Stop! Help!\fOh, you're not\nwith TEAM ROCKET." + .. "\vI thought...\vI'm sorry. Here,\vplease take this!", received = "_SilphCo2FSilphWorkerFReceivedTM36Text", explain = "_SilphCo2FSilphWorkerFTM36ExplanationText", noRoom = "_SilphCo2FSilphWorkerFTM36NoRoomText", diff --git a/docs/linux-arm64-build.md b/docs/linux-arm64-build.md index b02e0ced..b36a34ab 100644 --- a/docs/linux-arm64-build.md +++ b/docs/linux-arm64-build.md @@ -105,8 +105,9 @@ trixie. This is a statement about the *compile environment*, not about where the artifact runs — building on your own newer distro would silently raise that floor and strand every user on an older one, with no symptom until they -download it. CI enforces the floor: `linux-arm64-build` fails if the highest -required glibc symbol version climbs above 2.31. +download it. `scripts/linux-arm64/verify_appimage.sh` enforces the floor in +both CI (`linux-arm64-build`) and the release workflow: the build fails if +the highest required glibc symbol version climbs above 2.31. ### Why five libraries are built from source @@ -172,13 +173,15 @@ Three jobs, path-gated on `scripts/build_linux_arm64.sh`, exclude list still classifies known sonames correctly, that AppRun still launches `game.love` with `--fused`, and that the host-arch guard actually fires. Needs no container and no arm64 machine. -- **`linux-arm64-build`** (`ubuntu-24.04-arm`) — the real build, then extracts - the artifact and asserts the layout, that every bundled object resolves - under AppRun's `LD_LIBRARY_PATH`, and that the glibc floor is still ≤ 2.31. - Uploads the AppImage for 7 days. +- **`linux-arm64-build`** (`ubuntu-24.04-arm`) — the real build, then + `scripts/linux-arm64/verify_appimage.sh` extracts the artifact and asserts + the layout, that every bundled object resolves under AppRun's + `LD_LIBRARY_PATH`, and that the glibc floor is still ≤ 2.31. Uploads the + AppImage for 7 days. - **release** — `linux-arm64` runs on `ubuntu-24.04-arm`, reuses the shared - `game.love` from the `love-payload` job, and the AppImage is staged and - published like every other release asset. + `game.love` from the `love-payload` job, runs the same + `verify_appimage.sh` checks on the shipped image, and the AppImage is + staged and published like every other release asset. Unlike the Switch job, none of this needs secrets or self-hosted hardware, so it runs on fork PRs too. diff --git a/docs/mod-api-gen2-compat.md b/docs/mod-api-gen2-compat.md index fb6bd21a..0e7e7ffd 100644 --- a/docs/mod-api-gen2-compat.md +++ b/docs/mod-api-gen2-compat.md @@ -55,9 +55,10 @@ The short version, for an author deciding what to write: ``` `games` is an optional array of version ids (`"red"`, `"blue"`, `"yellow"`, -`"gold"`), generations (`"gen1"`, `"gen2"`, case-insensitive) or `"all"`. -`src/mods/ModTargets.lua` resolves the tokens off `GameVersion.ORDER` and -`GameVersion.generation`, so nothing anywhere restates the game list. +`"gold"`, `"silver"`), generations (`"gen1"`, `"gen2"`, case-insensitive) or +`"all"`. `src/mods/ModTargets.lua` resolves the tokens off `GameVersion.ORDER` +and `GameVersion.generation`, so nothing anywhere restates the game list. +`"gen2"` now expands to both Gold and Silver. `Manifest.validate` stores the resolved, ORDER-sorted ids on `manifest.games` and **derives** `manifest.gen2compat` from them, which is the one field the loader's gate reads. diff --git a/docs/modding.md b/docs/modding.md index c487e5c2..4016e1f1 100644 --- a/docs/modding.md +++ b/docs/modding.md @@ -81,7 +81,7 @@ Every mod contains a root `manifest.json` defining its metadata, supported games | `entry` | `string` | Entry Lua file path relative to mod root (usually `"main.lua"`). | | `profile` | `string` | Mod profile: `"content"`, `"overhaul"`, or `"total_conversion"`. | | `category` | `string` | Categorization chip (e.g. `"GAMEPLAY"`, `"CONTENT"`, `"UI"`, `"AUDIO"`). | -| `games` | `array` | Supported game versions: `["gen1"]`, `["gen2"]`, `["red"]`, `["blue"]`, `["yellow"]`, `["gold"]`, or `["all"]`. | +| `games` | `array` | Supported game versions: `["gen1"]`, `["gen2"]`, `["red"]`, `["blue"]`, `["yellow"]`, `["gold"]`, `["silver"]`, or `["all"]`. | | `game_version`| `string` | Semver range of required engine version (e.g. `">=0.0.0-dev <2.0.0"`). | | `priority` | `integer` | Load priority order (lower numbers load earlier; dependencies always precede dependents regardless of priority). | | `dependencies` | `array` | Hard required dependencies. A mod will not load if a required dependency is missing or disabled for the active game. | diff --git a/docs/new-features.md b/docs/new-features.md index 7ab7c658..983034f2 100644 --- a/docs/new-features.md +++ b/docs/new-features.md @@ -11,10 +11,12 @@ Features intentionally added beyond the original Pokémon Red, Blue, and Yellow * **Persistent custom options** stored separately from game saves * **Optional widescreen battle layout** * **Mobile touch controls** with editable layouts, vibration, and orientation settings +* **Screen position setting** (center, upper, top) shared across all games, for clamp-on controllers that cover the lower screen * **Touch skins** in RetroArch overlay format and Delta `.deltaskin` (including PDF-wrapped bezel art), with per-button press states and Super Game Boy borders * **Pokédex diploma and printer image exports** ## Gen 2 Specifics +* **Pokémon Silver** as an importable, launcher-selectable version alongside Gold * **Mod manager** with Gen 1 mod adapters, per-game targeting, and `modkit gen2check` * **Followers** for mods, plus Gen 2-only registries and hooks diff --git a/docs/preparing-your-mod-for-gen2.md b/docs/preparing-your-mod-for-gen2.md index cf4eec9d..382a3d21 100644 --- a/docs/preparing-your-mod-for-gen2.md +++ b/docs/preparing-your-mod-for-gen2.md @@ -176,7 +176,7 @@ something the filesystem encodes. | token | means | | --- | --- | -| `"red"`, `"blue"`, `"yellow"`, `"gold"` | that one game (a version id from `GameVersion.ORDER`) | +| `"red"`, `"blue"`, `"yellow"`, `"gold"`, `"silver"` | that one game (a version id from `GameVersion.ORDER`) | | `"gen1"`, `"gen2"` | every game of that generation (case-insensitive; `"gen 2"` also parses) | | `"all"` | every game this engine has | diff --git a/docs/required-to-function.md b/docs/required-to-function.md index bc681949..84475d8e 100644 --- a/docs/required-to-function.md +++ b/docs/required-to-function.md @@ -1,7 +1,8 @@ # What This Port Requires The packaged desktop app requires one user-supplied input on first boot: a -canonical 1 MiB US Pokemon Red, Blue, or Yellow ROM. +canonical 1 MiB US Pokemon Red, Blue, or Yellow ROM, or a canonical 2 MiB US +Pokemon Gold or Silver ROM. The importer verifies the SHA-1 for the game (see `src/core/GameVersion.lua` for specific hashes). Other revisions and Virtual Console releases are rejected @@ -15,7 +16,8 @@ Python and Pillow are not required by the packaged app. Assembly removes high-level names and some relationships that the Lua port needs. The version-specific files `tools/rom_manifest.json`, -`tools/rom_manifest_blue.json`, and `tools/rom_manifest_yellow.json` therefore +`tools/rom_manifest_blue.json`, `tools/rom_manifest_yellow.json`, +`tools/rom_manifest_gold.json`, and `tools/rom_manifest_silver.json` therefore contain: - the ROM symbol addresses actually read by the extractor diff --git a/docs/switch-install.md b/docs/switch-install.md index 2567cf2c..a9ad0ade 100644 --- a/docs/switch-install.md +++ b/docs/switch-install.md @@ -91,14 +91,14 @@ Do **not** launch from the Album applet path for normal play. This project ships **no** game data. On first launch: -1. Put your own legally obtained Pokémon Red, Blue (`.gb`), Yellow, or - Gold (`.gbc`) dump into `switch/gen1recomp/pokemon-love2d/imports/` (the - launcher also shows the live save-dir path). All four can sit in the +1. Put your own legally obtained Pokémon Red, Blue (`.gb`), Yellow, Gold, or + Silver (`.gbc`) dump into `switch/gen1recomp/pokemon-love2d/imports/` (the + launcher also shows the live save-dir path). All five can sit in the same folder. -2. Use **Scan again** on that game's tab (Red / Blue / Yellow / Gold). - Rescan matches by ROM SHA-1 for the open tab only. A Red dump never - imports from the Yellow tab (and vice versa). Gold is Beta in the - launcher; a clean US Gold dump is enough to Play. +2. Use **Scan again** on that game's tab (Red / Blue / Yellow / Gold / + Silver). Rescan matches by ROM SHA-1 for the open tab only. A Red dump + never imports from the Yellow tab (and vice versa). Gold and Silver are + Beta in the launcher; a clean US dump of either is enough to Play. ## 5. Import / Export a raw `.sav` @@ -111,10 +111,12 @@ SD / FTP, same transfer methods as ROMs. Paths are **per game**: | Blue | `imports/saves/blue/` | `exports/blue/` | | Yellow | `imports/saves/yellow/` | `exports/yellow/` | | Gold | `imports/saves/gold/` | `exports/gold/` | +| Silver | `imports/saves/silver/` | `exports/silver/` | (Under the save dir `pokemon-love2d/`. The zip already creates these folders. -Gold cart `.sav` import/export is not supported yet -- the folders exist so -MTP browsing matches the other games. Gold progress still saves in-engine.) +Gold and Silver cart `.sav` import/export is not supported yet -- the folders +exist so MTP browsing matches the other games. Gold and Silver progress still +saves in-engine.) 1. Copy a Gen 1 `.sav` (32 KB) into that game's inbox under the save dir ([switch-transfer.md](switch-transfer.md)). diff --git a/docs/switch-transfer.md b/docs/switch-transfer.md index 1c027086..798a8f85 100644 --- a/docs/switch-transfer.md +++ b/docs/switch-transfer.md @@ -22,8 +22,8 @@ Player install (what to download, title override) stays in | Loose iteration pair | `sdmc:/switch/gen1recomp/gen1recomp.nro` **and** `game.love` beside it | | ROM inbox | LÖVE save dir → `imports/` (launcher shows the live `getSaveDirectory()` path; under MTP often `1: SD Card//imports/`) | | Mod zip inbox | Same save dir → `imports/mods/` then MODS → **Scan again** | -| Save `.sav` inbox | Same save dir → `imports/saves/red\|blue\|yellow\|gold/` then that game's SAVE FILES → **Import save** (Gold cart `.sav` not supported yet) | -| Save exports | Same save dir → `exports/red\|blue\|yellow\|gold/` (pull after **Export save**; Gold cart `.sav` not supported yet) | +| Save `.sav` inbox | Same save dir → `imports/saves/red\|blue\|yellow\|gold\|silver/` then that game's SAVE FILES → **Import save** (Gold / Silver cart `.sav` not supported yet) | +| Save exports | Same save dir → `exports/red\|blue\|yellow\|gold\|silver/` (pull after **Export save**; Gold / Silver cart `.sav` not supported yet) | | Opt-in diagnostics | Empty `switch-debug.txt` in the save dir → `switch.log` | | Lua error log | `lua-error.log` in the save dir | @@ -54,7 +54,8 @@ macOS, not a Mac-only requirement. 3. Create `switch/gen1recomp/` if needed; extract the release zip at SD root (or copy NRO / `game.love` for loose). 4. For ROMs/mods/saves, open the save-dir `imports/`, `imports/mods/`, - `imports/saves//`, or `exports//` + `imports/saves//`, or + `exports//` path the launcher prints. 5. Wait for the queue; refresh; exit MTP responder; title-override launch. diff --git a/main.lua b/main.lua index 87d81589..b820766c 100644 --- a/main.lua +++ b/main.lua @@ -386,11 +386,12 @@ function bootGame(version) love.window.setTitle(Version.title( GameVersion.info().displayName .. " (Gen 1 Recompilation Project)")) end - -- Gold: Gen 1 Game:load cannot consume a Gen 2 cache -- different generated - -- tables, save shape and screen registry -- so Gold boots its own service - -- owner, which mounts src/world/gen2 (walk / warps / connections) and the - -- Gen 2 screens instead of src/core/Game.lua's Gen 1 wiring. - if GameVersion.isGold() then + -- Gen 2: Gen 1 Game:load cannot consume a Gen 2 cache -- different generated + -- tables, save shape and screen registry -- so Gold and Silver boot their + -- own service owner, which mounts src/world/gen2 (walk / warps / + -- connections) and the Gen 2 screens instead of src/core/Game.lua's Gen 1 + -- wiring. + if GameVersion.generation() == 2 then Game = require("src.core.Game2").new() Game:load() else diff --git a/mobile/ANDROID.md b/mobile/ANDROID.md index b234058b..7680c614 100644 --- a/mobile/ANDROID.md +++ b/mobile/ANDROID.md @@ -109,10 +109,10 @@ The APK lands under `app/build/outputs/apk/embedNoRecord/debug/`. `app/src/embed/assets/game.love` - zip of `main.lua`, `conf.lua`, `src/`, `libs/` (the vendored FlexLove toolkit the launcher UI needs), `data/`, -`assets/`, and the Red, Blue, and Yellow ROM manifests. The Android -packer verifies the Yellow manifest before it packages; if a partial source -export omitted it, it restores the file from this checkout's Git data and then -falls back to the project's GitHub copy. Generated game data, +`assets/`, and the Red, Blue, Yellow, Gold, and Silver ROM manifests. The +Android packer verifies the Yellow, Gold, and Silver manifests before it +packages; if a partial source export omitted one, it restores the file from +this checkout's Git data and then falls back to the project's GitHub copy. Generated game data, scripts, tests, and mobile build sources are excluded. ## Branding (applied by the build script) diff --git a/mobile/android/app/src/main/res/drawable-hdpi/ic_shortcut_silver.png b/mobile/android/app/src/main/res/drawable-hdpi/ic_shortcut_silver.png new file mode 100644 index 00000000..30a113e3 Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-hdpi/ic_shortcut_silver.png differ diff --git a/mobile/android/app/src/main/res/drawable-mdpi/ic_shortcut_silver.png b/mobile/android/app/src/main/res/drawable-mdpi/ic_shortcut_silver.png new file mode 100644 index 00000000..c4d95a58 Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-mdpi/ic_shortcut_silver.png differ diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/ic_shortcut_silver.png b/mobile/android/app/src/main/res/drawable-xhdpi/ic_shortcut_silver.png new file mode 100644 index 00000000..7655ee86 Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xhdpi/ic_shortcut_silver.png differ diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/ic_shortcut_silver.png b/mobile/android/app/src/main/res/drawable-xxhdpi/ic_shortcut_silver.png new file mode 100644 index 00000000..e6ea7d4d Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xxhdpi/ic_shortcut_silver.png differ diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_shortcut_silver.png b/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_shortcut_silver.png new file mode 100644 index 00000000..b0efbbe1 Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_shortcut_silver.png differ diff --git a/mobile/android/app/src/main/res/values/colors.xml b/mobile/android/app/src/main/res/values/colors.xml index caf722e0..98c05cbb 100644 --- a/mobile/android/app/src/main/res/values/colors.xml +++ b/mobile/android/app/src/main/res/values/colors.xml @@ -8,4 +8,5 @@ #1E88E5 #FDD835 #D4AF37 + #BEC6D2 diff --git a/scripts/build.sh b/scripts/build.sh index a110d55c..10b3aec8 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -6,6 +6,7 @@ # # Usage: scripts/build.sh [mac|win|linux|android|ios|all] [--version X.Y.Z] [--identity "Developer ID Application: ..."] # [--notary-profile NAME] [--no-notarize] +# [--game-love PATH] # fuse a prebuilt payload (scripts/pack_love.sh) instead of packing one # [--release] # ios only: release config instead of debug # # Output: dist/mac/gen1recomp-macos.zip @@ -36,6 +37,7 @@ NOTARY_PROFILE="notary-profile" NOTARIZE=true IOS_RELEASE=false IOS_IPA=false +GAME_LOVE_IN="" say() { printf '\033[1;32m==>\033[0m %s\n' "$*"; } warn() { printf '\033[1;33mwarn:\033[0m %s\n' "$*" >&2; } @@ -48,6 +50,7 @@ while [ $# -gt 0 ]; do --identity) IDENTITY="$2"; shift ;; --notary-profile) NOTARY_PROFILE="$2"; shift ;; --no-notarize) NOTARIZE=false ;; + --game-love) GAME_LOVE_IN="${2:?--game-love needs a path}"; shift ;; --release) IOS_RELEASE=true ;; --ipa) IOS_IPA=true ;; *) fail "unknown argument: $1" ;; @@ -62,16 +65,23 @@ mkdir -p "$CACHE" "$WORK" "$DIST/mac" "$DIST/win" "$DIST/linux" # launcher's Edit button on a save row opens it in-process (main.lua), and # `--editor` / POKEPORT_EDITOR=1 opens it standalone. It is required through # love.filesystem's require path, so it has to live inside the archive. -say "packing game.love" LOVE_FILE="$WORK/game.love" rm -f "$LOVE_FILE" -# The launcher UI kit lives at src/ui/kit (inside src/, packed wholesale); -# the vendored libs/flexlove tree it replaced is gone. -(cd "$ROOT" && zip -q -9 -r "$LOVE_FILE" \ - main.lua conf.lua src data assets tools/save-editor \ - tools/rom_manifest.json tools/rom_manifest_blue.json \ - tools/rom_manifest_yellow.json tools/rom_manifest_gold.json \ - -x '*.DS_Store' 'data/generated/*' 'assets/generated/*') +if [ -n "$GAME_LOVE_IN" ]; then + [ -f "$GAME_LOVE_IN" ] || fail "--game-love: no such file: $GAME_LOVE_IN" + say "using prebuilt payload: $GAME_LOVE_IN" + cp "$GAME_LOVE_IN" "$LOVE_FILE" +else + say "packing game.love" + # The launcher UI kit lives at src/ui/kit (inside src/, packed wholesale); + # the vendored libs/flexlove tree it replaced is gone. + (cd "$ROOT" && zip -q -9 -r "$LOVE_FILE" \ + main.lua conf.lua src data assets tools/save-editor \ + tools/rom_manifest.json tools/rom_manifest_blue.json \ + tools/rom_manifest_yellow.json tools/rom_manifest_gold.json \ + tools/rom_manifest_silver.json \ + -x '*.DS_Store' 'data/generated/*' 'assets/generated/*') +fi # Materialize the listing once and grep the file: piping unzip straight into # grep -q under `set -o pipefail` SIGPIPEs unzip when grep exits early on a # match, and the pipeline's failure reads as "missing " for whichever @@ -90,7 +100,8 @@ for required in tools/save-editor/App.lua tools/save-editor/Kit.lua \ tools/save-editor/panels/Party.lua \ src/ui/kit/Kit.lua \ tools/rom_manifest.json tools/rom_manifest_blue.json \ - tools/rom_manifest_yellow.json tools/rom_manifest_gold.json; do + tools/rom_manifest_yellow.json tools/rom_manifest_gold.json \ + tools/rom_manifest_silver.json; do grep -qxF "$required" "$LOVE_LISTING" \ || fail "game.love is missing $required" done @@ -105,18 +116,26 @@ say "game.love: $(du -h "$LOVE_FILE" | cut -f1)" # mistaken for a release. The stamp is then read back out of the archive and the # build fails if it did not take. if printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then - say "stamping engine version $VERSION into game.love" - stamp_dir="$WORK/stamp" - rm -rf "$stamp_dir" - mkdir -p "$stamp_dir/src/core" - sed -E "s/(engine[[:space:]]*=[[:space:]]*\")[^\"]*(\")/\1$VERSION\2/" \ - "$ROOT/src/core/Version.lua" > "$stamp_dir/src/core/Version.lua" - (cd "$stamp_dir" && zip -q "$LOVE_FILE" src/core/Version.lua) - version_re="$(printf '%s' "$VERSION" | sed 's/\./\\./g')" - unzip -p "$LOVE_FILE" src/core/Version.lua \ - | grep -Eq "engine[[:space:]]*=[[:space:]]*\"$version_re\"" \ - || fail "version stamp failed: game.love does not report engine $VERSION" - say "stamped engine version: $VERSION" + if [ -n "$GAME_LOVE_IN" ]; then + version_re="$(printf '%s' "$VERSION" | sed 's/\./\\./g')" + unzip -p "$LOVE_FILE" src/core/Version.lua \ + | grep -Eq "engine[[:space:]]*=[[:space:]]*\"$version_re\"" \ + || fail "prebuilt payload does not report engine $VERSION (pack it with pack_love.sh --version $VERSION)" + say "prebuilt payload already stamped: $VERSION" + else + say "stamping engine version $VERSION into game.love" + stamp_dir="$WORK/stamp" + rm -rf "$stamp_dir" + mkdir -p "$stamp_dir/src/core" + sed -E "s/(engine[[:space:]]*=[[:space:]]*\")[^\"]*(\")/\1$VERSION\2/" \ + "$ROOT/src/core/Version.lua" > "$stamp_dir/src/core/Version.lua" + (cd "$stamp_dir" && zip -q "$LOVE_FILE" src/core/Version.lua) + version_re="$(printf '%s' "$VERSION" | sed 's/\./\\./g')" + unzip -p "$LOVE_FILE" src/core/Version.lua \ + | grep -Eq "engine[[:space:]]*=[[:space:]]*\"$version_re\"" \ + || fail "version stamp failed: game.love does not report engine $VERSION" + say "stamped engine version: $VERSION" + fi else say "version '$VERSION' is not X.Y.Z, shipping default engine (no stamp)" fi diff --git a/scripts/build_android.sh b/scripts/build_android.sh index 6644f3d5..1661cfc5 100755 --- a/scripts/build_android.sh +++ b/scripts/build_android.sh @@ -30,6 +30,8 @@ YELLOW_MANIFEST_RELATIVE="tools/rom_manifest_yellow.json" YELLOW_MANIFEST_URL="${YELLOW_MANIFEST_URL:-https://raw.githubusercontent.com/bryanthaboi/gen1recomp/main/tools/rom_manifest_yellow.json}" GOLD_MANIFEST_RELATIVE="tools/rom_manifest_gold.json" GOLD_MANIFEST_URL="${GOLD_MANIFEST_URL:-https://raw.githubusercontent.com/bryanthaboi/gen1recomp/main/tools/rom_manifest_gold.json}" +SILVER_MANIFEST_RELATIVE="tools/rom_manifest_silver.json" +SILVER_MANIFEST_URL="${SILVER_MANIFEST_URL:-https://raw.githubusercontent.com/bryanthaboi/gen1recomp/main/tools/rom_manifest_silver.json}" VERSION="" PACKAGE_ONLY=false @@ -177,6 +179,54 @@ ensure_gold_manifest() { fail "Gold import manifest is unavailable. Git recovery failed and could not download $GOLD_MANIFEST_URL" } +silver_manifest_is_valid() { + local path="$1" + python3 - "$path" <<'PY' +import json, pathlib, sys + +try: + manifest = json.loads(pathlib.Path(sys.argv[1]).read_text()) +except (OSError, ValueError): + raise SystemExit(1) + +raise SystemExit(0 if manifest.get("romSha1") == + "49b163f7e57702bc939d642a18f591de55d92dae" else 1) +PY +} + +ensure_silver_manifest() { + local manifest="$ROOT/$SILVER_MANIFEST_RELATIVE" + local staged + staged="$(mktemp)" + + if silver_manifest_is_valid "$manifest"; then + rm -f "$staged" + return + fi + + warn "Silver import manifest is missing or invalid; recovering it before packaging" + if git -C "$ROOT" show "HEAD:$SILVER_MANIFEST_RELATIVE" > "$staged" 2>/dev/null \ + && silver_manifest_is_valid "$staged"; then + mkdir -p "$(dirname "$manifest")" + mv "$staged" "$manifest" + say "restored Silver import manifest from this checkout's Git data" + return + fi + + if command -v curl >/dev/null 2>&1 \ + && curl --fail --location --retry 2 --connect-timeout 15 \ + --output "$staged" "$SILVER_MANIFEST_URL" \ + && silver_manifest_is_valid "$staged"; then + mkdir -p "$(dirname "$manifest")" + mv "$staged" "$manifest" + say "downloaded Silver import manifest from the project repository" + return + fi + + rm -f "$staged" + fail "Silver import manifest is unavailable. Git recovery failed and could not download $SILVER_MANIFEST_URL" +} + # --------------------------------------------------------------- branding # love-android 11.5+ reads app id / name / orientation from gradle.properties. # Manifest still gets permission trims. Re-applied every build so refreshing @@ -242,6 +292,7 @@ pack_game_love() { say "packing game.love for love-android embed flavor" ensure_yellow_manifest ensure_gold_manifest + ensure_silver_manifest mkdir -p "$EMBED_ASSETS" rm -f "$LOVE_FILE" # tools/save-editor ships with the app: the launcher's Edit button on a save @@ -256,6 +307,7 @@ pack_game_love() { main.lua conf.lua src data assets tools/save-editor \ tools/rom_manifest.json tools/rom_manifest_blue.json \ tools/rom_manifest_yellow.json tools/rom_manifest_gold.json \ + tools/rom_manifest_silver.json \ -x '*.DS_Store' -x '*/.git/*' -x '*/.DS_Store' \ -x 'data/generated/*' -x 'assets/generated/*') # List once and match against the captured text: piping unzip straight into @@ -277,6 +329,8 @@ pack_game_love() { || fail "game.love is missing the Yellow ROM import manifest" grep -qx 'tools/rom_manifest_gold.json' <<< "$archive_entries" \ || fail "game.love is missing the Gold ROM import manifest" + grep -qx 'tools/rom_manifest_silver.json' <<< "$archive_entries" \ + || fail "game.love is missing the Silver ROM import manifest" # This gate exists because the launcher's UI toolkit once lived outside # src/ (libs/flexlove) and was added to scripts/build.sh's payload and to # no other packager, so Android and iOS built an APK/IPA whose launcher diff --git a/scripts/linux-arm64/build_appimage.sh b/scripts/linux-arm64/build_appimage.sh index a83548f5..59d59a71 100755 --- a/scripts/linux-arm64/build_appimage.sh +++ b/scripts/linux-arm64/build_appimage.sh @@ -366,6 +366,8 @@ cp "$IN/game.love" "$APPDIR/game.love" unzip -Z1 "$APPDIR/game.love" > "$WORK/love-listing.txt" grep -qxF "tools/rom_manifest_gold.json" "$WORK/love-listing.txt" \ || fail "game.love is missing tools/rom_manifest_gold.json" +grep -qxF "tools/rom_manifest_silver.json" "$WORK/love-listing.txt" \ + || fail "game.love is missing tools/rom_manifest_silver.json" # The .desktop's Icon= resolves against the AppDir root by basename, and # .DirIcon is what appimaged and file-manager thumbnailers read. cp "$IN/icon.png" "$APPDIR/$APP_NAME.png" diff --git a/scripts/linux-arm64/selftest_build_linux_arm64.sh b/scripts/linux-arm64/selftest_build_linux_arm64.sh index 5b4ab651..8320222b 100755 --- a/scripts/linux-arm64/selftest_build_linux_arm64.sh +++ b/scripts/linux-arm64/selftest_build_linux_arm64.sh @@ -169,5 +169,7 @@ unzip -p "$temp_dir/game.love" src/core/Version.lua \ || fail "shared payload version was not stamped" grep -qxF "tools/rom_manifest_gold.json" "$temp_dir/love-listing.txt" \ || fail "shared payload is missing tools/rom_manifest_gold.json" +grep -qxF "tools/rom_manifest_silver.json" "$temp_dir/love-listing.txt" \ + || fail "shared payload is missing tools/rom_manifest_silver.json" say "Linux arm64 self-test passed" diff --git a/scripts/linux-arm64/verify_appimage.sh b/scripts/linux-arm64/verify_appimage.sh new file mode 100755 index 00000000..b437ebc5 --- /dev/null +++ b/scripts/linux-arm64/verify_appimage.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Verifies a built arm64 AppImage is self-contained and bullseye-compatible. +# Usage: scripts/linux-arm64/verify_appimage.sh + +set -euo pipefail + +image="${1:?usage: verify_appimage.sh }" +[ -f "$image" ] || { echo "::error::no such AppImage: $image"; exit 1; } +image="$(cd "$(dirname "$image")" && pwd)/$(basename "$image")" + +workdir="$(mktemp -d)" +trap 'rm -rf "$workdir"' EXIT +cd "$workdir" + +# --appimage-extract needs no FUSE, so this works on a runner +# without /dev/fuse and still exercises the real payload. +"$image" --appimage-extract >/dev/null +for required in AppRun bin/love game.love lib/liblove-11.5.so; do + [ -e "squashfs-root/$required" ] \ + || { echo "::error::AppImage is missing $required"; exit 1; } +done + +# Every bundled object must resolve once AppRun's LD_LIBRARY_PATH is +# applied; an unresolved soname here is a user-visible launch crash. +# +# This runs on a HEADLESS runner on purpose, and that is the point. +# The first version of this build bundled Debian's SDL2, which +# hard-links libpulse/libasound/libX11/libwayland, so it only ever +# started on a full desktop -- a bare runner is what exposed it. +missing="$(LD_LIBRARY_PATH="$PWD/squashfs-root/lib" \ + ldd squashfs-root/bin/love squashfs-root/lib/*.so* 2>/dev/null \ + | grep 'not found' || true)" +[ -z "$missing" ] || { echo "::error::unresolved deps:"; echo "$missing"; exit 1; } + +# Nothing may hard-link a driver, session or audio-stack library: +# those must be reached through dlopen so the AppImage runs on a box +# with only ALSA, only Wayland, or only KMSDRM. +linked="$(for f in squashfs-root/bin/love squashfs-root/lib/*.so*; do + objdump -p "$f" 2>/dev/null | awk '/NEEDED/{print $2}' +done | sort -u | grep -E '^lib(pulse|asound|X11|wayland|GL|EGL|drm|gbm|xcb|cairo|sndio|dbus)' || true)" +[ -z "$linked" ] \ + || { echo "::error::these must be dlopened, not linked:"; echo "$linked"; exit 1; } + +# The whole point of compiling on bullseye. If a future change moves +# the builder to a newer base, the glibc floor silently rises and +# every user on an older distro gets "GLIBC_2.xx not found" -- catch +# it here instead of in a release. +floor="$(objdump -T squashfs-root/bin/love squashfs-root/lib/*.so* 2>/dev/null \ + | grep -o 'GLIBC_[0-9.]*' | sort -V | tail -1)" +echo "highest required glibc symbol version: $floor" +[ -n "$floor" ] \ + || { echo "::error::found no versioned glibc symbols -- objdump read nothing"; exit 1; } +highest="$(printf '%s\n' "$floor" "GLIBC_2.31" | sort -V | tail -1)" +[ "$highest" = "GLIBC_2.31" ] \ + || { echo "::error::AppImage requires $floor, above the bullseye 2.31 floor"; exit 1; } + +echo "AppImage verified: $image" diff --git a/scripts/pack_love.sh b/scripts/pack_love.sh index beff791e..a32653b5 100755 --- a/scripts/pack_love.sh +++ b/scripts/pack_love.sh @@ -50,6 +50,7 @@ rm -f "$OUTPUT" main.lua conf.lua src data assets tools/save-editor \ tools/rom_manifest.json tools/rom_manifest_blue.json \ tools/rom_manifest_yellow.json tools/rom_manifest_gold.json \ + tools/rom_manifest_silver.json \ -x '*.DS_Store' 'data/generated/*' 'assets/generated/*') if [ -f "$ROOT/PATCH_NOTES.md" ]; then (cd "$ROOT" && zip -q "$OUTPUT" PATCH_NOTES.md) @@ -94,6 +95,7 @@ for required in tools/save-editor/App.lua tools/save-editor/Kit.lua \ tools/save-editor/panels/Party.lua \ tools/rom_manifest.json tools/rom_manifest_blue.json \ tools/rom_manifest_yellow.json tools/rom_manifest_gold.json \ + tools/rom_manifest_silver.json \ src/ui/kit/Kit.lua \ src/import/LauncherView.lua; do grep -qxF "$required" "$LISTING" \ diff --git a/scripts/run.sh b/scripts/run.sh index 49276927..6cfefde1 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -17,7 +17,9 @@ fail() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } if [ ! -f "$ROOT/data/generated/maps.lua" ] \ && [ ! -f "$ROOT/blue/data/generated/maps.lua" ] \ - && [ ! -f "$ROOT/yellow/data/generated/maps.lua" ]; then + && [ ! -f "$ROOT/yellow/data/generated/maps.lua" ] \ + && [ ! -f "$ROOT/gold/data/generated/maps.lua" ] \ + && [ ! -f "$ROOT/silver/data/generated/maps.lua" ]; then fail "generated data missing, run scripts/setup.sh first" fi diff --git a/scripts/switch/pack_sd_zip.sh b/scripts/switch/pack_sd_zip.sh index eb9d4b53..1d0478a9 100755 --- a/scripts/switch/pack_sd_zip.sh +++ b/scripts/switch/pack_sd_zip.sh @@ -71,15 +71,15 @@ First install or update (same steps): your saves, imported ROMs, mods, and options. Re-extracting only replaces the NRO(s) and these help files. 3. Launch with title override (hold R on HOME, open any title → hbmenu). - 4. Copy a legal Pokemon Red/Blue .gb or Yellow/Gold .gbc into: + 4. Copy a legal Pokemon Red/Blue .gb or Yellow/Gold/Silver .gbc into: switch/gen1recomp/pokemon-love2d/imports/ then use Scan again in the launcher if needed. Inboxes (drop files here via MTP / SD / FTP): imports/ — ROM .gb / .gbc imports/mods/ — community mod .zip - imports/saves/red|blue|yellow|gold/ — raw .sav import (Gold cart .sav not yet) - exports/red|blue|yellow|gold/ — pull after Export save (Gold not yet) + imports/saves/red|blue|yellow|gold|silver/ — raw .sav import (Gold/Silver cart .sav not yet) + exports/red|blue|yellow|gold|silver/ — pull after Export save (Gold/Silver not yet) Full guide: https://github.com/bryanthaboi/gen1recomp/blob/main/docs/switch-install.md EOF @@ -92,7 +92,7 @@ write_readme() { } write_readme "$SAVE_ROOT/imports/README.txt" \ - "Put a legal Pokemon Red/Blue .gb or Yellow/Gold .gbc here, then Scan again in the launcher." + "Put a legal Pokemon Red/Blue .gb or Yellow/Gold/Silver .gbc here, then Scan again in the launcher." write_readme "$SAVE_ROOT/imports/mods/README.txt" \ "Put community mod .zip files here, then MODS → Scan again." write_readme "$SAVE_ROOT/imports/saves/red/README.txt" \ @@ -103,6 +103,8 @@ write_readme "$SAVE_ROOT/imports/saves/yellow/README.txt" \ "Put a Yellow .sav (32 KB) here, then Yellow tab → SAVE FILES → Import save." write_readme "$SAVE_ROOT/imports/saves/gold/README.txt" \ "Gold cart .sav import is not supported yet. Folder reserved so MTP matches the other games." +write_readme "$SAVE_ROOT/imports/saves/silver/README.txt" \ + "Silver cart .sav import is not supported yet. Folder reserved so MTP matches the other games." write_readme "$SAVE_ROOT/exports/red/README.txt" \ "After Export save (Red), copy the .sav out of this folder via MTP / SD / FTP." write_readme "$SAVE_ROOT/exports/blue/README.txt" \ @@ -111,6 +113,8 @@ write_readme "$SAVE_ROOT/exports/yellow/README.txt" \ "After Export save (Yellow), copy the .sav out of this folder via MTP / SD / FTP." write_readme "$SAVE_ROOT/exports/gold/README.txt" \ "Gold cart .sav export is not supported yet. Folder reserved so MTP matches the other games." +write_readme "$SAVE_ROOT/exports/silver/README.txt" \ + "Silver cart .sav export is not supported yet. Folder reserved so MTP matches the other games." rm -f "$OUT_ZIP" ( @@ -140,10 +144,12 @@ REQUIRED=( "switch/gen1recomp/pokemon-love2d/imports/saves/blue/README.txt" "switch/gen1recomp/pokemon-love2d/imports/saves/yellow/README.txt" "switch/gen1recomp/pokemon-love2d/imports/saves/gold/README.txt" + "switch/gen1recomp/pokemon-love2d/imports/saves/silver/README.txt" "switch/gen1recomp/pokemon-love2d/exports/red/README.txt" "switch/gen1recomp/pokemon-love2d/exports/blue/README.txt" "switch/gen1recomp/pokemon-love2d/exports/yellow/README.txt" "switch/gen1recomp/pokemon-love2d/exports/gold/README.txt" + "switch/gen1recomp/pokemon-love2d/exports/silver/README.txt" ) for rel in "${REQUIRED[@]}"; do printf '%s\n' "$LISTING" | grep -Fq "$rel" || fail "zip missing $rel" diff --git a/scripts/switch/selftest_build_switch.sh b/scripts/switch/selftest_build_switch.sh index 9554cdd4..946a1107 100755 --- a/scripts/switch/selftest_build_switch.sh +++ b/scripts/switch/selftest_build_switch.sh @@ -271,10 +271,12 @@ for rel in \ "switch/gen1recomp/pokemon-love2d/imports/saves/blue/README.txt" \ "switch/gen1recomp/pokemon-love2d/imports/saves/yellow/README.txt" \ "switch/gen1recomp/pokemon-love2d/imports/saves/gold/README.txt" \ + "switch/gen1recomp/pokemon-love2d/imports/saves/silver/README.txt" \ "switch/gen1recomp/pokemon-love2d/exports/red/README.txt" \ "switch/gen1recomp/pokemon-love2d/exports/blue/README.txt" \ "switch/gen1recomp/pokemon-love2d/exports/yellow/README.txt" \ - "switch/gen1recomp/pokemon-love2d/exports/gold/README.txt" + "switch/gen1recomp/pokemon-love2d/exports/gold/README.txt" \ + "switch/gen1recomp/pokemon-love2d/exports/silver/README.txt" do printf '%s\n' "$ZIP_LIST" | grep -Fq "$rel" || PACK_MISSING="${PACK_MISSING} ${rel}" done diff --git a/scripts/test.sh b/scripts/test.sh index b41b683d..fffa984c 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -67,6 +67,7 @@ run_tier() { # ------- ROM-free tiers: these are what CI runs run_tier "T0 ROM builder version routing" python3 tests/build_rom_data_cli_test.py +run_tier "T0 ROM manifest generator pin/overrides" python3 tests/rom_manifest_generator_test.py run_tier "T0 switch CI workflow content gate" "$LUA" tests/switch_ci_workflows_test.lua run_tier "T0 switch transfer docs gate" "$LUA" tests/switch_transfer_docs_test.lua # NX Blue/Yellow asset overlay: ROM-free, must run on every checkout so a diff --git a/src/core/Game.lua b/src/core/Game.lua index 136aab99..b2c8e5bf 100644 --- a/src/core/Game.lua +++ b/src/core/Game.lua @@ -326,6 +326,9 @@ function Game:logicSpeed() if self.linkSession or (self.linkNet and not self.linkNet.closed) then return 1 end + if Game.isFixedSpeedInStack and Game.isFixedSpeedInStack(self.stack) then + return 1 + end if self.speedOverride then return GameSpeed.clamp(self.speedOverride) end -- Clamp here too, not just in _resolveLogicSpeed's vanilla path: a mod's -- core.logic_speed hook can return anything (0, negative, nil, NaN) and @@ -475,6 +478,15 @@ function Game.speedCategoryInStack(stack) return "menu" end +function Game.isFixedSpeedInStack(stack) + local states = stack and stack.states + for i = #(states or {}), 1, -1 do + local state = states[i] + if state and (state.isFixedSpeed or state.isMinigame) then return true end + end + return false +end + -- Whether a state on the stack composes its own screen and so wants the -- edge anchors held off (BattleState.holdsUIAnchors). Whole-stack, like -- everything else here: the text box and YES/NO a battle puts up are states @@ -1248,6 +1260,7 @@ function Game:applyOptions(opts) -- after VideoMode: a faithful-resolution lock is an exact window size, so -- it has to be the last word on the window (it drops fullscreen to hold) require("src.core.FaithfulRes").applyOptions(opts) + require("src.core.ScreenPosition").applyOptions(opts) -- normalizes a nil/garbage cap to the 60 default, so old saves with no -- fpsCap key pace at the standard rate (issue #88) require("src.core.FrameCap").applyOptions(opts) diff --git a/src/core/Game2.lua b/src/core/Game2.lua index f95fca7c..3eee6a6a 100644 --- a/src/core/Game2.lua +++ b/src/core/Game2.lua @@ -3,7 +3,7 @@ -- everything under src/*/gen2 reaches shared services through here. Gen 1 -- Game:load cannot consume a Gen 2 cache -- different generated tables, save -- shape and screen registry -- so main.lua's bootGame picks this owner when --- GameVersion.isGold(), and the two never branch into each other. +-- GameVersion.generation() == 2, and the two never branch into each other. -- -- Boot: copyright → GameFreak Presents → GS intro stub → title -- (tilemap + Ho-Oh flap / clouds / trails) → Oak speech (Marill + shrink) @@ -1660,8 +1660,9 @@ function Game2:drawScene(w, h) -- row to opt into the step-down half, so CENTERED is the whole rule -- here. local s = self.world:fitScale() + local ox, oy = Chrome.fitOrigin(w, h, s) G.push() - G.translate(math.floor((w - 160 * s) / 2), math.floor((h - 144 * s) / 2)) + G.translate(ox, oy) G.scale(s, s) self.stack:draw() G.pop() @@ -1702,7 +1703,7 @@ function Game2:hotkey(key) self:writeSave() return true elseif key == "f2" then - local loaded = Save.load("gold") + local loaded = Save.load() if loaded then self:continueGame(loaded) end return true elseif key == "1" then @@ -1982,6 +1983,7 @@ function Game2:applyOptions() haptics = options.haptics, }) require("src.core.VideoMode").applyOptions(options) + require("src.core.ScreenPosition").applyOptions(options) require("src.core.FrameCap").applyOptions(options) require("src.world.gen2.BorderFill").applyOptions(options) local GBCFX = require("src.render.GBCFX") diff --git a/src/core/GameVersion.lua b/src/core/GameVersion.lua index 8ec25399..b6435b86 100644 --- a/src/core/GameVersion.lua +++ b/src/core/GameVersion.lua @@ -1,5 +1,5 @@ -- Which game this process is running: Red (the historical default), Blue, --- Yellow, or Gold. One source of truth for everything that differs by +-- Yellow, Gold, or Silver. One source of truth for everything that differs by -- version -- the accepted ROM hash, the import manifest, where the -- extracted cache lives, and the save-file suffix -- so the importer, -- cache mount, SaveData, title screen and palette all agree. @@ -8,7 +8,8 @@ -- saves are untouched, but its extracted cache lives under red/ like Blue, -- Yellow, and Gold (issue #899); a legacy root cache is moved into red/ once -- by CacheFs.migrateLegacyRedCache. All supported versions can be imported --- and selected side by side. Gold is Gen 2 (see docs/gold-phase1.md). +-- and selected side by side. Gold and Silver are Gen 2 (see +-- docs/gold-phase1.md). -- -- Zero requires, so it loads during love.conf and under plain Lua for tools -- and tests. The active version is a process-global set once at boot from @@ -61,13 +62,26 @@ GameVersion.VERSIONS = { manifest = "tools/rom_manifest_gold.json", cachePrefix = "gold/", -- gold/data/generated, gold/assets/generated saveSuffix = "_gold", -- save_gold.lua / .bak / .tmp - -- The only row that carries one; absent reads as 1 (GameVersion.generation) + -- Absent reads as 1 (GameVersion.generation) + generation = 2, + }, + -- Gold's engine with edition-selected data; the manifest is derived from + -- Gold's by tools/make_silver_manifest.py. + silver = { + id = "silver", + label = "Silver", + displayName = "Pokemon Silver", + launcherName = "Silver (Beta)", + sha1 = "49b163f7e57702bc939d642a18f591de55d92dae", + manifest = "tools/rom_manifest_silver.json", + cachePrefix = "silver/", -- silver/data/generated, silver/assets/generated + saveSuffix = "_silver", -- save_silver.lua / .bak / .tmp generation = 2, }, } -- Launcher column order. -GameVersion.ORDER = { "red", "blue", "yellow", "gold" } +GameVersion.ORDER = { "red", "blue", "yellow", "gold", "silver" } GameVersion.current = "red" diff --git a/src/core/LaunchOptions.lua b/src/core/LaunchOptions.lua index 0bc98a3c..7317ba8a 100644 --- a/src/core/LaunchOptions.lua +++ b/src/core/LaunchOptions.lua @@ -39,6 +39,7 @@ local function normalizeVersion(v) b = "blue", blue = "blue", y = "yellow", yellow = "yellow", g = "gold", gold = "gold", + s = "silver", silver = "silver", } v = alias[v] or v if GameVersion.VERSIONS and not GameVersion.VERSIONS[v] then return nil end diff --git a/src/core/Music.lua b/src/core/Music.lua index 08930249..c700e591 100644 --- a/src/core/Music.lua +++ b/src/core/Music.lua @@ -230,7 +230,7 @@ end function Music.play(data, song, loop, ctx) if not song then return end - if not love.audio then return end -- headless test stub + if not (love and love.audio) then return end -- headless test stub ctx = ctx or {} song = selectSong(song, ctx) diff --git a/src/core/SaveData.lua b/src/core/SaveData.lua index e3d977f5..f8f5149e 100644 --- a/src/core/SaveData.lua +++ b/src/core/SaveData.lua @@ -285,6 +285,7 @@ function SaveData.defaultOptions() -- lock the window to an exact 160x144 multiple, 1..4 (0 = OFF); see -- src/core/FaithfulRes.lua. Ignored on mobile. faithfulRes = 0, + screenPos = "center", -- hard render frame-rate cap; render-only pacing (issue #88, FrameCap.lua) fpsCap = 60, -- graphics performance tier: auto | high | balanced | low. "auto" @@ -1279,13 +1280,26 @@ function SaveData.ensurePlaythroughId(save, injectedFs) local isFresh = save == freshPlaythrough if isFresh then freshPlaythrough = nil end local byVersion = opts.playthroughIds and opts.playthroughIds[version] - id = not isFresh and byVersion and byVersion[scope] or nil + local existing = byVersion and byVersion[scope] + id = not isFresh and existing or nil if type(id) ~= "string" or id == "" then id = SaveData.newPlaythroughId() - opts.playthroughIds = opts.playthroughIds or {} - opts.playthroughIds[version] = opts.playthroughIds[version] or {} - opts.playthroughIds[version][scope] = id - SaveData.saveOptions(opts, injectedFs) + -- A fresh skeleton still gets its own id (two unsaved New Games sharing a + -- slot must stay distinct), and it is still persisted when the slot has no + -- binding yet -- that is the contract a tool relies on to resolve + -- `selected` at the title after a restart, before any normal SAVE. + -- + -- What it must NOT do is OVERWRITE a binding that already exists. newGame() + -- marks a skeleton on the boot frame, before any save is loaded, and mods + -- initialise inside that window -- so a mod touching storage at init + -- replaced the real save's id with a throwaway, stranding that save's mod + -- storage and repeating on every launch. + if not (isFresh and type(existing) == "string" and existing ~= "") then + opts.playthroughIds = opts.playthroughIds or {} + opts.playthroughIds[version] = opts.playthroughIds[version] or {} + opts.playthroughIds[version][scope] = id + SaveData.saveOptions(opts, injectedFs) + end end save.meta.playthroughId = id return id diff --git a/src/core/ScreenPosition.lua b/src/core/ScreenPosition.lua new file mode 100644 index 00000000..d4834db4 --- /dev/null +++ b/src/core/ScreenPosition.lua @@ -0,0 +1,66 @@ +local ScreenPosition = {} + +ScreenPosition.MODES = { "center", "upper", "top" } +ScreenPosition.DEFAULT = "center" +ScreenPosition.mode = ScreenPosition.DEFAULT + +local LABELS = { center = "CENTER", upper = "UPPER", top = "TOP" } + +function ScreenPosition.normalize(v) + if LABELS[v] then return v end + return ScreenPosition.DEFAULT +end + +function ScreenPosition.label(v) + return LABELS[ScreenPosition.normalize(v)] +end + +function ScreenPosition.cycle(v, dir) + v = ScreenPosition.normalize(v) + local modes = ScreenPosition.MODES + local cur = 1 + for i, mode in ipairs(modes) do + if mode == v then cur = i break end + end + return modes[(cur - 1 + (dir or 1)) % #modes + 1] +end + +function ScreenPosition.setMode(v) + ScreenPosition.mode = ScreenPosition.normalize(v) +end + +function ScreenPosition.applyOptions(opts) + ScreenPosition.setMode(opts and opts.screenPos) +end + +function ScreenPosition.safeTop() + local ok, SafeArea = pcall(require, "src.core.SafeArea") + if not ok then return 0 end + local okr, _, y = pcall(SafeArea.rect) + if not okr then return 0 end + return math.max(0, tonumber(y) or 0) +end + +function ScreenPosition.skinActive(w, h) + local ok, TouchSkin = pcall(require, "src.core.TouchSkin") + if not ok or type(TouchSkin.viewport) ~= "function" then return false end + local okv, x = pcall(TouchSkin.viewport, w, h) + return okv and x ~= nil +end + +function ScreenPosition.lift(viewH, contentH, safeTop) + if ScreenPosition.mode == "center" then return 0 end + viewH = tonumber(viewH) or 0 + contentH = tonumber(contentH) or 0 + local slack = viewH - contentH + if slack <= 0 then return 0 end + local centered = math.floor(slack / 2) + local target = ScreenPosition.mode == "top" and 0 or math.floor(slack / 4) + safeTop = math.floor(tonumber(safeTop) or 0) + if safeTop > 0 and target < safeTop then + target = math.min(safeTop, centered) + end + return centered - target +end + +return ScreenPosition diff --git a/src/core/TouchControls.lua b/src/core/TouchControls.lua index dd9c679a..32190f90 100644 --- a/src/core/TouchControls.lua +++ b/src/core/TouchControls.lua @@ -218,7 +218,7 @@ function TouchControls.defaultLayout(ww, wh, ox, oy, scale) local ssW = dpadW * 0.30 local margin = dpadW * 0.12 local ok, GameVersion = pcall(require, "src.core.GameVersion") - if ok and GameVersion.isGold and GameVersion.isGold() then + if ok and GameVersion.generation and GameVersion.generation() == 2 then margin = math.max(margin, math.min(ww * 0.10, 72)) end return { diff --git a/src/core/gen2/Save.lua b/src/core/gen2/Save.lua index 29d7d661..945144d6 100644 --- a/src/core/gen2/Save.lua +++ b/src/core/gen2/Save.lua @@ -103,7 +103,7 @@ Save.PLAYER_STATES = { } local function saveNames(version) - version = version or "gold" + version = version or GameVersion.get() -- Resolve the ACTIVE SLOT the same way SaveData does, and only fall back to -- the flat save_.lua when no slot is registered. -- @@ -133,16 +133,22 @@ local function fs() return love.filesystem end --- A fresh Gold save. `opts` carries what the intro collected: player name, +-- The blank-name fallback is the first PlayerNameArray row, which differs +-- per edition -- data/player_names.asm:12-23. +function Save.defaultPlayerName(version) + return (version or GameVersion.get()) == "silver" and "SILVER" or "GOLD" +end + +-- A fresh Gen 2 save. `opts` carries what the intro collected: player name, -- rival name, and the options the OPTION screen was left on. function Save.newGame(opts) opts = opts or {} local save = { format = Save.FORMAT, - version = "gold", + version = GameVersion.get(), generation = 2, player = { - name = opts.playerName or "GOLD", + name = opts.playerName or Save.defaultPlayerName(), -- _ResetWRAM rolls wPlayerID out of hRandomSub/hRandomAdd -- (engine/menus/intro_menu.asm:41-49). id = opts.trainerId or rand(0, 65535), @@ -282,6 +288,7 @@ Save.DEFAULT_OPTIONS = { musicFilter = 0, -- low-pass steps, 0 = off haptics = "light", touchControls = { enabled = true }, + screenPos = "center", } function Save.defaultOptions() @@ -302,7 +309,7 @@ end Save.OPTIONS_KEY = "gold" local SHARED_KEYS = { - touchControls = true, haptics = true, + touchControls = true, haptics = true, screenPos = true, mods = true, modsByVersion = true, modsGen2 = true, modOptions = true, modProfiles = true, modProfilesSeeded = true, activeProfile = true, @@ -374,10 +381,13 @@ end function Save.normalize(save) if type(save) ~= "table" then return nil end save.format = save.format or Save.FORMAT - save.version = "gold" + if not (GameVersion.VERSIONS[save.version] + and GameVersion.generation(save.version) == 2) then + save.version = GameVersion.get() + end save.generation = 2 save.player = save.player or {} - save.player.name = save.player.name or "GOLD" + save.player.name = save.player.name or Save.defaultPlayerName(save.version) save.player.id = save.player.id or rand(0, 65535) save.player.money = math.max(0, math.min(save.player.money or 0, Save.MAX_MONEY)) save.player.coins = math.max(0, math.min(save.player.coins or 0, Save.MAX_COINS)) @@ -753,21 +763,24 @@ end -- copy is the witness that survives a crash mid-replace. function Save.save(save) if type(save) ~= "table" then return false, "no save" end - if (save.version or "gold") == "gold" then + Save.normalize(save) + local version = save.version + do local ok, SaveData = pcall(require, "src.core.SaveData") - if ok and SaveData.activeSlot and not SaveData.activeSlot("gold") then - local id = SaveData.createSlot and SaveData.createSlot("gold") - if id and SaveData.setActiveSlot then SaveData.setActiveSlot("gold", id) end + if ok and SaveData.activeSlot and not SaveData.activeSlot(version) then + local id = SaveData.createSlot and SaveData.createSlot(version) + if id and SaveData.setActiveSlot then + SaveData.setActiveSlot(version, id) + end end end - local main, backup, tmp = saveNames(save.version) + local main, backup, tmp = saveNames(version) local f = fs() if not f then return false, "no filesystem" end -- saveNames may now return a saves//.lua path, and -- love.filesystem.write does not create missing parent directories. local dir = main:match("^(.*)/[^/]+$") if dir and f.createDirectory then f.createDirectory(dir) end - Save.normalize(save) save.savedAt = os.time() local encoded = SaveSerializer.encode(save) if f.getInfo(main) then diff --git a/src/debug/SwitchDiagnostics.lua b/src/debug/SwitchDiagnostics.lua index 8d60a147..04ed4403 100644 --- a/src/debug/SwitchDiagnostics.lua +++ b/src/debug/SwitchDiagnostics.lua @@ -249,10 +249,12 @@ function SwitchDiagnostics.probeAssets(version) end -- Shallow listing so we can see if the extract tree exists at all. - local roots = { "yellow", "blue", "gold", "assets", "yellow/assets/generated", + local roots = { "yellow", "blue", "gold", "silver", "assets", + "yellow/assets/generated", "yellow/assets/generated/sprites", "blue/assets/generated/sprites", "gold/assets/generated", "gold/assets/generated/sprites", - "gold/data/generated" } + "gold/data/generated", "silver/assets/generated", + "silver/data/generated" } for _, dir in ipairs(roots) do local info = filesystem.getInfo(dir) if info and info.type == "directory" and filesystem.getDirectoryItems then diff --git a/src/import/ExtractThread.lua b/src/import/ExtractThread.lua index 26eb84dc..b1bf99b2 100644 --- a/src/import/ExtractThread.lua +++ b/src/import/ExtractThread.lua @@ -27,7 +27,8 @@ local ok, err = pcall(function() CacheFs.prefix = prefix local manifest = require("src.import.RomManifest").decode(version) - local RomExtractor = version == "gold" + local RomExtractor = + require("src.core.GameVersion").generation(version) == 2 and require("src.import.RomExtractorGen2") or require("src.import.RomExtractor") diff --git a/src/import/LauncherSettings.lua b/src/import/LauncherSettings.lua index dc14a5c1..b832121e 100644 --- a/src/import/LauncherSettings.lua +++ b/src/import/LauncherSettings.lua @@ -332,6 +332,17 @@ local function coreRows(opts, hooks) end) end + local okSp, ScreenPos = pcall(require, "src.core.ScreenPosition") + if okSp then + add(Strings("SCREEN POS"), + function() return Strings(ScreenPos.label(opts.screenPos)) end, + function(dir) + opts.screenPos = ScreenPos.cycle(opts.screenPos, dir) + ScreenPos.setMode(opts.screenPos) + return true + end) + end + local okCap, FrameCap = pcall(require, "src.core.FrameCap") if okCap then add(Strings("MAX FPS"), @@ -550,9 +561,10 @@ end -- opened on the Gold tab used to offer a dozen controls that did nothing -- and hide the seven that the cart itself has. -- --- The block lives in options.lua under `gold`, which is exactly where --- src/core/gen2/Save.lua loadOptions reads it, so an edit here is live on the --- next boot the same way a Gen 1 edit is. Ladders mirror +-- The block lives in options.lua under `gold` (the historical key; Gold and +-- Silver share it the way the Gen 1 games share the flat namespace), which is +-- exactly where src/core/gen2/Save.lua loadOptions reads it, so an edit here +-- is live on the next boot the same way a Gen 1 edit is. Ladders mirror -- src/ui/gen2/OptionsMenu.lua's ROWS; when editing one, keep the two in sync. local GEN2_KEY = "gold" @@ -699,7 +711,9 @@ end function LauncherSettings.open(hooks, version) local opts = SaveData.loadOptions() local sections - if version == "gold" then + local GameVersion = require("src.core.GameVersion") + if GameVersion.VERSIONS[version] + and GameVersion.generation(version) == 2 then local block = opts[GEN2_KEY] if type(block) ~= "table" then block = {} diff --git a/src/import/LauncherView.lua b/src/import/LauncherView.lua index 37e49781..49f3a8e9 100644 --- a/src/import/LauncherView.lua +++ b/src/import/LauncherView.lua @@ -14,9 +14,9 @@ -- height, and flex-shrink compressing text until it overlapped. -- -- THE RULES THIS FILE FOLLOWS: --- * Lists paginate (Kit.pager). The installed-mod list also scrolls inside --- its viewport, so each of its pages can hold at least ten entries without --- requiring a tall window. Pages still bound how many mod rows we visit. +-- * Short lists paginate (Kit.pager, perPage from Kit.rowsThatFit); the +-- installed-mod list is one continuous scroll instead, drawing only the +-- rows inside the region viewport so the window bounds the frame cost. -- * Every click handler only QUEUES work (imp._uiActions); update() drains -- the queue, so an action that tears the view down (Play, Edit save) -- never runs inside the frame that dispatched it. @@ -45,9 +45,6 @@ local COMMUNITY_URL = "https://bois.icu" local ACT_DEDUP = 0.35 -- Finger travel past this (px) is a drag, not a tap. local TAP_SLOP2 = 16 * 16 --- Installed mods should not turn into a one- or two-item pager on a compact --- display. Keep a useful page size, then let the list viewport scroll. -local MIN_MODS_PER_PAGE = 10 local MIN_SKIN_ROWS = 4 local SKIN_FORMAT_LABEL = { native = "GEN1", @@ -81,15 +78,6 @@ local function setTabScroll(imp, value) imp._tabScroll[tabKeyOf(imp)] = clamp(value, 0, tabScrollMax(imp)) end -local function modListWantsWheel(imp, wheel) - if imp.tab ~= "mods" or (imp._modScrollMax or 0) <= 0 then return false end - if not inRect(imp._modListRect, Kit.mouseX, Kit.mouseY) then return false end - if not inRect(imp._tabRegionRect, Kit.mouseX, Kit.mouseY) then return false end - local at = clamp(imp.modScroll or 0, 0, imp._modScrollMax) - if wheel < 0 then return at < imp._modScrollMax end - return at > 0 -end - -- ------------------------------------------------------------- lifecycle local function ensureState(imp) @@ -192,9 +180,6 @@ function LauncherView.touchpressed(imp, id, x, y) imp._touchAt = imp._touchAt or {} imp._touchAt[tostring(id)] = { x = x, y = y, - modsList = imp.tab == "mods" and (imp._modScrollMax or 0) > 0 - and inRect(imp._modListRect, x, y) - and inRect(imp._tabRegionRect, x, y), region = tabScrollMax(imp) > 0 and inRect(imp._tabRegionRect, x, y), } end @@ -207,19 +192,9 @@ function LauncherView.touchmoved(imp, id, x, y) if ddx * ddx + ddy * ddy > TAP_SLOP2 then start.dragged = true end - -- A drag that began in the installed-mod viewport scrolls that page's - -- rows. Its pager remains available for moving to the next ten-plus - -- entries; a drag elsewhere keeps the normal short-window page scroll. if start.dragged then local last = start.lastY or start.y local move = -(y - last) - if start.modsList then - local listMax = imp._modScrollMax or 0 - local at, leftover = Kit.scrollHandoff( - clamp(imp.modScroll or 0, 0, listMax), listMax, move) - imp.modScroll = at - move = leftover - end if move ~= 0 and start.region then local at, leftover = Kit.scrollHandoff(tabScrollAt(imp), tabScrollMax(imp), move) @@ -258,6 +233,22 @@ function LauncherView.clickAt(imp, x, y) imp._clickPt = { x = x, y = y } end +-- Event-driven click: a macOS trackpad tap delivers press+release inside one +-- frame, so update()'s love.mouse.isDown poll never sees it. Mint the click +-- from the press event under the poll's own suppression rules, and mark the +-- press seen so the poll cannot mint a second one when isDown does catch it. +function LauncherView.mousepressed(imp, x, y) + if not imp._flex then return end + local now = love.timer.getTime() + local touching = imp._touchAt ~= nil and next(imp._touchAt) ~= nil + if touching or now < (imp._suppressMouseUntil or 0) + or now < (imp._suppressClickUntil or 0) then + return + end + imp._clickPt = { x = x, y = y } + imp._prevMouseDown = true +end + -- Keyboard focus ring. Returns true when the key was consumed. Arrows arm -- the ring; Enter only activates a focused control once the user has actually -- used the arrows this session, so the long-standing "Enter plays the visible @@ -343,7 +334,7 @@ end local CART_COLOR = { red = PAL.railRed, blue = PAL.railBlue, yellow = PAL.railGold, - gold = PAL.railAmber, + gold = PAL.railAmber, silver = PAL.railSilver, } local function cartColor(version) return CART_COLOR[version] or PAL.green @@ -418,13 +409,13 @@ end local function cartPill(project, x, y, w, h, z, color, alpha) local points, radius = {}, h / 2 for i = 0, 10 do - local a = math.pi + math.pi * i / 10 - points[#points + 1] = { project(x + radius + math.cos(a) * radius, + local a = -math.pi / 2 + math.pi * i / 10 + points[#points + 1] = { project(x + w - radius + math.cos(a) * radius, y + radius + math.sin(a) * radius, z) } end for i = 0, 10 do - local a = math.pi * i / 10 - points[#points + 1] = { project(x + w - radius + math.cos(a) * radius, + local a = math.pi / 2 + math.pi * i / 10 + points[#points + 1] = { project(x + radius + math.cos(a) * radius, y + radius + math.sin(a) * radius, z) } end cartPolygon(points, color, alpha) @@ -605,7 +596,7 @@ local function cartridgeButton(imp, x, y, w, h, key, version, gameName, action) end local halfW, halfH = w / 2, h / 2 - local depth = math.max(8, w * 0.14) + local depth = math.max(6, w * 0.10) local project = function(px, py, pz) return cartProject(cx + pressX, cy + pressY, yaw, pitch, px * pressedScale, py * pressedScale, pz * pressedScale) @@ -648,6 +639,7 @@ local function cartridgeButton(imp, x, y, w, h, key, version, gameName, action) cartPolygon({ mainFront[2], mainFront[3], mainBack[3], mainBack[2] }, side, 1) cartPolygon({ mainFront[3], mainFront[4], mainBack[4], mainBack[3] }, side, 1) cartPolygon({ mainFront[1], mainFront[2], mainBack[2], mainBack[1] }, side, 1) + cartPolygon({ mainFront[4], mainFront[1], mainBack[1], mainBack[4] }, side, 1) cartPolygon({ capFront[2], capFront[3], capBack[3], capBack[2] }, side, 1) cartPolygon({ capFront[1], capFront[2], capBack[2], capBack[1] }, side, 1) cartPolygon({ capFront[4], capFront[1], capBack[1], capBack[4] }, side, 1) @@ -657,22 +649,72 @@ local function cartridgeButton(imp, x, y, w, h, key, version, gameName, action) else cartPolygon(mainBack, side, 1) cartPolygon(capBack, side, 1) + -- The tri-wing security screw: a domed brass head with three teardrop + -- recesses pinwheeled at 120 degrees. + local backZ = -(depth + 0.8) + local sd = math.min(w, h) * 0.11 + cartPill(project, -sd * 0.62, -sd * 0.62, sd * 1.24, sd * 1.24, backZ, + { math.floor(shell[1] * 0.4), math.floor(shell[2] * 0.4), + math.floor(shell[3] * 0.4) }, 0.9) + cartPill(project, -sd / 2, -sd / 2, sd, sd, backZ - 0.4, + { 196, 186, 148 }, 1) + cartPill(project, -sd * 0.32, -sd * 0.32, sd * 0.64, sd * 0.64, + backZ - 0.6, { 220, 212, 178 }, 0.8) + local r = sd / 2 + for k = 0, 2 do + local a = -math.pi / 2 + k * (2 * math.pi / 3) + local ux, uy = math.cos(a), math.sin(a) + local vx, vy = -uy, ux + local r0, r1 = r * 0.16, r * 0.82 + local w0, w1 = r * 0.13, r * 0.3 + cartPolygon({ + { project(ux * r0 + vx * w0, uy * r0 + vy * w0, backZ - 0.8) }, + { project(ux * r0 - vx * w0, uy * r0 - vy * w0, backZ - 0.8) }, + { project(ux * r1 - vx * w1, uy * r1 - vy * w1, backZ - 0.8) }, + { project(ux * r1 + vx * w1, uy * r1 + vy * w1, backZ - 0.8) }, + }, { 112, 104, 76 }, 1) + end end if frontFacing then local faceZ = depth + 0.8 - for i = 0, 4 do - local ry = mainTop + 7 + i * h * 0.025 - cartPolygon(cartQuad(project, -halfW + 2, ry, w * 0.13, 2, faceZ), side, 0.7) - cartPolygon(cartQuad(project, halfW - w * 0.13 - 2, ry, w * 0.13, 2, faceZ), side, 0.7) + -- The shell's grip grooves: a stack beside the label recess on the left, + -- and one below the top-right corner notch, like the DMG cart. + local grooveW = w * 0.115 + local grooveH = math.max(1, h * 0.009) + for i = 0, 5 do + local ry = mainTop + h * 0.014 + i * h * 0.021 + cartPolygon(cartQuad(project, -halfW + w * 0.02, ry, + grooveW, grooveH, faceZ), side, 0.7) + cartPolygon(cartQuad(project, halfW - grooveW - w * 0.02, ry, + grooveW, grooveH, faceZ), side, 0.7) end - local recessX, recessY = -w * 0.32, mainTop + h * 0.023 - local recessW, recessH = w * 0.64, h * 0.24 - cartPolygon(cartQuad(project, recessX, recessY, recessW, recessH, faceZ), shell, 0.88) - cartPill(project, recessX + w * 0.025, recessY + h * 0.025, - recessW - w * 0.05, h * 0.12, faceZ + 0.5, shell, 0.7) - cartPill(project, recessX + w * 0.045, recessY + h * 0.043, - recessW - w * 0.09, h * 0.083, faceZ + 0.8, side, 0.42) + -- The thin diagonal mold ridge cut into each long side a little below + -- the grip grooves, mirrored left/right. + local function diagonal(x0, y0, x1, y1) + local dx, dy = x1 - x0, y1 - y0 + local len = math.sqrt(dx * dx + dy * dy) + local nx, ny = -dy / len, dx / len + local t = math.max(0.6, h * 0.004) + cartPolygon({ + { project(x0 + nx * t, y0 + ny * t, faceZ) }, + { project(x0 - nx * t, y0 - ny * t, faceZ) }, + { project(x1 - nx * t, y1 - ny * t, faceZ) }, + { project(x1 + nx * t, y1 + ny * t, faceZ) }, + }, side, 0.7) + end + local dgY = mainTop + h * 0.20 + diagonal(-halfW + w * 0.006, dgY, -halfW + w * 0.085, dgY + h * 0.038) + diagonal(halfW - w * 0.006, dgY, halfW - w * 0.085, dgY + h * 0.038) + -- The Nintendo GAME BOY recess: one stadium pill sunk into the shell. + local pillX, pillW = -halfW + w * 0.17, w * 0.62 + local pillY, pillH = mainTop + h * 0.024, h * 0.115 + cartPill(project, pillX, pillY, pillW, pillH, faceZ + 0.5, side, 0.55) + local inX, inY = w * 0.008, h * 0.008 + cartPill(project, pillX + inX, pillY + inY, + pillW - 2 * inX, pillH - 2 * inY, faceZ + 0.8, + { math.floor(shell[1] * 0.92), math.floor(shell[2] * 0.92), + math.floor(shell[3] * 0.92) }, 1) local labelX, labelY = -w * 0.33, -h * 0.20 local labelW, labelH = w * 0.66, h * 0.55 @@ -942,6 +984,8 @@ local GAME_TABS = { label = "Yellow" }, { id = "gold", key = "tab-gold", letter = "G", color = PAL.railAmber, label = "Gold" }, + { id = "silver", key = "tab-silver", letter = "S", color = PAL.railSilver, + label = "Silver" }, } local HEADER_TABS = { @@ -1114,16 +1158,11 @@ local function buildHeader(imp, m) local tabGap = math.floor(6 * m.s) local tabRowGap = math.floor(4 * m.s) - -- the cartridge dropdown, sized to its longest label so switching games - -- never reflows the row + -- the cartridge dropdown: just the game's initial and the caret; the + -- popup list carries the full names local chrome0 = headerChrome(imp) local game = currentGame(imp) - local labelW = 0 - for _, g in ipairs(GAME_TABS) do - labelW = math.max(labelW, Kit.textWidth("tab", Strings(g.label))) - end - local dropW = math.min(tabRight - tabLeft, - tabH + labelW + math.floor(34 * m.s)) + local dropW = math.min(tabRight - tabLeft, tabH + math.floor(24 * m.s)) chrome0.game.color = game.color chrome0.game.letter = game.letter chrome0.game.active = imp.tab == game.id @@ -1133,7 +1172,7 @@ local function buildHeader(imp, m) -- flip with it or it vanishes into the cartridge colour local gameInvert = chrome0.game.active or gameHot chrome0.game.ring = gameHot and not chrome0.game.active or nil - btn(imp, tx, ty, dropW, tabH, "tab-game", Strings(game.label), chrome0.game) + btn(imp, tx, ty, dropW, tabH, "tab-game", "", chrome0.game) do local cw = math.floor(7 * m.s) local ccx = tx + dropW - math.floor(14 * m.s) @@ -1147,7 +1186,7 @@ local function buildHeader(imp, m) end tx = tx + dropW + tabGap - for _, t in ipairs(tabs) do + local function headerTab(t) local w = tabH if tx > tabLeft and tx + w > tabRight then tx = tabLeft @@ -1160,6 +1199,11 @@ local function buildHeader(imp, m) btn(imp, tx, ty, w, tabH, t.key, "", o) tx = tx + w + tabGap end + -- The bug-report chip sits LAST, past the sync chip. + local bugTab + for _, t in ipairs(tabs) do + if t.id == "bug" then bugTab = t else headerTab(t) end + end do local w = tabH @@ -1181,6 +1225,7 @@ local function buildHeader(imp, m) end tx = tx + w + tabGap end + if bugTab then headerTab(bugTab) end -- `ty` has walked down with the wraps, so this stays correct at one row too. y = ty + tabH + math.floor(8 * m.s) @@ -1964,7 +2009,6 @@ local function buildModsPanel(imp, x, y, w, availH, m) cy = cy + buildModScopeRow(imp, x, cy, w, m) if #mods == 0 then - imp.modScroll, imp._modScrollMax, imp._modListRect = 0, 0, nil Kit.emptyBox(x, cy, w, math.floor(110 * m.s), imp:_modsEmptyHint()) return (cy - y) + math.floor(110 * m.s) end @@ -2005,159 +2049,139 @@ local function buildModsPanel(imp, x, y, w, availH, m) end -- A mod row is a fixed height: its details first, then a dedicated second - -- line of per-game checkboxes. Fixed because a page of uniform rows is - -- what lets perPage come from the viewport. + -- line of per-game checkboxes. Fixed row heights are what make the + -- cull below plain arithmetic. local togH = math.floor(26 * m.s) local gamesLabel = Strings("Enable for:") local textH = Kit.textHeight("button") + math.floor(4 * m.s) + Kit.textHeight("small") + math.floor(2 * m.s) + Kit.textHeight("small") local rowH = math.floor(8 * m.s) + textH + math.floor(8 * m.s) + togH + math.floor(8 * m.s) - local pagerH = math.max(Kit.tapMin(), math.floor(30 * m.s)) - local listH = availH - (cy - y) - pagerH - gap - local perPage = Kit.rowsThatFit(listH, rowH, gap, MIN_MODS_PER_PAGE, 20) - local first, last, cur, pages = Kit.pageBounds(page(imp, "mods"), #mods, perPage) - setPage(imp, "mods", cur) local listTop = cy - local shown = math.max(0, last - first + 1) - local contentH = shown * rowH + math.max(0, shown - 1) * gap - local scrollMax = math.max(0, contentH - listH) - local scroll = clamp(imp.modScroll or 0, 0, scrollMax) - local lr = imp._modListRect - if not lr then lr = {}; imp._modListRect = lr end - lr.x, lr.y, lr.w, lr.h = x, listTop, w, listH - imp._modScrollMax = scrollMax - if scrollMax > 0 and (Kit.wheelY or 0) ~= 0 and not Kit.blockClicks - and Kit.hit(x, listTop, w, listH) then - scroll = clamp(scroll - Kit.wheelY * math.floor(48 * m.s), 0, scrollMax) - Kit.wheelY = 0 - elseif scrollMax == 0 then - local wheelPage = Kit.wheelPage(x, listTop, w, listH, cur, #mods, perPage) - if wheelPage ~= cur then imp.modScroll = 0 end - setPage(imp, "mods", wheelPage) - end - imp.modScroll = scroll - Kit.pushClip(x, listTop, w, listH) - for i = first, last do + -- One continuous list: every row is laid out, the region scroll moves + -- through all of it, and only rows inside the region's viewport draw -- + -- so the per-frame cost stays bounded by the window, not the list. + local view = imp._tabRegionRect + local viewTop = view and view.y or listTop + local viewBot = view and (view.y + view.h) or (listTop + availH) + for i = 1, #mods do local mod = mods[i] - local ry = listTop + (i - first) * (rowH + gap) - scroll - local rowKey = rowKeyFor(imp, "mod-row-", mod.id) - local isFullyDisabled = true - if mod.enabledByVersion then - for _, on in pairs(mod.enabledByVersion) do - if on then isFullyDisabled = false; break end + local ry = listTop + (i - 1) * (rowH + gap) + if ry + rowH >= viewTop and ry <= viewBot then + local rowKey = rowKeyFor(imp, "mod-row-", mod.id) + local isFullyDisabled = true + if mod.enabledByVersion then + for _, on in pairs(mod.enabledByVersion) do + if on then isFullyDisabled = false; break end + end + else + isFullyDisabled = not mod.enabled end - else - isFullyDisabled = not mod.enabled - end - local focused = Kit.focusable(rowKey, x, ry, w, rowH) - local hot = focused or Kit.hover(x, ry, w, rowH) - if isFullyDisabled then - Kit.card(x, ry, w, rowH, hot and "mutedHot" or "muted") - else - Kit.card(x, ry, w, rowH, hot) - end - local pad = math.floor(12 * m.s) - local px, inner = x + pad, w - 2 * pad - local ly = ry + math.floor(10 * m.s) - - local togGap = math.floor(5 * m.s) + 1 - local info = mod.github and mod.github ~= "" and imp:_modUpdateInfo(mod.id) - - -- These answer separate games, not a single shared install flag. The - -- importer receives the game id so an experimental confirmation also - -- applies only to the checkbox the player pressed. - local flipped = false - local gamesY = ry + math.floor(8 * m.s) + textH + math.floor(8 * m.s) - Kit.text("micro", gamesLabel, px, - gamesY + (togH - Kit.textHeight("micro")) / 2, PAL.muted) - local tx = px + Kit.textWidth("micro", gamesLabel) + math.floor(10 * m.s) - for _, game in ipairs(GameVersion.ORDER) do - local togKey = "mod-toggle-" .. mod.id .. "-" .. game - if modGameCheckbox(tx, gamesY, togH, - mod.enabledByVersion and mod.enabledByVersion[game] == true, - game, togKey, not safeMode) then - local version = game - queueAction(imp, togKey, function() imp:_toggleMod(mod.id, nil, version) end) - flipped = true + local focused = Kit.focusable(rowKey, x, ry, w, rowH) + local hot = focused or Kit.hover(x, ry, w, rowH) + if isFullyDisabled then + Kit.card(x, ry, w, rowH, hot and "mutedHot" or "muted") + else + Kit.card(x, ry, w, rowH, hot) end - tx = tx + togH + togGap - end - -- The checkboxes sit inside the row's rect, so their press also passes the - -- row hit test; `flipped` gates the row action to everywhere else. - if not flipped - and (Kit.press(x, ry, w, rowH) or Kit._activateId == rowKey) then - local id = mod.id - queueAction(imp, rowKey, function() imp._modActions = id end) - end - local textW = inner + local pad = math.floor(12 * m.s) + local px, inner = x + pad, w - 2 * pad + local ly = ry + math.floor(10 * m.s) - local badgeW = Kit.textWidth("micro", mod.badge) + math.floor(12 * m.s) - -- the games the mod is for, beside its category: the same chip the - -- in-game manager shows (src/mods/ModTargets.lua) - local gamesW = mod.targets - and Kit.textWidth("micro", mod.targets) + math.floor(12 * m.s) or 0 - local nameShown = Kit.ellipsize("button", mod.name, - textW - badgeW - gamesW - math.floor(12 * m.s)) - local headingCol = isFullyDisabled and PAL.muted or PAL.heading - Kit.text("button", nameShown, px, ly, headingCol) - local tagX = px + Kit.textWidth("button", nameShown) + math.floor(8 * m.s) - Kit.tag(tagX, ly, badgeW, Kit.textHeight("button"), mod.badge, - mod.experimental and PAL.yellow or PAL.muted) - if mod.targets then - Kit.tag(tagX + badgeW + math.floor(4 * m.s), ly, gamesW, - Kit.textHeight("button"), mod.targets, - mod.targetsHere == false and PAL.steel or PAL.blue) - end - ly = ly + Kit.textHeight("button") + math.floor(4 * m.s) + local togGap = math.floor(5 * m.s) + 1 + local info = mod.github and mod.github ~= "" and imp:_modUpdateInfo(mod.id) - -- version + status + update state - local statusText, statusCol = modStatusColor(mod.status) - local line = "v" .. tostring(mod.version or "?") .. " " .. statusText - Kit.text("small", line, px, ly, statusCol) - local lx = px + Kit.textWidth("small", line) + math.floor(12 * m.s) - if imp:_modInfoPending(mod.id) then - -- An inline spinner, because this row's release check is genuinely in - -- flight -- the list stays usable while it resolves. - Loader.dot(lx, ly, Kit.textHeight("small")) - Kit.text("small", Strings("Checking..."), - lx + Kit.textHeight("small") + math.floor(6 * m.s), ly, PAL.muted) - elseif info and info.status == "available" then - Kit.text("small", Strings("v%s available", tostring(info.latest)), - lx, ly, PAL.yellow) - elseif info and info.status == "current" then - Kit.text("small", Strings("up to date"), lx, ly, PAL.muted) - elseif info and info.status == "error" then - Kit.text("small", Strings("check failed"), lx, ly, PAL.red) - end - ly = ly + Kit.textHeight("small") + math.floor(2 * m.s) - - -- one line of description, or the download stats when we have them - -- (download count in green so popularity reads at a glance) - if info and info.downloads then - local d = info.dates - local dl = ModUpdate.downloadsLine(info.downloads.total) - local dates = ModUpdate.datesLine(d and d.first, d and d.latest) - local segs = {} - if dl then segs[#segs + 1] = { dl, PAL.green } end - if dates then - segs[#segs + 1] = { (dl and " - " or "") .. dates, PAL.detail } + -- These answer separate games, not a single shared install flag. The + -- importer receives the game id so an experimental confirmation also + -- applies only to the checkbox the player pressed. + local flipped = false + local gamesY = ry + math.floor(8 * m.s) + textH + math.floor(8 * m.s) + Kit.text("micro", gamesLabel, px, + gamesY + (togH - Kit.textHeight("micro")) / 2, PAL.muted) + local tx = px + Kit.textWidth("micro", gamesLabel) + math.floor(10 * m.s) + for _, game in ipairs(GameVersion.ORDER) do + local togKey = "mod-toggle-" .. mod.id .. "-" .. game + if modGameCheckbox(tx, gamesY, togH, + mod.enabledByVersion and mod.enabledByVersion[game] == true, + game, togKey, not safeMode) then + local version = game + queueAction(imp, togKey, function() imp:_toggleMod(mod.id, nil, version) end) + flipped = true + end + tx = tx + togH + togGap + end + -- The checkboxes sit inside the row's rect, so their press also passes the + -- row hit test; `flipped` gates the row action to everywhere else. + if not flipped + and (Kit.press(x, ry, w, rowH) or Kit._activateId == rowKey) then + local id = mod.id + queueAction(imp, rowKey, function() imp._modActions = id end) + end + local textW = inner + + local badgeW = Kit.textWidth("micro", mod.badge) + math.floor(12 * m.s) + -- the games the mod is for, beside its category: the same chip the + -- in-game manager shows (src/mods/ModTargets.lua) + local gamesW = mod.targets + and Kit.textWidth("micro", mod.targets) + math.floor(12 * m.s) or 0 + local nameShown = Kit.ellipsize("button", mod.name, + textW - badgeW - gamesW - math.floor(12 * m.s)) + local headingCol = isFullyDisabled and PAL.muted or PAL.heading + Kit.text("button", nameShown, px, ly, headingCol) + local tagX = px + Kit.textWidth("button", nameShown) + math.floor(8 * m.s) + Kit.tag(tagX, ly, badgeW, Kit.textHeight("button"), mod.badge, + mod.experimental and PAL.yellow or PAL.muted) + if mod.targets then + Kit.tag(tagX + badgeW + math.floor(4 * m.s), ly, gamesW, + Kit.textHeight("button"), mod.targets, + mod.targetsHere == false and PAL.steel or PAL.blue) + end + ly = ly + Kit.textHeight("button") + math.floor(4 * m.s) + + -- version + status + update state + local statusText, statusCol = modStatusColor(mod.status) + local line = "v" .. tostring(mod.version or "?") .. " " .. statusText + Kit.text("small", line, px, ly, statusCol) + local lx = px + Kit.textWidth("small", line) + math.floor(12 * m.s) + if imp:_modInfoPending(mod.id) then + -- An inline spinner, because this row's release check is genuinely in + -- flight -- the list stays usable while it resolves. + Loader.dot(lx, ly, Kit.textHeight("small")) + Kit.text("small", Strings("Checking..."), + lx + Kit.textHeight("small") + math.floor(6 * m.s), ly, PAL.muted) + elseif info and info.status == "available" then + Kit.text("small", Strings("v%s available", tostring(info.latest)), + lx, ly, PAL.yellow) + elseif info and info.status == "current" then + Kit.text("small", Strings("up to date"), lx, ly, PAL.muted) + elseif info and info.status == "error" then + Kit.text("small", Strings("check failed"), lx, ly, PAL.red) + end + ly = ly + Kit.textHeight("small") + math.floor(2 * m.s) + + -- one line of description, or the download stats when we have them + -- (download count in green so popularity reads at a glance) + if info and info.downloads then + local d = info.dates + local dl = ModUpdate.downloadsLine(info.downloads.total) + local dates = ModUpdate.datesLine(d and d.first, d and d.latest) + local segs = {} + if dl then segs[#segs + 1] = { dl, PAL.green } end + if dates then + segs[#segs + 1] = { (dl and " - " or "") .. dates, PAL.detail } + end + segLine("small", segs, px, ly, textW) + elseif (mod.description or "") ~= "" then + Kit.text("small", Kit.ellipsize("small", mod.description, textW), + px, ly, PAL.detail) end - segLine("small", segs, px, ly, textW) - elseif (mod.description or "") ~= "" then - Kit.text("small", Kit.ellipsize("small", mod.description, textW), - px, ly, PAL.detail) end end - Kit.popClip() - local pagerY = listTop + listH + gap - local newPage, newPagerH = Kit.pager(x, pagerY, w, cur, #mods, perPage, "mods") - if newPage ~= cur then imp.modScroll = 0 end - setPage(imp, "mods", newPage) - return pagerY + newPagerH - y + local contentH = #mods * rowH + (#mods - 1) * gap + return (listTop + contentH + gap) - y end -- ---------------------------------------------------------- find mods panel @@ -2889,6 +2913,8 @@ local function buildConfirmModal(imp, m) imp:_setAllMods(true, true) elseif c.kind == "importOversize" then imp:_importSave(c.version, c.source, true) + elseif c.kind == "largeImport" then + imp:_importRequiredSource(c.modId, c.importId, c.source, true) else imp:_toggleMod(c.id, true, c.version) end @@ -4851,8 +4877,7 @@ function LauncherView.draw(imp) Kit.blockClicks = modalUp(imp) local step = Kit.scrollStep(m.s) - local nested = modListWantsWheel(imp, Kit.wheelY or 0) - if not nested then + do local rect = imp._tabRegionRect if rect then setTabScroll(imp, (Kit.scrollWheel(tabScrollAt(imp), tabScrollMax(imp), @@ -4860,8 +4885,7 @@ function LauncherView.draw(imp) end end local scroll = math.max(0, math.min(imp._pageScroll or 0, scrollMax)) - if scrollMax > 0 and (Kit.wheelY or 0) ~= 0 and not nested - and not Kit.blockClicks then + if scrollMax > 0 and (Kit.wheelY or 0) ~= 0 and not Kit.blockClicks then local moved = math.max(0, math.min(scroll - Kit.wheelY * step, scrollMax)) if moved ~= scroll then scroll = moved @@ -4869,7 +4893,7 @@ function LauncherView.draw(imp) end end imp._pageScroll, imp._pageScrollMax = scroll, scrollMax - if (Kit.wheelY or 0) ~= 0 and not nested and not Kit.blockClicks + if (Kit.wheelY or 0) ~= 0 and not Kit.blockClicks and tabScrollMax(imp) > 0 then local was = tabScrollAt(imp) local to = Kit.scrollClamp(was - Kit.wheelY * step, tabScrollMax(imp)) diff --git a/src/import/RomExtractorGen2.lua b/src/import/RomExtractorGen2.lua index 4cc2eb73..737f0e71 100644 --- a/src/import/RomExtractorGen2.lua +++ b/src/import/RomExtractorGen2.lua @@ -4,6 +4,7 @@ -- (species order IS dex order, pics/tilesets are lz3-compressed rather than -- pkmncompress'd, maps are grouped instead of flat). See docs/gold-phase1.md. local bit = require("bit") +local GameVersion = require("src.core.GameVersion") local ImageWriter = require("src.import.ImageWriter") local LuaWriter = require("src.import.LuaWriter") local Rom = require("src.import.Rom") @@ -168,6 +169,10 @@ function RomExtractorGen2.new(romData, manifest, progress) symbols = manifest.symbols, progress = progress, stage = 0, + -- _GOLD / _SILVER: the labels are shared, the data behind a handful of + -- them is not (gfx/misc.asm:9-20 vs :46-57). + edition = GameVersion.forSha1(manifest.romSha1) == "silver" + and "silver" or "gold", }, RomExtractorGen2) end @@ -1641,22 +1646,40 @@ function RomExtractorGen2:extractTitle() return 3 end - -- pret gfx/title/title_bg_gold.pal / title_fg.pal (5 BG pals, 2 OBJ pals). - local BG_PALS = { + local silver = self.edition == "silver" + + -- pret gfx/title/title_bg_gold.pal / title_bg_silver.pal (5 BG pals); + -- GSTitleBGPals is the edition-selected include (engine/gfx/color.asm:1234). + local BG_PALS = silver and { + { { 31, 31, 31 }, { 0, 12, 15 }, { 4, 8, 21 }, { 0, 0, 0 } }, + { { 31, 21, 0 }, { 15, 17, 15 }, { 4, 8, 21 }, { 0, 0, 17 } }, + { { 31, 31, 31 }, { 31, 0, 0 }, { 4, 8, 21 }, { 0, 0, 0 } }, + { { 31, 31, 31 }, { 24, 23, 25 }, { 4, 8, 21 }, { 8, 8, 9 } }, + { { 31, 31, 31 }, { 5, 10, 11 }, { 0, 12, 15 }, { 0, 0, 0 } }, + } or { { { 31, 31, 31 }, { 18, 23, 31 }, { 15, 20, 31 }, { 0, 0, 0 } }, { { 31, 21, 0 }, { 12, 14, 12 }, { 15, 20, 31 }, { 0, 0, 17 } }, { { 31, 31, 31 }, { 31, 0, 0 }, { 15, 20, 31 }, { 0, 0, 0 } }, { { 31, 31, 31 }, { 29, 25, 0 }, { 15, 20, 31 }, { 17, 10, 1 } }, { { 31, 31, 31 }, { 23, 26, 31 }, { 18, 23, 31 }, { 0, 0, 0 } }, } - -- title_fg.pal: pal 0 = Ho-Oh silhouette (shades 1-3 are the same brown); - -- pal 1 = gold trail sparks (OAM_PAL1 on GSTitleTrail). + -- title_fg.pal, shared (GSTitleOBPals, engine/gfx/color.asm:1241): pal 0 = + -- Ho-Oh silhouette; pal 1 = gold trail sparks (OAM_PAL1 on GSTitleTrail). local OBJ_HOOH = { { 31, 31, 31 }, { 7, 6, 3 }, { 7, 6, 3 }, { 7, 6, 3 }, } local OBJ_TRAIL = { { 31, 31, 31 }, { 31, 31, 0 }, { 26, 22, 0 }, { 0, 0, 0 }, } + -- engine/movie/title.asm:134-141 + CopyPals (home/palettes.asm:190): + -- DmgToCgbObjPal0 %11100000 makes Silver's OBJ pal 0 {c0, c0, c2, c3}. + if silver then + OBJ_HOOH = { + OBJ_HOOH[1], OBJ_HOOH[1], OBJ_HOOH[3], OBJ_HOOH[4], + } + -- .OAMData_GSTitleTrail is attribute 0, not OAM_PAL1 (oam.asm:834-837). + OBJ_TRAIL = OBJ_HOOH + end local function palColor(pal, shade) local c = pal[shade + 1] or pal[4] @@ -1671,8 +1694,9 @@ function RomExtractorGen2:extractTitle() -- solid BLACK silhouette on a monochrome screen rather than the shaded pose -- a straight decode gives. rOBP1 (%11111000) carries the gold trail. local DMG_BGP = { 0, 2, 1, 3 } - local DMG_OBP0 = { 3, 3, 3, 3 } - local DMG_OBP1 = { 0, 2, 3, 3 } + -- engine/movie/title.asm:105-115: Silver writes %11110000 to both OBPs. + local DMG_OBP0 = silver and { 0, 0, 3, 3 } or { 3, 3, 3, 3 } + local DMG_OBP1 = silver and { 0, 0, 3, 3 } or { 0, 2, 3, 3 } -- ImageWriter's four hardware shades, by shade number. local DMG_SHADE = { 1, 2 / 3, 1 / 3, 0 } @@ -1776,6 +1800,28 @@ function RomExtractorGen2:extractTitle() -- Ho-Oh frames from OAMData_GSIntroHoOh1..5 (data/sprite_anims/oam.asm). local hoohTiles = tilesFrom2bpp(self:decompressLz3Symbol("TitleScreenGFX4"), true) + -- .OAMData_GSIntroLugia1 / 2 (data/sprite_anims/oam.asm:736-773); the + -- spriteanimoam vtile offset is added per frame (core.asm:224-227). + local LUGIA_1 = { + { -5, -2, 0, 0, 0x00 }, { -5, 0, 0, 0, 0x02 }, + { -4, -2, 0, 0, 0x04 }, { -4, 0, 0, 0, 0x06 }, + { -3, -1, 0, 0, 0x08 }, { -2, -1, 0, 0, 0x0a }, + { -1, -2, 0, 0, 0x0c }, { -1, 0, 0, 0, 0x0e }, + { 0, -2, 0, 0, 0x10 }, { 0, 0, 0, 0, 0x12 }, + { 1, -2, 0, 0, 0x14 }, { 1, 0, 0, 0, 0x16 }, + { 2, -2, 0, 0, 0x18 }, { 2, 0, 0, 0, 0x1a }, + { 3, -1, 0, 0, 0x1c }, { 4, -1, 0, 0, 0x1e }, + } + local LUGIA_2 = { + { -5, -2, 0, 0, 0x00 }, { -5, 0, 0, 0, 0x02 }, + { -4, -2, 0, 0, 0x04 }, { -4, 0, 0, 0, 0x06 }, + { -3, -1, 0, 0, 0x08 }, { -2, -1, 0, 0, 0x0a }, + { -1, -2, 0, 0, 0x0c }, { -1, 0, 0, 0, 0x0e }, + { 0, -2, 0, 0, 0x10 }, { 0, 0, 0, 0, 0x12 }, + { 1, -2, 0, 0, 0x14 }, { 1, 0, 0, 0, 0x16 }, + { 2, -2, 0, 0, 0x18 }, { 2, 0, 0, 0, 0x1a }, + { 3, -2, 0, 0, 0x1c }, { 4, -2, 0, 0, 0x1e }, + } local HOOH_FRAMES = { { -- 1 { -4, -1, 0, 0, 0x00 }, { -3, -2, 0, 0, 0x02 }, { -3, 0, 0, 0, 0x04 }, @@ -1823,22 +1869,36 @@ function RomExtractorGen2:extractTitle() { 3, -2, 0, 0, 0x22 }, { 3, 0, 0, 0, 0x24 }, }, } - -- Frameset_GSIntroHoOhLugia (Gold): 1,2,3,4,3,5 with these durations. - local HOOH_SEQUENCE = { + -- Silver's five oamsets, as {layout, vtile base} (oam.asm:103-107). + local LUGIA_FRAMES = { + { LUGIA_1, 0x00 }, { LUGIA_1, 0x20 }, { LUGIA_2, 0x40 }, + { LUGIA_2, 0x60 }, { LUGIA_1, 0x00 }, + } + -- Frameset_GSIntroHoOhLugia (data/sprite_anims/framesets.asm:376-396): + -- Gold 1,2,3,4,3,5; Silver 2,1,2,3,3,4,4,3,2 on a faster clock. + local HOOH_SEQUENCE = silver and { + { 2, 3 }, { 1, 7 }, { 2, 7 }, { 3, 7 }, { 3, 7 }, + { 4, 7 }, { 4, 7 }, { 3, 7 }, { 2, 3 }, + } or { { 1, 10 }, { 2, 9 }, { 3, 10 }, { 4, 10 }, { 3, 9 }, { 5, 10 }, } local hoohPaths, hoohGrayPaths = {}, {} - local originX, originY = 32, 24 - for fi, oam in ipairs(HOOH_FRAMES) do + -- Lugia1/2 span x tiles -5..4, four tiles wider than Ho-Oh's -4..3. + local originX, originY = silver and 40 or 32, 24 + local poseW = silver and 80 or 64 + local frames = silver and LUGIA_FRAMES or HOOH_FRAMES + for fi, entry in ipairs(frames) do + local oam = silver and entry[1] or entry + local base = silver and entry[2] or 0 -- The pose starts EMPTY, not white: an OBJ's colour 0 is transparent -- wherever it falls, so a gap enclosed by the bird shows the sky through -- exactly like one outside it, and there is no matte to flood-fill. - local pose = ImageWriter.blank(64, 64, 0, 0, 0, 0) + local pose = ImageWriter.blank(poseW, 64, 0, 0, 0, 0) for _, spr in ipairs(oam) do local px = originX + spr[1] * 8 + spr[3] local py = originY + spr[2] * 8 + spr[4] - blitSprite(pose, hoohTiles[spr[5] + 1], px, py) - blitSprite(pose, hoohTiles[spr[5] + 2], px, py + 8) + blitSprite(pose, hoohTiles[base + spr[5] + 1], px, py) + blitSprite(pose, hoohTiles[base + spr[5] + 2], px, py + 8) end local tinted = colorize(pose, function() return OBJ_HOOH end) local rel = ("title/hooh_%d.png"):format(fi) @@ -1855,14 +1915,20 @@ function RomExtractorGen2:extractTitle() end self:tick("Title screen", 3, 5) - -- Trail: TitleScreenGFX3 is raw 2bpp (8 tiles); Gold OAM uses one 8x16 - -- on OAM_PAL1 (gold), not the Ho-Oh silhouette pal. + -- Trail: TitleScreenGFX3 is raw 2bpp; Gold's OAM is one 8x16, Silver's two + -- side by side, and only 4 of Silver's 8 copied tiles exist (title.asm:43). local trailSym = self:symbol("TitleScreenGFX3") - local trailRaw = self.rom:bytes(trailSym.bank, trailSym.address, 8 * 16) + local trailTileCount = silver and 4 or 8 + local trailRaw = + self.rom:bytes(trailSym.bank, trailSym.address, trailTileCount * 16) local trailTiles = tilesFrom2bpp(trailRaw, true) - local trail = ImageWriter.blank(8, 16, 0, 0, 0, 0) + local trail = ImageWriter.blank(silver and 16 or 8, 16, 0, 0, 0, 0) blitSprite(trail, trailTiles[1], 0, 0) blitSprite(trail, trailTiles[2], 0, 8) + if silver then + blitSprite(trail, trailTiles[3], 8, 0) + blitSprite(trail, trailTiles[4], 8, 8) + end local trailTint = colorize(trail, function() return OBJ_TRAIL end) self:save(trailTint, "title/trail.png") self:save(throughRegister(trail, DMG_OBP1), "title/trail_gray.png") @@ -1908,8 +1974,11 @@ function RomExtractorGen2:extractTitle() cloudsGray = "assets/generated/title/clouds_gray.png", hoohFramesGray = hoohGrayPaths, trailGray = "assets/generated/title/trail_gray.png", - -- Frameset_GSIntroHoOhLugia (Gold), frame index 1-based + duration frames. + -- Frameset_GSIntroHoOhLugia, frame index 1-based + duration frames. hoohSequence = HOOH_SEQUENCE, + -- AnimSeq_GSIntroHoOhLugia (engine/sprite_anims/functions.asm:820-838). + hoohBobAmplitude = silver and 8 or 2, + hoohBobStep = silver and -1 or 1, -- `depixel 12, 11` (engine/movie/title.asm). Two traps, and the port had -- fallen into both, which is what put Ho-Oh off-centre: -- * ldpixel's own comment calls its first tile argument the X one and is @@ -1919,19 +1988,45 @@ function RomExtractorGen2:extractTitle() -- the cursor two rows up on the box screen. So this is x 88, y 96. -- * those are OAM coordinates, which are biased; a drawn object sits at -- (x - 8, y - 16) on screen. - -- The pose canvas holds its own origin at (32, 24), so the sheet's corner - -- is (88 - 8 - 32, 96 - 16 - 24) -- and the bird's 64px width then lands - -- centred on the screen, 48 to 112. - hoohX = 48, - hoohY = 56, + -- The pose canvas holds its own origin, so the sheet's corner is + -- (88 - 8 - originX, 96 - 16 - originY) -- and the pose's width then lands + -- centred on the screen (Ho-Oh 48..112, Lugia 40..120). + hoohX = 80 - originX, + hoohY = 80 - originY, trail = "assets/generated/title/trail.png", copyright = "assets/generated/title/copyright.png", copyrightSplash = "assets/generated/title/copyright_splash.png", - -- ScrollTitleScreenClouds: Gold decrements the cloud-band SCX every - -- 8 vblanks, so the strip slides 1px right. Silver does the same - -- decrement every frame. - cloudScrollEvery = 8, + -- ScrollTitleScreenClouds (engine/menus/intro_menu.asm:917-928): Gold + -- decrements the cloud-band SCX every 8 vblanks, so the strip slides 1px + -- right. Silver does the same decrement every frame. + cloudScrollEvery = silver and 1 or 8, cloudY = 88, + -- BG pal 0 colour 2: the sky the widescreen bands have to match. + sky = { + BG_PALS[1][3][1] / 31, BG_PALS[1][3][2] / 31, BG_PALS[1][3][3] / 31, + }, + -- The fill under the cloud/wave band: Gold's cloud field is BG pal 0 + -- colour 0 (white), Silver's sea floor is colour 3 (black). + below = silver and { + BG_PALS[1][4][1] / 31, BG_PALS[1][4][2] / 31, BG_PALS[1][4][3] / 31, + } or { + BG_PALS[1][1][1] / 31, BG_PALS[1][1][2] / 31, BG_PALS[1][1][3] / 31, + }, + -- UpdateTitleTrailSprite (engine/menus/intro_menu.asm:1069-1124). Silver's + -- `depixel 15, 11, 4, 0` is OAM (88, 124), less the bias and the (-16, -8) + -- corner .OAMData_GSTitleTrail draws from. + trailMode = silver and "silver" or "gold", + trailSpawns = silver and { { 72, 100 } } or { + { 80, 88 }, { 104, 88 }, { 104, 88 }, { 120, 88 }, + { 120, 88 }, { 88, 88 }, + }, + trailSpawnEvery = 4, + trailStepX = 4, + trailStepY = silver and 0 or 1, + -- AnimSeq_GSTitleTrail (functions.asm:784-813) with wIntroSceneTimer 0. + trailBobAmplitude = silver and 3 or 2, + trailPhaseStep = silver and 7 or 3, + trailPhase = silver and 0 or nil, } self:write("title", data) return data @@ -5136,6 +5231,82 @@ function RomExtractorGen2:extractMenuGfx() end if eggHatch.egg or eggHatch.shell then out.eggHatch = eggHatch end + -- Goldenrod Game Corner: Slot Machine graphics assets + if self.symbols["Slots1LZ"] then + local raw1 = self:decompressLz3Symbol("Slots1LZ") + self:write2bpp(raw1, 16, #raw1 / 4, "slots/gold_slots_1.png") + end + if self.symbols["Slots2LZ"] then + local raw2 = self:decompressLz3Symbol("Slots2LZ") + -- In Pokemon Gold ROM, Seven symbol (first 4 tiles = 64 bytes) has inverted bit polarity + for i = 1, math.min(64, #raw2) do + raw2[i] = bit.band(bit.bnot(raw2[i]), 0xFF) + end + self:write2bpp(raw2, 16, #raw2 / 4, "slots/gold_slots_2.png") + end + if self.symbols["Slots3LZ"] then + local raw3 = self:decompressLz3Symbol("Slots3LZ") + self:write2bpp(raw3, 24, #raw3 / 6, "slots/gold_slots_3.png", true) + -- Slots3LZ is a 24px-wide (3 tiles), 240px-tall (30 tiles) sprite sheet containing: + -- Y=0: Golem 1 (Standing, 24x32) + -- Y=32: Golem 2 (Ball, 24x32) + -- Y=64: Chansey 1 (Standing / Step 1, 24x32) + -- Y=96: Chansey 2 (Step 2, 24x32) + -- Y=128: Chansey 3 (Step 3, 24x32) + -- Y=160: Chansey 4 (Arm raised / Step 4, 24x32) + -- Y=192: Chansey 5 (Egg Drop pose, 24x32) + -- Y=224: Egg (8x16 at X=0) + self:write2bpp(raw3, 24, #raw3 / 6, "slots/gold_slots_actors.png", true) + end + if self.symbols["SlotsTilemap"] then + local symbol = self:symbol("SlotsTilemap") + local tm = self.rom:bytes(symbol.bank, symbol.address, 20 * 12) + self:save(tm, "slots/gold_slots.tilemap") + end + + -- Goldenrod Game Corner: Card Flip graphics assets + if self.symbols["CardFlipLZ01"] then + local raw1 = self:decompressLz3Symbol("CardFlipLZ01") + self:write2bpp(raw1, 128, #raw1 / 32, "card_flip/card_flip_1.png") + end + if self.symbols["CardFlipLZ02"] then + local raw2 = self:decompressLz3Symbol("CardFlipLZ02") + self:write2bpp(raw2, 24, #raw2 / 6, "card_flip/card_flip_2.png") + end + if self.symbols["CardFlipLZ03"] then + local raw3 = self:decompressLz3Symbol("CardFlipLZ03") + self:write2bpp(raw3, 8, #raw3 / 2, "card_flip/card_flip_3.png") + end + if self.symbols["CardFlipOnButtonGFX"] then + local symbol = self:symbol("CardFlipOnButtonGFX") + self:write2bpp(self.rom:bytes(symbol.bank, symbol.address, 16), 8, 8, "card_flip/on.png") + end + if self.symbols["CardFlipOffButtonGFX"] then + local symbol = self:symbol("CardFlipOffButtonGFX") + self:write2bpp(self.rom:bytes(symbol.bank, symbol.address, 16), 8, 8, "card_flip/off.png") + end + if self.symbols["CardFlipTilemap"] then + local symbol = self:symbol("CardFlipTilemap") + local tm = self.rom:bytes(symbol.bank, symbol.address, 11 * 12) + self:save(tm, "card_flip/card_flip.tilemap") + end + + out.slots = { + sheet1 = "assets/generated/slots/gold_slots_1.png", + sheet2 = "assets/generated/slots/gold_slots_2.png", + sheet3 = "assets/generated/slots/gold_slots_3.png", + tilemap = "assets/generated/slots/gold_slots.tilemap", + } + + out.cardFlip = { + sheet1 = "assets/generated/card_flip/card_flip_1.png", + sheet2 = "assets/generated/card_flip/card_flip_2.png", + sheet3 = "assets/generated/card_flip/card_flip_3.png", + on = "assets/generated/card_flip/on.png", + off = "assets/generated/card_flip/off.png", + tilemap = "assets/generated/card_flip/card_flip.tilemap", + } + self:write("menu_gfx", out) self:tick("Menu graphics", 1, 1) return out diff --git a/src/import/RomImporter.lua b/src/import/RomImporter.lua index a06aa5f5..9ce176ff 100644 --- a/src/import/RomImporter.lua +++ b/src/import/RomImporter.lua @@ -142,6 +142,8 @@ local VERSION_REQUIRED_FILES_OVERRIDE = { "assets/generated/audio/programs.bin", }, } +-- Same Gen 2 extract, so a Silver cache is complete when the same files exist. +VERSION_REQUIRED_FILES_OVERRIDE.silver = VERSION_REQUIRED_FILES_OVERRIDE.gold -- "Split-screen ROM selector" first-run palette (matches the FirstRun mockup): -- a dark neon arcade panel, one column per game. @@ -335,7 +337,7 @@ function RomImporter.syncAndroidShortcuts(activeVersion) return false end - local allVersions = { "red", "blue", "yellow", "gold" } + local allVersions = GameVersion.ORDER local ready = {} local seen = {} @@ -1361,11 +1363,10 @@ function RomImporter.new(onComplete, opts) saveNotice = {}, -- MODS panel state (pass 3): mods is the cached LauncherMods.list() array -- (refreshed lazily on first draw and after any toggle/install/delete); - -- modScroll is the current paged list's inner scroll offset (px, clamped - -- in draw); modNotice is the last install/delete result { ok, text }. + -- modNotice is the last install/delete result { ok, text }. -- requiredImportNotice stays inside the imported-files modal so validation -- failures are visible beside the file picker that caused them. - mods = nil, modScroll = 0, modNotice = nil, issueNotice = nil, + mods = nil, modNotice = nil, issueNotice = nil, requiredImportNotice = nil, -- Which game the MODS panel is answering for (a GameVersion id, nil = -- every game). Rows resolve their enable-state and their "runs here" @@ -1422,7 +1423,8 @@ function RomImporter.new(onComplete, opts) self.returning[version] = (not ready) and marker ~= nil and marker ~= markerFor(version) self.romName[version] = "pokemon_" .. info.id - .. ((info.id == "yellow" or info.id == "gold") and ".gbc" or ".gb") + .. ((info.id == "yellow" or GameVersion.generation(version) == 2) + and ".gbc" or ".gb") end RomImporter.syncAndroidShortcuts() self:_applyLastVersionTab() @@ -1687,7 +1689,7 @@ function RomImporter:startData(data, displayName) end if not isAcceptedRomSize(#data) then self:setError(("Expected a 1 MiB Game Boy ROM (Red/Blue/Yellow) or a " - .. "2 MiB Game Boy Color ROM (Gold); this file is %.2f MiB.") + .. "2 MiB Game Boy Color ROM (Gold/Silver); this file is %.2f MiB.") :format(#data / 1024 / 1024)) return end @@ -1695,7 +1697,8 @@ function RomImporter:startData(data, displayName) local version = GameVersion.forSha1(actualHash) if not version then self:setError(("Unsupported ROM (SHA-1 %s). This needs a clean US Pokemon " - .. "Red, Blue, Yellow, or Gold dump; patched, trimmed or \"fixed\" dumps " + .. "Red, Blue, Yellow, Gold, or Silver dump; patched, trimmed or " + .. "\"fixed\" dumps " .. "(tagged [b] or [BF]) never verify."):format(actualHash)) return end @@ -1772,7 +1775,7 @@ function RomImporter:_startExtractCoroutine(version, info, prefix, displayName) local CacheFs = require("src.import.CacheFs") CacheFs.prefix = prefix local manifest = require("src.import.RomManifest").decode(version) - local RomExtractor = version == "gold" + local RomExtractor = GameVersion.generation(version) == 2 and require("src.import.RomExtractorGen2") or require("src.import.RomExtractor") local extractor = RomExtractor.new(self.romData, manifest, @@ -2057,7 +2060,7 @@ function RomImporter:_importRequiredData(modId, importId, data) return nil end -function RomImporter:_importRequiredSource(modId, importId, source) +function RomImporter:_importRequiredSource(modId, importId, source, confirmed) local manifest = requiredManifest(self, modId) local spec = manifest and requiredSpec(manifest, importId) if not spec then @@ -2065,14 +2068,31 @@ function RomImporter:_importRequiredSource(modId, importId, source) self.modNotice = nil return nil end + local RequiredImports = require("src.mods.RequiredImports") local info = love.filesystem.getInfo(source, "file") local size = info and info.size or externalFileSize(source) - local sizeErr = require("src.mods.RequiredImports").sizeError(spec, size, false) + local sizeErr = RequiredImports.sizeError(spec, size, false) if sizeErr then requiredImportNotice(self, modId, importId, sizeErr) self.modNotice = nil return nil end + if not confirmed and type(size) == "number" + and size > RequiredImports.LARGE_WARN_BYTES then + self._modConfirm = { + kind = "largeImport", + modId = modId, importId = importId, source = source, + title = Strings("Large import"), + lines = { + Strings("This is a large import (%s).", + RequiredImports.sizeLabel(size)), + Strings("Please ensure you have enough space on your"), + Strings("device before doing this."), + }, + yesLabel = Strings("I understand"), + } + return nil + end local data = love.filesystem.read(source) if not data then data = readExternalPath(source) end if not data then @@ -2115,8 +2135,13 @@ function RomImporter:chooseRequiredImport(modId, importId) if name:sub(1, 1) ~= "." then local path = inbox .. "/" .. name local info = love.filesystem.getInfo(path, "file") - local sizeErr = info and require("src.mods.RequiredImports") - .sizeError(spec, info.size, false) + local RequiredImports = require("src.mods.RequiredImports") + local sizeErr = info + and RequiredImports.sizeError(spec, info.size, false) + if info and not sizeErr + and info.size > RequiredImports.LARGE_WARN_BYTES then + return self:_importRequiredSource(modId, importId, path) + end local data = not sizeErr and love.filesystem.read(path) or nil if data and self:_importRequiredData(modId, importId, data) then return end if sizeErr then lastError = sizeErr @@ -2757,7 +2782,8 @@ function RomImporter:resumeAfterOverlay() end function RomImporter:_cycleTab(delta) - local order = { "red", "blue", "yellow", "gold", "mods", "find", "skins", "bug" } + local order = { "red", "blue", "yellow", "gold", "silver", + "mods", "find", "skins", "bug" } local idx = 1 for i, id in ipairs(order) do if id == self.tab then idx = i; break end @@ -3094,8 +3120,10 @@ end -- multi-monitor coords). Same contract as PadCursor.yieldToPointer for -- the overlay hosts. Touch move/press/release must still reach -- FlexLove.touch* or scroll containers never drag on phones. -function RomImporter:mousepressed() +function RomImporter:mousepressed(x, y, button) self._padCursorActive = false + if button ~= 1 or not self._flex then return end + require("src.import.LauncherView").mousepressed(self, x, y) end function RomImporter:touchpressed(id, x, y, dx, dy, pressure) @@ -3123,7 +3151,6 @@ function RomImporter:_switchTab(id) self.tab = id self._findSearchFocus = false self._skinUrlFocus = false - self._modScrollMax, self._modListRect = 0, nil self:_disarmTextInput() -- the skins list is cheap and can change behind the launcher's back -- (an export, a hand-dropped folder), so re-read it on every visit diff --git a/src/mods/Manifest.lua b/src/mods/Manifest.lua index 92544589..865b9f03 100644 --- a/src/mods/Manifest.lua +++ b/src/mods/Manifest.lua @@ -195,8 +195,8 @@ local function parseImports(value, field, required) if value == nil then return end assert(type(value) == "number" and value > 0 and value % 1 == 0, field .. " " .. label .. " must be a positive integer") - assert(value <= 128 * 1024 * 1024, - field .. " " .. label .. " exceeds the 128 MiB hard limit") + assert(value <= 2 * 1024 * 1024 * 1024, + field .. " " .. label .. " exceeds the 2 GiB hard limit") end validateSize(size, "size") validateSize(maxSize, "max_size") diff --git a/src/mods/RequiredImports.lua b/src/mods/RequiredImports.lua index 167c263f..806e916e 100644 --- a/src/mods/RequiredImports.lua +++ b/src/mods/RequiredImports.lua @@ -23,11 +23,17 @@ local function isRequired(spec) end RequiredImports.specs = allSpecs -RequiredImports.MAX_BYTES = 128 * 1024 * 1024 +RequiredImports.MAX_BYTES = 2 * 1024 * 1024 * 1024 +-- Past this, the launcher interposes a free-space warning before importing. +RequiredImports.LARGE_WARN_BYTES = 128 * 1024 * 1024 local function sizeLabel(bytes) + if bytes >= 1024 * 1024 * 1024 then + return ("%.1f GiB"):format(bytes / (1024 * 1024 * 1024)) + end return ("%.1f MiB"):format(bytes / (1024 * 1024)) end +RequiredImports.sizeLabel = sizeLabel -- Check size before a caller reads an external or stored file into one large -- Lua string. N64 sources may carry a 512-byte copier header, while stored diff --git a/src/render/Renderer.lua b/src/render/Renderer.lua index 79b79501..bfb29b0c 100644 --- a/src/render/Renderer.lua +++ b/src/render/Renderer.lua @@ -15,6 +15,7 @@ local Runtime = require("src.mods.Runtime") local GameViewport = require("src.render.GameViewport") -- leaf module (no renderer dependency), so requiring it here cannot cycle local FaithfulRes = require("src.core.FaithfulRes") +local ScreenPosition = require("src.core.ScreenPosition") local Playfield = require("src.render.Playfield") local Renderer = {} @@ -94,6 +95,11 @@ local function displayMetrics() return ww, wh, pw, ph, dpiX, dpiY, vx, vy, cut, grow end +local function positionLift(ph, contentPx, dpiY, cut) + if cut then return 0 end + return ScreenPosition.lift(ph, contentPx, ScreenPosition.safeTop() * dpiY) +end + function Renderer:init() -- 160x144 real pixels, never DPI-scaled: see src/render/PixelCanvas.lua -- (#208). Every canvas below is sized in framebuffer pixels for the same @@ -269,7 +275,7 @@ end -- corners; flat mode returns exactly today's size (growth factor is 1 when -- tilt is inactive). function Renderer:worldViewSize() - local _, _, pw, ph, _, _, _, _, cut, grow = displayMetrics() + local _, _, pw, ph, _, dpiY, _, _, cut, grow = displayMetrics() -- FAITHFUL RATIO on mobile. The world pass deliberately expands to cover the -- WHOLE display, so letterbox voids become more map instead of black bars. -- That is why the lock appeared to do nothing in the overworld: it shrank @@ -293,6 +299,9 @@ function Renderer:worldViewSize() -- so unfloored FX/sprite math cannot phase-shimmer against the tile layer. if vw % 2 ~= 0 then vw = vw + 1 end if vh % 2 ~= 0 then vh = vh + 1 end + local _, uih = self:uiSize() + local lift = positionLift(ph, uih * self:fitScale(), dpiY, cut) + if lift > 0 then vh = vh + 2 * math.ceil(lift / sp) end if Tilt.active() then local g = Tilt.viewGrowth() vw, vh = math.ceil(vw * g), math.ceil(vh * g) @@ -774,8 +783,9 @@ function Renderer:frameRects() r.uiw, r.uih = uiw, uih r.vpw, r.vph = uiw * r.Sx, uih * r.Sy -- Snap the letterbox origin to a framebuffer pixel, then convert to units. + r.lift = positionLift(ph, uih * Sp, dpiY, cut) r.ox = (vx + math.floor((pw - uiw * Sp) / 2)) / dpiX - r.oy = (vy + math.floor((ph - uih * Sp) / 2)) / dpiY + r.oy = (vy + math.floor((ph - uih * Sp) / 2) - r.lift) / dpiY -- The UI has its own scale: it steps down as the survey zoom goes out (see -- uiScale), so it can be smaller than the world letterbox. Un-zoomed these -- are identical to Sp/ox/oy and every rect below is what it always was. @@ -795,7 +805,7 @@ function Renderer:frameRects() r.Up, r.Ux, r.Uy = Up, Up / dpiX, Up / dpiY r.uvpw, r.uvph = uiw * r.Ux, uih * r.Uy r.uox = (vx + math.floor((pw - uiw * Up) / 2)) / dpiX - r.uoy = (vy + math.floor((ph - uih * Up) / 2)) / dpiY + r.uoy = (vy + math.max(0, math.floor((ph - uih * Up) / 2) - r.lift)) / dpiY return r end @@ -1002,7 +1012,7 @@ function Renderer:endFrame(zones, worldZones) local wvw = self.worldCanvas:getWidth() local wvh = self.worldCanvas:getHeight() local wox = (vx + math.floor((pw - wvw * sp) / 2)) / dpiX - local woy = (vy + math.floor((ph - wvh * sp) / 2)) / dpiY + local woy = (vy + math.floor((ph - wvh * sp) / 2) - R.lift) / dpiY -- Tilt mode projects the ground world pass through the perspective mesh -- (SGB zones baked in beforehand -- see drawTiltedWorld -- so no zone -- scissoring here). drawTiltedWorld returns false when tilt is off or diff --git a/src/save_convert/SaveConvert.lua b/src/save_convert/SaveConvert.lua index 476b4794..e022faba 100644 --- a/src/save_convert/SaveConvert.lua +++ b/src/save_convert/SaveConvert.lua @@ -228,7 +228,10 @@ SaveConvert.mergeDefaults = mergeDefaults -- codec exists yet. Both directions answer with a plain message the -- launcher's save card renders as-is, instead of pushing a Gen 2 save -- table through Gen 1 offsets and surfacing a codec traceback. -local GEN2_SAV_UNSUPPORTED = { gold = "Pokemon Gold" } +local GEN2_SAV_UNSUPPORTED = { + gold = "Pokemon Gold", + silver = "Pokemon Silver", +} -- importSav(bytes, version, gameVersion) -> saveTable, err -- bytes: the raw 32768-byte SRAM string. Validates size and the main-data diff --git a/src/sync/SyncMods.lua b/src/sync/SyncMods.lua index 018c80c5..6366556a 100644 --- a/src/sync/SyncMods.lua +++ b/src/sync/SyncMods.lua @@ -5,7 +5,7 @@ SyncMods.REV = 1 local function versions() local ok, GameVersion = pcall(require, "src.core.GameVersion") if ok and GameVersion and GameVersion.ORDER then return GameVersion.ORDER end - return { "red", "blue", "yellow", "gold" } + return { "red", "blue", "yellow", "gold", "silver" } end local function defaultDeps() diff --git a/src/ui/OptionsMenu.lua b/src/ui/OptionsMenu.lua index 945aaca1..d79d2b5d 100644 --- a/src/ui/OptionsMenu.lua +++ b/src/ui/OptionsMenu.lua @@ -21,6 +21,7 @@ local GameVersion = require("src.core.GameVersion") local VideoMode = require("src.core.VideoMode") local Orientation = require("src.core.Orientation") local FaithfulRes = require("src.core.FaithfulRes") +local ScreenPosition = require("src.core.ScreenPosition") local FrameCap = require("src.core.FrameCap") local Performance = require("src.core.Performance") local Logger = require("src.core.Logger") @@ -443,6 +444,16 @@ local function buildRows(game) FaithfulRes.apply(o.faithfulRes) return true end }, + { id = "screenPos", label = Strings("SCREEN POS"), + value = function(g) + return Strings(ScreenPosition.label(g.save.options.screenPos)) + end, + step = function(g, dir) + local o = g.save.options + o.screenPos = ScreenPosition.cycle(o.screenPos, dir) + ScreenPosition.setMode(o.screenPos) + return true + end }, -- hard render cap (issue #88): bounds the present rate so a -- driver-forced vsync-off run cannot spin at thousands of FPS. Logic -- is fixed-step off dt, so this touches presentation only. diff --git a/src/ui/SurfingMinigame.lua b/src/ui/SurfingMinigame.lua index 70f51ee3..91e236e1 100644 --- a/src/ui/SurfingMinigame.lua +++ b/src/ui/SurfingMinigame.lua @@ -66,6 +66,7 @@ local TOTAL_SECTIONS = 24 -- 24 sections ($18) of 128px = 3072px course local BG_HEIGHT = 128 -- Rows the BG shows; HP window covers the rest (y=128..144) -- Routine numbers (wSurfingMinigameRoutineNumber) +local ROUTINE_TITLE = -1 local ROUTINE_START_GAME = 0 local ROUTINE_RUN_GAME = 1 local ROUTINE_WAIT_RESULTS = 2 @@ -216,7 +217,9 @@ local WAVE_STEPS = { [0x78] = { "beach", 116, 116, ADV }, [0x79] = { "beach", 116, 116, ADV }, [0x7a] = { "beach", 116, 116, ADV }, [0x7b] = { "beach", 116, 116, RESET }, } -local SEQ_STARTS = { 0x01, 0x0e, 0x1a, 0x29, 0x32, 0x40, 0x4d, 0x5c } +local SEQ_STARTS = { + 0x01, 0x0e, 0x1a, 0x29, 0x32, 0x40, 0x4d, 0x5c +} SurfingMinigame.BG_METATILES = BG_METATILES SurfingMinigame.WAVE_PATTERNS = WAVE_PATTERNS @@ -233,6 +236,40 @@ local ANGLE_BASES = { [7] = { 0x33, 0x69 }, -- Angle 06 (nose down steep / frontflip apex) } +-- OAM Definitions from surfing_pikachu_oam.asm +-- .WaterSpray (3 tiles, relative to PIKA_X = 68, y = self.pikaY) +local OAM_WATER_SPRAY = { + { dy = -4, dx = 11, tile = 0xa7, xflip = false }, + { dy = 4, dx = 3, tile = 0xb6, xflip = false }, + { dy = 4, dx = 11, tile = 0xb7, xflip = false }, +} + +-- .SmallSplash (6 tiles, relative to cx = 80, cy = self.pikaY + 4) +local OAM_SMALL_SPLASH = { + { dy = -4, dx = -16, tile = 0xa7, xflip = true }, + { dy = -4, dx = 8, tile = 0xa7, xflip = false }, + { dy = 4, dx = -16, tile = 0xb7, xflip = true }, + { dy = 4, dx = -8, tile = 0xb6, xflip = true }, + { dy = 4, dx = 0, tile = 0xb6, xflip = false }, + { dy = 4, dx = 8, tile = 0xb7, xflip = false }, +} + +-- .LargeSplash (12 tiles, relative to cx = 80, cy = self.pikaY + 4) +local OAM_LARGE_SPLASH = { + { dy = -12, dx = -16, tile = 0xa8, xflip = false }, + { dy = -12, dx = -8, tile = 0xa9, xflip = false }, + { dy = -12, dx = 0, tile = 0xa9, xflip = true }, + { dy = -12, dx = 8, tile = 0xa8, xflip = true }, + { dy = -4, dx = -16, tile = 0xb8, xflip = false }, + { dy = -4, dx = -8, tile = 0xb9, xflip = false }, + { dy = -4, dx = 0, tile = 0xb9, xflip = true }, + { dy = -4, dx = 8, tile = 0xb8, xflip = true }, + { dy = 4, dx = -16, tile = 0xc8, xflip = false }, + { dy = 4, dx = -8, tile = 0xc9, xflip = false }, + { dy = 4, dx = 0, tile = 0xc9, xflip = true }, + { dy = 4, dx = 8, tile = 0xc8, xflip = true }, +} + -- Beach outro tilemap (gfx/surfing_pikachu/beach_outro.tilemap, 20x10) local BEACH_OUTRO = { { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0f, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b }, @@ -258,9 +295,9 @@ local PIKACHUS_BEACH_PAL = { -- 5 Tempo tiers (117, 109, 101, 93, 85 from surfing_pikachu.asm) local TEMPO_TIERS = { 1.0, 117 / 109, 117 / 101, 117 / 93, 117 / 85 } -function SurfingMinigame.new(game, onDone) +function SurfingMinigame.new(game, onDone, skipTitle) local self = setmetatable({ game = game, onDone = onDone }, SurfingMinigame) - self.routine = ROUTINE_START_GAME + self.routine = skipTitle and ROUTINE_START_GAME or ROUTINE_TITLE self.pikaState = PIKA_STATE_RIDING self.t = 0 self.routineTimer = 0 @@ -269,9 +306,11 @@ function SurfingMinigame.new(game, onDone) self.hp = 6000 -- starts at 6000 (60.00 seconds) self.radness = 0 -- accumulated trick stunt points self.totalScore = 0 -- tallied total score - self.hiScore = game.save.surfingHighScore or 0 + self.hiScore = (game and game.save and game.save.surfingHighScore) or 0 self.newRecord = false self.currentPitch = 1.0 + self.isMinigame = true + self.isFixedSpeed = true -- Hardware RNG simulation registers (hRandomAdd, hRandomSub, rDIV) self.rDiv = 0 @@ -297,6 +336,7 @@ function SurfingMinigame.new(game, onDone) self.boardAngleDecreasing = false self.boardAngleTimer = 0 self.crashTimer = 0 + self.landingTimer = 0 -- 3-frame buffered D-Pad rotation & input accumulator self.joyCounter = 0 @@ -318,14 +358,28 @@ function SurfingMinigame.new(game, onDone) self.tallyStep = 0 self.tallyTimer = 0 - -- Load sheets safely + -- Load sheets safely across all GameVersion prefix paths local function sheet(path) if not (love and love.graphics and love.graphics.newImage) then return nil end - local ok, img = pcall(love.graphics.newImage, path) - return ok and img or nil + local GameVersion = require("src.core.GameVersion") + local prefix = (GameVersion and GameVersion.cachePrefix and GameVersion.cachePrefix(game and game.version)) or "yellow/" + local filename = path:match("([^/]+)$") or path + local paths = { + prefix .. path, + path, + "yellow/assets/generated/minigame/" .. filename, + "assets/generated/minigame/" .. filename, + } + for _, p in ipairs(paths) do + local ok, img = pcall(love.graphics.newImage, p) + if ok and img then return img end + end + return nil end self.bg = sheet("assets/generated/minigame/surf_1a.png") self.ob = sheet("assets/generated/minigame/surf_1b.png") + self.intro = sheet("assets/generated/minigame/surf_1c.png") + self.titleBg = sheet("assets/generated/minigame/title_bg.png") if self.bg then self.tq = {} @@ -343,32 +397,97 @@ function SurfingMinigame.new(game, onDone) end end - Music.play(game.data, "Music_SurfingPikachu") + if self.intro then + self.iq = {} + local iW, iH = self.intro:getDimensions() -- 96x96 tile sheet (12x12 tiles) + for n = 0, 143 do + self.iq[n] = love.graphics.newQuad((n % 12) * 8, math.floor(n / 12) * 8, 8, 8, iW, iH) + end + -- Pika Intro Poses (24x32 px each in top 32px: X=0, 24, 48) + self.introPikaQuad1 = love.graphics.newQuad(0, 0, 24, 32, iW, iH) + self.introPikaQuad2 = love.graphics.newQuad(24, 0, 24, 32, iW, iH) + self.introPikaQuad3 = love.graphics.newQuad(48, 0, 24, 32, iW, iH) + -- Title Banner Logo ("PIKACHU'S BEACH") (96x32 px at Y=32..64) + self.introLogoQuad = love.graphics.newQuad(0, 32, 96, 32, iW, iH) + -- Instruction Text ("Use Control Pad to Surf") (96x32 px at Y=64..96) + self.introTextQuad = love.graphics.newQuad(0, 64, 96, 32, iW, iH) + end + + -- GPU Shader & Canvas for authentic HBlank wave distortion + if love and love.graphics and love.graphics.newShader then + local shaderCode = [[ + extern float u_time; + extern float u_water_line; + + vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) { + vec2 uv = texture_coords; + if (uv.y >= u_water_line) { + float pixel_y = uv.y * 144.0; + float wave_px = sin((pixel_y * 0.39269908) + (u_time * 6.0)) * (1.5 / 160.0); + uv.x = fract(uv.x + wave_px); + } + return Texel(texture, uv) * color; + } + ]] + local ok, shader = pcall(love.graphics.newShader, shaderCode) + if ok then self.waveShader = shader end + end + + if love and love.graphics and love.graphics.newCanvas then + local ok, canvas = pcall(love.graphics.newCanvas, 160, 144) + if ok then self.bgCanvas = canvas end + end + + Music.play(game and game.data, "Music_SurfingPikachu") return self end --- Authentic Game Boy VBlank LFSR Random Number Generator +-- Transition cleanly from Title Screen to active run, flushing input bleed-through +function SurfingMinigame:startFromTitle() + self.routine = ROUTINE_START_GAME + self.inputAccum = 0 + self.joyCounter = 0 + self.rotCountLeft = 0 + self.rotCountRight = 0 + self:resetTempo() + if self.game and self.game.input and self.game.input.clearPressed then + self.game.input:clearPressed() + end + Sound.play(self.game and self.game.data, "Press_AB") +end + +-- Authentic Game Boy LFSR Random Number Generator function SurfingMinigame:getGBRandom() - self.rDiv = (self.rDiv + 7 + (self.inputAccum or 0)) % 256 - self.rAdd = (self.rAdd + self.rDiv) % 256 - self.rSub = (self.rSub - self.rDiv + 256) % 256 - return (self.rAdd + self.rSub) % 256 + self.rDiv = (self.rDiv + 13) % 256 + local tempAdd = self.rAdd + self.rDiv + self.rAdd = tempAdd % 256 + local tempSub = self.rSub - self.rDiv + self.rSub = (tempSub + 256) % 256 + return self.rAdd end function SurfingMinigame:chooseSequence() local distPx = math.floor(self.distanceFixed / 256) - if math.floor(distPx / 128) >= 0x16 then + local section = math.floor(distPx / 128) + if section == 0x16 then self.waveFn = 0x6a - else + elseif section < 0x16 then local r = self:getGBRandom() - if r ~= 0 then self.waveFn = SEQ_STARTS[((r - 1) % 8) + 1] end + if r ~= 0 then + self.waveFn = SEQ_STARTS[bit.band(r - 1, 0x07) + 1] + end end return WAVE_PATTERNS[0x00], FLAT_WATER_Y, FLAT_WATER_Y end function SurfingMinigame:pushColumn() local pat, hl, hr - if self.waveFn == 0 then + local distPx = math.floor(self.distanceFixed / 256) + if distPx >= (TOTAL_SECTIONS * 128) or self.waveFn >= 0x74 then + -- Lock finish line to solid beach sand without cycling back to ocean waves + pat, hl, hr = WAVE_PATTERNS["beach"], FLAT_WATER_Y, FLAT_WATER_Y + self.waveFn = 0x74 + elseif self.waveFn == 0 then pat, hl, hr = self:chooseSequence() else local step = WAVE_STEPS[self.waveFn] @@ -396,30 +515,40 @@ function SurfingMinigame:seaY(x) local distPx = math.floor(self.distanceFixed / 256) local tile = math.floor((distPx + x) / 8) local col = self.cols[math.floor(tile / 2)] - if not col then return FLAT_WATER_Y - 16 end - return (tile % 2 == 0 and col.hl or col.hr) - 16 + if not col then return FLAT_WATER_Y end + return tile % 2 == 0 and col.hl or col.hr end --- Get the tile ID of the wave under Pikachu (sample 9-10 tiles into viewport) +-- Get the tile ID of the wave under Pikachu (sample 9 tiles / 72 pixels into viewport) function SurfingMinigame:getWaveTileUnderPika() local distPx = math.floor(self.distanceFixed / 256) - local tile = math.floor((distPx + 80) / 8) - local col = self.cols[math.floor(tile / 2)] + local tile_col = math.floor((distPx + 72) / 8) + local waterY = math.floor(self:seaY(80)) + local tile_row = math.floor(waterY / 8) + + local c = math.floor(tile_col / 2) + local col = self.cols[c] if not col or not col.pat then return 0x01 end - local pat = col.pat - for i = 4, 8 do - local mt = pat[i] - if mt then - if mt == 0x02 or mt == 0x06 or mt == 0x0a or mt == 0x11 then - return 0x06 -- rising slope - elseif mt == 0x03 or mt == 0x07 or mt == 0x0b or mt == 0x0d then - return 0x07 -- falling slope - elseif mt == 0x08 or mt == 0x09 or mt == 0x0f or mt == 0x10 or mt == 0x14 or mt == 0x15 then - return 0x14 -- wave crest / face - end - end + + local i = math.floor(tile_row / 2) + 1 + if i < 1 or i > 8 then return 0x01 end + local mt = BG_METATILES[col.pat[i]] + if not mt then return 0x01 end + + local sub_x = (tile_col % 2 == 0) and 0 or 1 + local sub_y = (tile_row % 2 == 0) and 0 or 1 + + local sub_idx = 1 + sub_x + sub_y * 2 + local mt_tile = mt[sub_idx] or 0x01 + + if mt_tile == 0x02 or mt_tile == 0x04 or mt_tile == 0x06 or mt_tile == 0x0a or mt_tile == 0x11 then + return 0x06 -- rising slope + elseif mt_tile == 0x03 or mt_tile == 0x05 or mt_tile == 0x07 or mt_tile == 0x0d or mt_tile == 0x13 then + return 0x07 -- falling slope + elseif mt_tile == 0x08 or mt_tile == 0x09 or mt_tile == 0x0f or mt_tile == 0x10 or mt_tile == 0x12 or mt_tile == 0x14 or mt_tile == 0x15 then + return 0x14 -- wave crest / face end - return 0x01 -- flat open water + return 0x01 -- open water (0x0b, 0x00, 0x0e, etc.) end function SurfingMinigame:spawnTrickPopup(text) @@ -497,21 +626,51 @@ function SurfingMinigame:evaluateLanding() end end +function SurfingMinigame:updateTempo() + local tier = 1 + if self.speedFixed >= 416 then + tier = 5 + elseif self.speedFixed >= 320 then + tier = 4 + elseif self.speedFixed >= 224 then + tier = 3 + elseif self.speedFixed >= 128 then + tier = 2 + else + tier = 1 + end + local targetPitch = TEMPO_TIERS[tier] or 1.0 + if self.currentPitch ~= targetPitch then + self.currentPitch = targetPitch + if Music and Music.setPitch then + Music.setPitch(self.currentPitch) + end + end +end + +function SurfingMinigame:resetTempo() + self.currentPitch = 1.0 + if Music and Music.setPitch then + Music.setPitch(1.0) + end +end + function SurfingMinigame:updateRiding() -- Automatic speed up (+2/256 = +1/128 per frame up to max 512 = 2.0) if self.speedFixed < SPEED_MAX then self.speedFixed = math.min(SPEED_MAX, self.speedFixed + SPEED_ACCEL) end + self:updateTempo() -- Follow wave surface height local targetY = self:seaY(80) - self.pikaY = math.floor(targetY) - self.pikaSubY = 0 + self.pikaY = math.floor(targetY) - 16 + self.pikaSubY = 0 -- Water spray every 4 frames self.sprayTimer = self.sprayTimer + 1 if self.sprayTimer % 4 == 0 then - table.insert(self.waterSprays, { x = PIKA_X + 16, y = self.pikaY + 12, timer = 12 }) + table.insert(self.waterSprays, { x = PIKA_X, y = self.pikaY, timer = 4 }) end -- Board angle wobbling every 8 frames @@ -532,14 +691,14 @@ function SurfingMinigame:updateRiding() end end - -- Select frame based on slope + -- Select frame based on slope (lock flat open water to frame 4 without wobble) local tile = self:getWaveTileUnderPika() if tile == 0x06 or tile == 0x14 then self.frameSet = 6 + (self.boardAngleOffset - 1) elseif tile == 0x07 then self.frameSet = 2 + (self.boardAngleOffset - 1) else - self.frameSet = 4 + (self.boardAngleOffset - 1) + self.frameSet = 4 -- flat open water: steady horizontal ride end self.frameSet = math.max(1, math.min(14, self.frameSet)) @@ -560,43 +719,71 @@ function SurfingMinigame:updateRiding() end end -function SurfingMinigame:updateJumping() - -- Process accumulated input on the 3-frame buffer boundary - self.joyCounter = (self.joyCounter + 1) % 3 - if self.joyCounter == 0 then - local rightHeld = bit.band(self.inputAccum, 1) ~= 0 - local leftHeld = bit.band(self.inputAccum, 2) ~= 0 - self.inputAccum = 0 - - if rightHeld then - self.rotCountLeft = 0 - self.rotCountRight = self.rotCountRight + 1 - if self.rotCountRight >= 11 then - self.rotCountRight = 0 - self.radnessMeter = math.min(3, self.radnessMeter + 1) - self.trickFlags = bit.bor(self.trickFlags, 1) - Sound.play(self.game.data, "Tink") - end - -- Increment frame set (frontflip forward) - self.frameSet = (self.frameSet % 14) + 1 - elseif leftHeld then - self.rotCountRight = 0 - self.rotCountLeft = self.rotCountLeft + 1 - if self.rotCountLeft >= 13 then - self.rotCountLeft = 0 - self.radnessMeter = math.min(3, self.radnessMeter + 1) - self.trickFlags = bit.bor(self.trickFlags, 2) - Sound.play(self.game.data, "Tink") - end - -- Decrement frame set (backflip backward) - self.frameSet = self.frameSet - 1 - if self.frameSet < 1 then self.frameSet = 14 end +function SurfingMinigame:handleLanding() + local result = self:evaluateLanding() + if result == "wipeout" then + self.pikaState = PIKA_STATE_CRASHED + self.crashTimer = 96 + self.speedFixed = SPEED_INITIAL + self.frameSet = 4 + Sound.play(self.game.data, "Faint_Fall") + else + if result == "rough" then + self.speedFixed = math.max(SPEED_INITIAL, self.speedFixed - SPEED_ROUGH_PENALTY) + elseif result == "hard" then + self.speedFixed = math.max(SPEED_INITIAL, self.speedFixed - SPEED_HARD_PENALTY) end + if self.routine == ROUTINE_RUN_GAME then + self:calculateStuntPoints() + end + self.pikaState = PIKA_STATE_LANDING + self.landingTimer = 32 + self.frameSet = 4 + Sound.play(self.game.data, "Cut") + end +end + +function SurfingMinigame:updateJumping() + -- Process accumulated input on the 3-frame buffer boundary (only during active run) + if self.routine == ROUTINE_RUN_GAME then + self.joyCounter = (self.joyCounter + 1) % 3 + if self.joyCounter == 0 then + local rightHeld = bit.band(self.inputAccum, 1) ~= 0 + local leftHeld = bit.band(self.inputAccum, 2) ~= 0 + self.inputAccum = 0 + + -- Game Boy priority: Left D-pad checked first, then Right D-pad + if leftHeld then + self.rotCountRight = 0 + self.rotCountLeft = self.rotCountLeft + 1 + if self.rotCountLeft >= 11 then + self.rotCountLeft = 0 + self.radnessMeter = math.min(3, self.radnessMeter + 1) + self.trickFlags = bit.bor(self.trickFlags, 1) + Sound.play(self.game.data, "Tink") + end + self.frameSet = (self.frameSet % 14) + 1 + elseif rightHeld then + self.rotCountLeft = 0 + self.rotCountRight = self.rotCountRight + 1 + if self.rotCountRight >= 13 then + self.rotCountRight = 0 + self.radnessMeter = math.min(3, self.radnessMeter + 1) + self.trickFlags = bit.bor(self.trickFlags, 2) + Sound.play(self.game.data, "Tink") + end + self.frameSet = self.frameSet - 1 + if self.frameSet < 1 then self.frameSet = 14 end + end + end + else + self.inputAccum = 0 end -- Authentic Game Boy collision boundary & integer fixed-point jump physics if not self.jumpDescending then - self.pikaSubY = (self.pikaSubY or 0) + (self.jumpArcMagnitude ^ 2) * 4 + local a = math.floor(self.jumpArcMagnitude) + self.pikaSubY = (self.pikaSubY or 0) + (a * a) * 4 local intDelta = math.floor(self.pikaSubY / 256) self.pikaSubY = self.pikaSubY % 256 self.pikaY = self.pikaY - intDelta @@ -608,34 +795,16 @@ function SurfingMinigame:updateJumping() end else -- Hardware execution order: evaluate boundary before adding velocity - local waveY = math.floor(self:seaY(80)) + local waveY = math.floor(self:seaY(80)) - 16 if self.pikaY >= waveY then self.pikaY = waveY self.pikaSubY = 0 - -- Evaluate landing angle vs wave slope - local result = self:evaluateLanding() - if result == "wipeout" then - self.pikaState = PIKA_STATE_CRASHED - self.crashTimer = 96 - self.speedFixed = SPEED_INITIAL - self.frameSet = 4 - Sound.play(self.game.data, "Faint_Fall") - else - if result == "rough" then - self.speedFixed = math.max(SPEED_INITIAL, self.speedFixed - SPEED_ROUGH_PENALTY) - elseif result == "hard" then - self.speedFixed = math.max(SPEED_INITIAL, self.speedFixed - SPEED_HARD_PENALTY) - end - self:calculateStuntPoints() - self.pikaState = PIKA_STATE_LANDING - self.routineTimer = 32 - self.frameSet = 4 - Sound.play(self.game.data, "Cut") - end + self:handleLanding() return end - self.pikaSubY = (self.pikaSubY or 0) + (self.jumpArcMagnitude ^ 2) * 4 + local a = math.floor(self.jumpArcMagnitude) + self.pikaSubY = (self.pikaSubY or 0) + (a * a) * 4 local intDelta = math.floor(self.pikaSubY / 256) self.pikaSubY = self.pikaSubY % 256 self.pikaY = self.pikaY + intDelta @@ -644,57 +813,66 @@ function SurfingMinigame:updateJumping() if self.pikaY >= waveY then self.pikaY = waveY self.pikaSubY = 0 - local result = self:evaluateLanding() - if result == "wipeout" then - self.pikaState = PIKA_STATE_CRASHED - self.crashTimer = 96 - self.speedFixed = SPEED_INITIAL - self.frameSet = 4 - Sound.play(self.game.data, "Faint_Fall") - else - if result == "rough" then - self.speedFixed = math.max(SPEED_INITIAL, self.speedFixed - SPEED_ROUGH_PENALTY) - elseif result == "hard" then - self.speedFixed = math.max(SPEED_INITIAL, self.speedFixed - SPEED_HARD_PENALTY) - end - self:calculateStuntPoints() - self.pikaState = PIKA_STATE_LANDING - self.routineTimer = 32 - self.frameSet = 4 - Sound.play(self.game.data, "Cut") - end + self:handleLanding() end end end function SurfingMinigame:updateLanding() - self.routineTimer = self.routineTimer - 1 + self.landingTimer = (self.landingTimer or 0) - 1 + + -- Follow wave surface height continuously while landing so slopes don't cause position jumps! + local targetY = self:seaY(80) + self.pikaY = math.floor(targetY) - 16 + self.pikaSubY = 0 + -- Sine wave splash offset - self.pikaYOffset = math.floor(math.sin((32 - self.routineTimer) / 32 * math.pi * 2) * 4) - if self.routineTimer % 4 == 0 then - table.insert(self.waterSprays, { x = PIKA_X + 16, y = self.pikaY + 12, timer = 12 }) + self.pikaYOffset = math.floor(math.sin((32 - math.max(0, self.landingTimer)) / 32 * math.pi * 2) * 4) + if self.landingTimer % 4 == 0 then + table.insert(self.waterSprays, { x = PIKA_X, y = self.pikaY, timer = 4 }) end - if self.routineTimer <= 0 then + + if self.landingTimer <= 0 then self.pikaYOffset = 0 self.pikaState = PIKA_STATE_RIDING + self.frameSet = 4 end end function SurfingMinigame:updateCrashed() self.crashTimer = self.crashTimer - 1 + self:resetTempo() + -- Follow water surface while wiped out + local targetY = self:seaY(80) + self.pikaY = math.floor(targetY) - 16 + self.pikaSubY = 0 if self.crashTimer <= 0 then self.pikaState = PIKA_STATE_RIDING self.frameSet = 4 end end -function SurfingMinigame:update() - local input = self.game.input +-- Single Game Boy hardware VBlank cycle tick (59.7275 Hz) +function SurfingMinigame:tick() + local input = self.game and self.game.input self.t = self.t + 1 + self.rDiv = (self.rDiv + 1) % 256 - -- Accumulate physical button presses on every frame (eliminates polling blind spot) - if input:isDown("right") then self.inputAccum = bit.bor(self.inputAccum, 1) end - if input:isDown("left") then self.inputAccum = bit.bor(self.inputAccum, 2) end + -- Title Screen State + if self.routine == ROUTINE_TITLE then + if input and (input:wasPressed("start") or input:wasPressed("a")) then + self:startFromTitle() + end + return + end + + -- Accumulate physical button presses on every frame (only during active run) + if self.routine == ROUTINE_RUN_GAME then + if input and input:isDown("right") then self.inputAccum = bit.bor(self.inputAccum, 1) end + if input and input:isDown("left") then self.inputAccum = bit.bor(self.inputAccum, 2) end + else + self.inputAccum = 0 + end -- Update trick popups for i = #self.trickPopups, 1, -1 do @@ -728,7 +906,7 @@ function SurfingMinigame:update() self.routineTimer = 128 self.speedFixed = 0 self.ohNoBanner = true - Sound.play(self.game.data, "Faint_Fall") + Sound.play(self.game and self.game.data, "Faint_Fall") return end @@ -758,20 +936,44 @@ function SurfingMinigame:update() end elseif self.routine == ROUTINE_WAIT_RESULTS then - -- 192 frames coasting past the goal line + -- Coasting past the goal line self.distanceFixed = self.distanceFixed + (2 * 256) + self.cloudOffsetFixed = self.cloudOffsetFixed + math.floor((2 * 256) * 0.25) self:generateAhead() - self.pikaY = math.floor(self:seaY(80)) - self.routineTimer = self.routineTimer - 1 - if self.routineTimer <= 0 then + + -- Run Pikachu state machine so mid-air jumps and wipeout crashes complete + if self.pikaState == PIKA_STATE_JUMPING then + self:updateJumping() + elseif self.pikaState == PIKA_STATE_LANDING then + self:updateLanding() + elseif self.pikaState == PIKA_STATE_CRASHED then + self:updateCrashed() + else + -- Riding: follow water surface + local targetY = self:seaY(80) + self.pikaY = math.floor(targetY) - 16 + self.pikaSubY = 0 + self.frameSet = 4 + end + + if self.routineTimer > 0 then + self.routineTimer = self.routineTimer - 1 + end + + -- Only advance to scroll results when delay has finished AND Pikachu is upright + if self.routineTimer <= 0 and self.pikaState == PIKA_STATE_RIDING then self.routine = ROUTINE_SCROLL_RESULTS self.routineTimer = 36 + self.pikaState = PIKA_STATE_GAME_END end elseif self.routine == ROUTINE_SCROLL_RESULTS then self.distanceFixed = self.distanceFixed + (1 * 256) + self.cloudOffsetFixed = self.cloudOffsetFixed + math.floor((1 * 256) * 0.25) self:generateAhead() - self.pikaY = math.floor(self:seaY(80)) + self.pikaY = math.floor(self:seaY(80)) - 16 + self.pikaSubY = 0 + self.frameSet = 4 self.routineTimer = self.routineTimer - 1 if self.routineTimer <= 0 then self.routine = ROUTINE_DRAW_RESULTS @@ -813,7 +1015,7 @@ function SurfingMinigame:update() local step = math.min(self.hp, 99) self.hp = self.hp - step self.totalScore = self.totalScore + step - Sound.play(self.game.data, "Press_AB") + Sound.play(self.game and self.game.data, "Press_AB") else self.routine = ROUTINE_ADD_RAD_TOTAL end @@ -824,18 +1026,18 @@ function SurfingMinigame:update() local step = math.min(self.radness, 99) self.radness = self.radness - step self.totalScore = self.totalScore + step - Sound.play(self.game.data, "Press_AB") + Sound.play(self.game and self.game.data, "Press_AB") else self.routine = ROUTINE_WAIT_LAST self.routineTimer = 64 -- High score check - self.newRecord = self.totalScore > (self.game.save.surfingHighScore or 0) + self.newRecord = self.totalScore > ((self.game and self.game.save and self.game.save.surfingHighScore) or 0) if self.newRecord then - self.game.save.surfingHighScore = self.totalScore - Sound.play(self.game.data, "Get_Item1") - Sound.playPikaCry(self.game.data, 34) + if self.game and self.game.save then self.game.save.surfingHighScore = self.totalScore end + Sound.play(self.game and self.game.data, "Get_Item1") + Sound.playPikaCry(self.game and self.game.data, 34) else - Sound.playPikaCry(self.game.data, 28) + Sound.playPikaCry(self.game and self.game.data, 28) end end @@ -846,64 +1048,117 @@ function SurfingMinigame:update() end elseif self.routine == ROUTINE_EXIT_ON_PRESS_A then - if input:wasPressed("a") or input:wasPressed("b") then - self.game.stack:pop() + if input and input:wasPressed("a") then + if self.game and self.game.stack then self.game.stack:pop() end if self.onDone then self.onDone(self.totalScore) end end elseif self.routine == ROUTINE_GAME_OVER then self.routineTimer = self.routineTimer - 1 - if self.routineTimer <= 0 and (input:wasPressed("a") or input:wasPressed("b")) then - self.game.stack:pop() + if self.routineTimer <= 0 and input and input:wasPressed("a") then + if self.game and self.game.stack then self.game.stack:pop() end if self.onDone then self.onDone(0) end end end end --- Draw scrolling wave background +-- Decoupled timestep accumulator for modern multi-refresh-rate displays +function SurfingMinigame:update(dt) + if not dt then + self:tick() + return + end + + -- Accumulate real-world time + self.tickAccumulator = (self.tickAccumulator or 0) + dt + + -- Exact Game Boy Color framerate: 59.7275 Hz (approx 0.0167427 seconds per frame) + local gbTickRate = 1 / 59.7275 + + -- Process as many physical hardware frames as necessary + while self.tickAccumulator >= gbTickRate do + self:tick() + self.tickAccumulator = self.tickAccumulator - gbTickRate + end +end + +-- Draw scrolling wave background with authentic GPU shader HBlank wave distortion function SurfingMinigame:drawBackground() local scx = math.floor(self.distanceFixed / 256) local first = math.floor(scx / 16) - for c = first, first + 10 do - local col = self.cols[c] - if col then - local x = c * 16 - scx - for i = 1, 8 do - local mt = BG_METATILES[col.pat[i]] - if mt then - local y = (i - 1) * 16 - love.graphics.draw(self.bg, self.tq[mt[1]], x, y) - love.graphics.draw(self.bg, self.tq[mt[2]], x + 8, y) - love.graphics.draw(self.bg, self.tq[mt[3]], x, y + 8) - love.graphics.draw(self.bg, self.tq[mt[4]], x + 8, y + 8) + + local function renderTiles() + love.graphics.setColor(1, 1, 1, 1) + if not self.bg then return end + for c = first, first + 10 do + local col = self.cols[c] + if col then + local x = c * 16 - scx + for i = 1, 8 do + local mt = BG_METATILES[col.pat[i]] + if mt then + local y = (i - 1) * 16 + love.graphics.draw(self.bg, self.tq[mt[1]], x, y) + love.graphics.draw(self.bg, self.tq[mt[2]], x + 8, y) + love.graphics.draw(self.bg, self.tq[mt[3]], x, y + 8) + love.graphics.draw(self.bg, self.tq[mt[4]], x + 8, y + 8) + end end end end end + + if self.bgCanvas and self.waveShader and love.graphics.setCanvas and love.graphics.getCanvas then + -- 1. Capture the framework's active canvas + local prevCanvas = love.graphics.getCanvas() + + love.graphics.setCanvas(self.bgCanvas) + -- 2. Clear with transparency (0 alpha) so the sky remains empty + love.graphics.clear(0, 0, 0, 0) + renderTiles() + + -- 3. Restore the framework's canvas before drawing! + love.graphics.setCanvas(prevCanvas) + + -- 4. Safely capture and restore the shader + local prevShader = love.graphics.getShader and love.graphics.getShader() + love.graphics.setColor(1, 1, 1, 1) + love.graphics.setShader(self.waveShader) + + if self.waveShader.hasUniform and self.waveShader:hasUniform("u_time") then + self.waveShader:send("u_time", self.t / 60.0) + end + if self.waveShader.hasUniform and self.waveShader:hasUniform("u_water_line") then + self.waveShader:send("u_water_line", 48.0 / 144.0) + end + + love.graphics.draw(self.bgCanvas, 0, 0) + love.graphics.setShader(prevShader) + else + renderTiles() + end end -- Draw 3x3 Pikachu sprite (24x24 px centered at cx, cy) function SurfingMinigame:draw3x3(baseTile, cx, cy, flipX, flipY) + local sx = flipX and -1 or 1 + local sy = flipY and -1 or 1 for r = 0, 2 do for c = 0, 2 do local tileId = baseTile + r * 16 + c local q = self.oq[tileId] if q then - if not flipX and not flipY then - local dx = (cx - 12) + c * 8 - local dy = (cy - 12) + r * 8 - love.graphics.draw(self.ob, q, dx, dy) - else - local dx = (cx - 12) + (2 - c) * 8 + 8 - local dy = (cy - 12) + (2 - r) * 8 + 8 - love.graphics.draw(self.ob, q, dx, dy, 0, -1, -1) - end + local colIdx = flipX and (2 - c) or c + local rowIdx = flipY and (2 - r) or r + local dx = (cx - 12) + colIdx * 8 + (flipX and 8 or 0) + local dy = (cy - 12) + rowIdx * 8 + (flipY and 8 or 0) + love.graphics.draw(self.ob, q, dx, dy, 0, sx, sy) end end end end --- Draw HUD status bar +-- Draw HUD status bar with inverted progress marker (moving right-to-left towards beach) function SurfingMinigame:drawHUD() -- White background in bottom 16 rows love.graphics.setColor(1, 1, 1, 1) @@ -931,9 +1186,14 @@ function SurfingMinigame:drawHUD() end -- Mini-Pikachu progress marker (tile $fe) + -- Game Boy: initial OAM X = $50 (80) = screen X 72, decrements by 2 per section. + -- 24 sections × 2 = 48 px total travel, ending at screen X 24. + -- We interpolate continuously across those same 48 pixels. local distPx = math.floor(self.distanceFixed / 256) local progressRatio = math.min(1.0, math.max(0, distPx / (TOTAL_SECTIONS * 128))) - local markerX = 16 + math.floor(progressRatio * 80) + local markerStartX = 72 -- OAM X $50 (80) minus 8-pixel OAM offset = screen X 72 + local markerTrack = 48 -- 24 sections × 2 px per section + local markerX = markerStartX - math.floor(progressRatio * markerTrack) if self.ob and self.oq and self.oq[0xfe] then love.graphics.draw(self.ob, self.oq[0xfe], markerX, BG_HEIGHT + 6) end @@ -956,46 +1216,100 @@ function SurfingMinigame:drawResultsOutro() for r = 1, 10 do for c = 1, 20 do local tId = BEACH_OUTRO[r][c] - if tId and self.tq[tId] then + if tId and self.tq and self.tq[tId] then love.graphics.draw(self.bg, self.tq[tId], (c - 1) * 8, (r + 5) * 8) end end end - -- Textbox frame on rows 1..9 (X=8..152, Y=8..72) + -- Fill interior with white love.graphics.setColor(1, 1, 1, 1) - love.graphics.rectangle("fill", 8, 8, 144, 64) - love.graphics.setColor(0, 0, 0, 1) - love.graphics.rectangle("line", 8.5, 8.5, 143, 63) + love.graphics.rectangle("fill", 16, 16, 128, 56) - -- Text lines + -- Draw the textbox frame using original Game Boy border tiles (rows 1..9, Y=8..72) + local function drawBoxRow(rowIdx, leftTile, interiorTile, rightTile) + local y = rowIdx * 8 + if self.tq[leftTile] then + love.graphics.draw(self.bg, self.tq[leftTile], 8, y) + end + if interiorTile and self.tq[interiorTile] then + for colIdx = 2, 17 do + love.graphics.draw(self.bg, self.tq[interiorTile], colIdx * 8, y) + end + end + if self.tq[rightTile] then + love.graphics.draw(self.bg, self.tq[rightTile], 144, y) + end + end + + drawBoxRow(1, 0x3b, 0x40, 0x3c) + for r = 2, 8 do + drawBoxRow(r, 0x3f, nil, 0x3f) + end + drawBoxRow(9, 0x3d, 0x40, 0x3e) + + -- Text lines matching Game Boy screen memory coordinates (cols 2, 10, 15; rows 2, 4, 6, 8) Font.draw(Strings("HP Left"), 16, 16) if self.routine >= ROUTINE_WRITE_HP_LEFT then - Font.draw(string.format("%4d Pts", self.hp), 88, 16) + Font.draw(string.format("%04d", self.hp), 80, 16) + Font.draw(Strings("Pts"), 120, 16) end if self.routine >= ROUTINE_WRITE_RADNESS then Font.draw(Strings("Radness"), 16, 32) - Font.draw(string.format("%4d Pts", self.radness), 88, 32) + Font.draw(string.format("%04d", self.radness), 80, 32) + Font.draw(Strings("Pts"), 120, 32) end if self.routine >= ROUTINE_WRITE_TOTAL then Font.draw(Strings("Total"), 16, 48) - Font.draw(string.format("%4d Pts", self.totalScore), 88, 48) + Font.draw(string.format("%04d", self.totalScore), 80, 48) + Font.draw(Strings("Pts"), 120, 48) end if self.routine >= ROUTINE_WAIT_LAST then if self.newRecord then - Font.draw(Strings("Hi-Score!!"), 48, 60) + Font.draw(Strings("Hi-Score!!"), 48, 64) end end love.graphics.setColor(1, 1, 1, 1) end +-- Draw minigame Title Screen ("Pikachu's Beach") +function SurfingMinigame:drawTitleScreen() + love.graphics.setColor(1, 1, 1, 1) + love.graphics.rectangle("fill", 0, 0, 160, 144) + + -- 1. Draw authentic 160x144 composite title background from ROM + if self.titleBg then + love.graphics.draw(self.titleBg, 0, 0) + else + Font.draw("PIKACHU'S BEACH", 20, 32) + end + + -- 2. Draw 3x3 Pikachu intro sprite on the water using authentic OAM tile indices + if self.ob and self.oq then + local animBase = (math.floor(self.t / 32) % 2 == 0) and 0x0c or 0x09 + self:draw3x3(animBase, 80, 100, false, false) + end + + -- 3. High score display at bottom + Font.draw(string.format("Hi-Score %4d Pt", self.hiScore), 16, 120) + + if math.floor(self.t / 30) % 2 == 0 then + Font.draw("PRESS START", 36, 132) + end +end + function SurfingMinigame:draw() love.graphics.setColor(1, 1, 1, 1) love.graphics.rectangle("fill", 0, 0, 160, 144) + if self.routine == ROUTINE_TITLE then + self:drawTitleScreen() + return + end + if self.routine >= ROUTINE_DRAW_RESULTS and self.routine <= ROUTINE_EXIT_ON_PRESS_A then self:drawResultsOutro() return @@ -1004,22 +1318,42 @@ function SurfingMinigame:draw() -- Draw scrolling BG waves self:drawBackground() - -- Parallax clouds in sky + -- Parallax clouds in sky (scrolling left at uniform 0.25x camera speed) local cloudOffsetPx = math.floor(self.cloudOffsetFixed / 256) - local c1x = (32 - cloudOffsetPx * 2) % 200 - 40 - local c2x = (128 - cloudOffsetPx * 3) % 200 - 40 - -- Wide cloud (5 tiles: $ec, $ed, $ed, $ee, $ef) - for i, tid in ipairs({ 0xec, 0xed, 0xed, 0xee, 0xef }) do - love.graphics.draw(self.ob, self.oq[tid], c1x + (i - 1) * 8, 12) + local c1x = (160 - (cloudOffsetPx % 200)) - 40 + local c2x = (240 - (cloudOffsetPx % 200)) - 40 + if self.ob and self.oq then + -- Wide cloud (5 tiles: $ec, $ed, $ed, $ee, $ef) + for i, tid in ipairs({ 0xec, 0xed, 0xed, 0xee, 0xef }) do + love.graphics.draw(self.ob, self.oq[tid], c1x + (i - 1) * 8, 12) + end + -- Narrow cloud (4 tiles: $ec, $ed, $ee, $ef) + for i, tid in ipairs({ 0xec, 0xed, 0xee, 0xef }) do + love.graphics.draw(self.ob, self.oq[tid], c2x + (i - 1) * 8, 20) + end end - -- Narrow cloud (4 tiles: $ec, $ed, $ee, $ef) - for i, tid in ipairs({ 0xec, 0xed, 0xee, 0xef }) do - love.graphics.draw(self.ob, self.oq[tid], c2x + (i - 1) * 8, 20) + -- Helper function to draw OAM multi-sprite composite objects with palette and X-flipping + function self:drawOAMSprites(sprites, ox, oy) + if not (self.ob and self.oq) then return end + love.graphics.setColor(0.5, 0.5, 0.5, 1) -- OAM_PAL1 maps shade 1 to shade 2 (Sea Blue) + for _, sp in ipairs(sprites) do + local q = self.oq[sp.tile] + if q then + local x = ox + sp.dx + local y = oy + sp.dy + if sp.xflip then + love.graphics.draw(self.ob, q, x + 8, y, 0, -1, 1) + else + love.graphics.draw(self.ob, q, x, y) + end + end + end + love.graphics.setColor(1, 1, 1, 1) end - -- Draw water spray sprites + -- Draw water spray sprites on trailing edge of surfboard for _, s in ipairs(self.waterSprays) do - love.graphics.draw(self.ob, self.oq[0xa7], s.x, s.y) + self:drawOAMSprites(OAM_WATER_SPRAY, s.x, s.y) end -- Draw Pikachu @@ -1028,11 +1362,19 @@ function SurfingMinigame:draw() self.pikaScreenY = cy if self.pikaState == PIKA_STATE_CRASHED then - -- Empty surfboard + splash animation - love.graphics.draw(self.ob, self.oq[0x98], cx - 12, cy + 4) - love.graphics.draw(self.ob, self.oq[0x99], cx - 4, cy + 4) - love.graphics.draw(self.ob, self.oq[0x9a], cx + 4, cy + 4) - love.graphics.draw(self.ob, self.oq[0xa8], cx - 12, cy - 8) + -- Empty surfboard (3 tiles at bottom) + if self.ob and self.oq then + love.graphics.draw(self.ob, self.oq[0x98], cx - 12, cy + 4) + love.graphics.draw(self.ob, self.oq[0x99], cx - 4, cy + 4) + love.graphics.draw(self.ob, self.oq[0x9a], cx + 4, cy + 4) + end + -- Multi-tile splash animation (.SmallSplash then .LargeSplash) + local elapsed = 60 - (self.crashTimer or 0) + if elapsed < 16 then + self:drawOAMSprites(OAM_SMALL_SPLASH, cx, cy + 4) + else + self:drawOAMSprites(OAM_LARGE_SPLASH, cx, cy + 4) + end else local angleIdx = ((self.frameSet - 1) % 7) + 1 local isFlipped = self.frameSet > 7 @@ -1048,20 +1390,24 @@ function SurfingMinigame:draw() -- "START" banner if self.routine == ROUTINE_START_GAME then - for r = 0, 1 do - for c = 0, 5 do - local tid = 0xe0 + r * 16 + c - love.graphics.draw(self.ob, self.oq[tid], self.startBannerX - 24 + c * 8, 64 + r * 8) + if self.ob and self.oq then + for r = 0, 1 do + for c = 0, 5 do + local tid = 0xe0 + r * 16 + c + love.graphics.draw(self.ob, self.oq[tid], self.startBannerX - 24 + c * 8, 64 + r * 8) + end end end end -- "Oh no.." banner on Game Over if self.ohNoBanner then - for r = 0, 1 do - for c = 0, 5 do - local tid = 0xca + r * 16 + c - love.graphics.draw(self.ob, self.oq[tid], 80 - 24 + c * 8, 64 + r * 8) + if self.ob and self.oq then + for r = 0, 1 do + for c = 0, 5 do + local tid = 0xca + r * 16 + c + love.graphics.draw(self.ob, self.oq[tid], 80 - 24 + c * 8, 64 + r * 8) + end end end end diff --git a/src/ui/TouchControlsEditor.lua b/src/ui/TouchControlsEditor.lua index f48905f2..a490746d 100644 --- a/src/ui/TouchControlsEditor.lua +++ b/src/ui/TouchControlsEditor.lua @@ -73,7 +73,10 @@ function Editor.load(opts) } local optsTbl = SaveData.loadOptions() local applied = optsTbl - if opts.version == "gold" then + local GameVersion = require("src.core.GameVersion") + local gen2 = GameVersion.VERSIONS[opts.version] + and GameVersion.generation(opts.version) == 2 + if gen2 then local gold = type(optsTbl.gold) == "table" and optsTbl.gold or {} applied = { touchControls = gold.touchControls, @@ -151,7 +154,9 @@ local function persist() skin = cfg.skin, layouts = cfg.layouts, } - if Editor.version == "gold" then + local GameVersion = require("src.core.GameVersion") + if GameVersion.VERSIONS[Editor.version] + and GameVersion.generation(Editor.version) == 2 then opts.gold = type(opts.gold) == "table" and opts.gold or {} opts.gold.touchControls = block else @@ -569,7 +574,7 @@ function Editor.new(game) local state = { game = game, isOpaque = true } Editor.hostPoll = true Editor.load({ - version = "gold", + version = require("src.core.GameVersion").get(), hostPoll = true, onClose = function() Editor.hostPoll = false diff --git a/src/ui/gen2/BattleTransition.lua b/src/ui/gen2/BattleTransition.lua index ed3e4a15..06cc7e16 100644 --- a/src/ui/gen2/BattleTransition.lua +++ b/src/ui/gen2/BattleTransition.lua @@ -619,8 +619,8 @@ function BattleTransition:grid(w, h) scale = math.max(1, math.floor(math.min(w / 160, h / 144))) end local size = 8 * scale - local ox = math.floor((w - 160 * scale) / 2) - local oy = math.floor((h - 144 * scale) / 2) + local Chrome = require("src.ui.gen2.Chrome") + local ox, oy = Chrome.fitOrigin(w, h, scale) return size, ox, oy end diff --git a/src/ui/gen2/CardFlip.lua b/src/ui/gen2/CardFlip.lua index d410e73b..a1b596ce 100644 --- a/src/ui/gen2/CardFlip.lua +++ b/src/ui/gen2/CardFlip.lua @@ -303,12 +303,12 @@ local TEXT_X, TEXT_Y, TEXT_LINE = 1, 14, 2 -- data/text/common_3.asm; none of these are in the cache's text.lua, because no -- script bytecode the extractor walks points at them. CardFlip.TEXTS = { - playWithThree = { "Play with three", "coins?" }, - notEnough = { "Not enough coins…" }, - chooseACard = { "Choose a card." }, - placeYourBet = { "Place your bet." }, - playAgain = { "Want to play", "again?" }, - shuffled = { "The cards have", "been shuffled." }, + playWithThree = { "Play with", "3 coins?" }, + notEnough = { "Not enough", "coins." }, + chooseACard = { "Choose a", "card." }, + placeYourBet = { "Place", "your bet" }, + playAgain = { "Play", "again?" }, + shuffled = { "The cards", "shuffled." }, yeah = { "Yeah!" }, darn = { "Darn…" }, } @@ -401,14 +401,34 @@ function CardFlip:enterBet() self.lines = CardFlip.TEXTS.placeYourBet end --- .CheckTheCard: the dealt card is turned face up and marked on the discard --- pile, which is what blanks its cell on the odds board. +-- .CheckTheCard: trigger hardware-accurate discrete tile flip sequence function CardFlip:flip() local card = CardFlip.dealt(self.deck, self.played, self.which) self.faceUp = card self.discarded[card] = true - self:sfx(SFX_CHOOSE) - self:tabulate() + local won = CardFlip.payout(self.cursorX, self.cursorY, card) + self.payoutLeft = won + self.payoutTick = 0 + self.phase = "flipping" + self.flipTimer = 0 + self.targetCard = card +end + +function CardFlip:updateFlipping() + self.flipTimer = (self.flipTimer or 0) + 1 + if self.flipTimer == 4 then + self:sfx(SFX_CHOOSE) + elseif self.flipTimer >= 12 then + if (self.payoutLeft or 0) > 0 then + self.phase = "payout" + self.lines = CardFlip.TEXTS.yeah + self:sfx(SFX_WIN) + else + self.phase = "result" + self.lines = CardFlip.TEXTS.darn + self:sfx(SFX_WRONG) + end + end end function CardFlip:tabulate() @@ -472,7 +492,21 @@ function CardFlip:quit() if self.onClose then self.onClose() end end -function CardFlip:update(_dt) +function CardFlip:update(dt) + -- Support both fixed-tick 60Hz loop and variable dt accumulator + if dt and dt > 0 then + self.dtAccum = (self.dtAccum or 0) + dt + local TICK = 1 / 60 + while self.dtAccum >= TICK do + self.dtAccum = self.dtAccum - TICK + self:tick() + end + else + self:tick() + end +end + +function CardFlip:tick() local input = self.game and self.game.input if not input then return end local phase = self.phase @@ -539,6 +573,11 @@ function CardFlip:update(_dt) return end + if phase == "flipping" then + self:updateFlipping() + return + end + if phase == "payout" then self:updatePayout() return @@ -552,79 +591,370 @@ function CardFlip:update(_dt) end -- ------------------------------------------------------------------- draw +-- +-- Authentic Color Game Boy palettes and tile graphics matching pret/pokegold +local TileSheet = require("src.ui.gen2.TileSheet") +local GbcPalette = require("src.render.GbcPalette") + +local CARDFLIP_PALS = { + bg = { + [0] = { { 255, 255, 255 }, { 140, 57, 255 }, { 49, 156, 66 }, { 0, 0, 0 } }, -- 0: Base / Table green + [1] = { { 255, 255, 255 }, { 239, 206, 0 }, { 49, 156, 66 }, { 0, 0, 0 } }, -- 1: Pikachu (Yellow) + [2] = { { 255, 255, 255 }, { 255, 107, 247 }, { 49, 156, 66 }, { 0, 0, 0 } }, -- 2: Jigglypuff (Pink) + [3] = { { 255, 255, 255 }, { 66, 140, 247 }, { 49, 156, 66 }, { 0, 0, 0 } }, -- 3: Poliwag (Blue) + [4] = { { 255, 255, 255 }, { 66, 255, 66 }, { 49, 156, 66 }, { 0, 0, 0 } }, -- 4: Oddish (Green) + [5] = { { 255, 255, 255 }, { 140, 57, 255 }, { 49, 156, 66 }, { 0, 0, 0 } }, -- 5: Level header + [6] = { { 255, 255, 255 }, { 140, 57, 255 }, { 49, 156, 66 }, { 0, 0, 0 } }, -- 6: Border + [7] = { { 255, 255, 255 }, { 140, 57, 255 }, { 49, 156, 66 }, { 0, 0, 0 } }, -- 7: Textbox + }, + obj = { + [0] = { { 255, 255, 255 }, { 248, 56, 40 }, { 248, 56, 40 }, { 248, 56, 40 } }, -- Authentic GBC Red OAM + } +} + +local TILEMAP = nil +local function getCardFlipTilemap() + if TILEMAP == nil then + local path = "assets/generated/card_flip/card_flip.tilemap" + local f = io.open(path, "rb") + if f then + local data = f:read("*a") + f:close() + TILEMAP = {} + for i = 1, #data do + TILEMAP[i] = string.byte(data, i) + end + else + TILEMAP = false + end + end + return TILEMAP or nil +end + +function CardFlip:sheets() + if self.sheet1 == nil then + self.sheet1 = TileSheet.new({ path = "assets/generated/card_flip/card_flip_1.png", wide = 16, firstTile = 0 }) + self.sheet2 = TileSheet.new({ path = "assets/generated/card_flip/card_flip_2.png", wide = 3, firstTile = 0 }) + self.sheet3 = TileSheet.new({ path = "assets/generated/card_flip/card_flip_3.png", wide = 1, firstTile = 0 }) + self.sheetOn = TileSheet.new({ path = "assets/generated/card_flip/on.png", wide = 1, firstTile = 0 }) + self.sheetOff = TileSheet.new({ path = "assets/generated/card_flip/off.png", wide = 1, firstTile = 0 }) + end + return self.sheet1, self.sheet2, self.sheet3, self.sheetOn, self.sheetOff +end + +function CardFlip:cursorQuads() + if self.s3Image == nil then + local _, _, s3 = self:sheets() + self.s3Image = s3:image() + if self.s3Image and love and love.graphics then + local G = love.graphics + self.quadCorner = G.newQuad(0, 0, 8, 8, 8, 56) -- Tile 0: 1px corner + self.quadVEdge = G.newQuad(0, 8, 8, 8, 8, 56) -- Tile 1: 1px vertical edge + self.quadHEdge = G.newQuad(0, 16, 8, 8, 8, 56) -- Tile 2: 1px horizontal edge + end + end + return self.s3Image, self.quadCorner, self.quadVEdge, self.quadHEdge +end + +local HEADER_TILE_MAP = { + [0x3E] = 0, [0x3F] = 1, + [0x40] = 3, [0x41] = 4, + [0x42] = 6, [0x43] = 7, + [0x44] = 9, [0x45] = 10, + [0x46] = 12, [0x47] = 13, + [0x48] = 15, [0x49] = 16, + [0x4A] = 18, [0x4B] = 19, + [0x4C] = 21, [0x4D] = 22, +} + +-- Draw an authentic GBC red cursor bounding frame using OAM sprite tiles. +-- On real GBC hardware OAM color 0 is always transparent, so only the 1px +-- dark edges of tiles 0-2 are visible; the board shows through the middle. +-- We replicate this by drawing in "multiply" blend mode: white (255,255,255) +-- pixels multiply to the board colour unchanged, black (0,0,0) pixels tinted +-- to GBC red draw the border, and nothing fills the interior. +function CardFlip:drawOamBox(px, py, w, h) + local img, qCorner, qVEdge, qHEdge = self:cursorQuads() + local G = love.graphics + + if not (img and qCorner and qVEdge and qHEdge) then + -- Fallback: plain 1px red outline + G.setColor(248 / 255, 56 / 255, 40 / 255, 1) + G.rectangle("fill", px, py, w, 1) + G.rectangle("fill", px, py + h - 1, w, 1) + G.rectangle("fill", px, py, 1, h) + G.rectangle("fill", px + w - 1, py, 1, h) + G.setColor(1, 1, 1, 1) + return + end + + local prevBlend, prevAlpha = G.getBlendMode() + G.setBlendMode("multiply", "premultiplied") + -- Tint: black (0) → GBC red, white (255) → white (passthrough = transparent) + G.setColor(248 / 255, 56 / 255, 40 / 255, 1) + + -- 4 Corners + G.draw(img, qCorner, px, py, 0, 1, 1) + G.draw(img, qCorner, px + w, py, 0, -1, 1) + G.draw(img, qCorner, px, py + h, 0, 1, -1) + G.draw(img, qCorner, px + w, py + h, 0, -1, -1) + + -- Top & Bottom horizontal edges + if w > 16 then + for x = px + 8, px + w - 16, 8 do + G.draw(img, qHEdge, x, py, 0, 1, 1) + G.draw(img, qHEdge, x, py + h, 0, 1, -1) + end + end + + -- Left & Right vertical edges + if h > 16 then + for y = py + 8, py + h - 16, 8 do + G.draw(img, qVEdge, px, y, 0, 1, 1) + G.draw(img, qVEdge, px + w, y, 0, -1, 1) + end + end + + G.setBlendMode(prevBlend, prevAlpha) + G.setColor(1, 1, 1, 1) +end + function CardFlip:drawBoard() - -- The twelve hand lights down column 9; CARDFLIP_LIGHT_ON marks the hand - -- being played and every one before it stays off. - for row = 0, CardFlip.HANDS_PER_DECK - 1 do - Chrome.print(row == self.played and "o" or ".", LIGHT_X, row) - end - for x = 2, 5 do - Chrome.print(CardFlip.MON_LABELS[x - 2], MON_COL[x], MON_ROW) - -- The pair headers sit above the two Pokemon they cover. - if x % 2 == 0 then Chrome.print("6", MON_COL[x] + 1, MON_PAIR_ROW) end - end - for y = 2, 7 do - local row = LEVEL_ROW[y] - Chrome.print(tostring(y - 1), LEVEL_COL, row) - if y % 2 == 0 then Chrome.print("9", LEVEL_PAIR_COL, row) end + local s1, s2, s3, sOn, sOff = self:sheets() + local tm = getCardFlipTilemap() + local G = love.graphics + + -- Green background fill + G.setColor(49 / 255, 156 / 255, 66 / 255, 1) + G.rectangle("fill", 0, 0, 160, 144) + + if not tm or not s2:available() then + -- Fallback simple board + for row = 0, CardFlip.HANDS_PER_DECK - 1 do + Chrome.print(row == self.played and "o" or ".", LIGHT_X, row) + end for x = 2, 5 do - -- A still-in-the-deck cell stands in for the card back until the art - -- lands. It cannot be '#': that is charmap.asm $54, the text command - -- that places "POKé", not a one-tile glyph. - local card = CardFlip.card(y - 2, x - 2) - Chrome.print(self.discarded[card] and " " or "?", CARD_COL[x], row) + Chrome.print(CardFlip.MON_LABELS[x - 2], MON_COL[x], MON_ROW) + if x % 2 == 0 then Chrome.print("6", MON_COL[x] + 1, MON_PAIR_ROW) end + end + for y = 2, 7 do + local pair = math.floor((y - 2) / 2) + local isBottom = ((y - 2) % 2 == 1) + local row = 3 + pair * 3 + (isBottom and 1 or 0) + Chrome.print(tostring(y - 1), LEVEL_COL, row) + if y % 2 == 0 then Chrome.print("9", LEVEL_PAIR_COL, row) end + for x = 2, 5 do + local card = CardFlip.card(y - 2, x - 2) + Chrome.print(self.discarded[card] and " " or "?", MON_COL[x], row) + end + end + return + end + + -- Draw the 11x12 board tilemap at (9, 0) + for ty = 0, 11 do + for tx = 0, 10 do + local idx = ty * 11 + tx + 1 + local tileId = tm[idx] + local screenX = 9 + tx + local screenY = ty + + -- Attribute palette + local pal = 0 + if screenY >= 1 and screenY <= 2 then + if screenX == 12 or screenX == 13 then pal = 1 -- Pikachu + elseif screenX == 14 or screenX == 15 then pal = 2 -- Jigglypuff + elseif screenX == 16 or screenX == 17 then pal = 3 -- Poliwag + elseif screenX == 18 or screenX == 19 then pal = 4 -- Oddish + end + elseif screenX == 9 then + pal = 1 -- Lights + end + + local colors = CARDFLIP_PALS.bg[pal] + s1.palette = colors + s2.palette = colors + s3.palette = colors + + if screenX == 9 then + -- Column 9: Light buttons + if screenY == self.played then + sOn.palette = colors + sOn:draw(0, screenX, screenY) + else + sOff.palette = colors + sOff:draw(0, screenX, screenY) + end + elseif tileId >= 0x3e then + -- Board graphics from card_flip_2 (using accurate 2x2 header tile mapping) + local mappedId = HEADER_TILE_MAP[tileId] or (tileId - 0x3e) + s2:draw(mappedId, screenX, screenY) + elseif tileId < 0x3e then + -- Graphics from card_flip_1 + s1:draw(tileId, screenX, screenY) + end + end + end + + -- Draw discarded card blanking covers (each 2-tile wide stacked card cell is 16x12 px) + for y = 2, 7 do + local level = y - 2 + local pair = math.floor(level / 2) + local isBottom = (level % 2 == 1) + local py = 24 + pair * 24 + (isBottom and 12 or 0) + for x = 2, 5 do + local mon = x - 2 + local card = CardFlip.card(level, mon) + if self.discarded[card] then + -- Discarded cover over full 16x12 stacked card cell + G.setColor(49 / 255, 156 / 255, 66 / 255, 1) + G.rectangle("fill", MON_COL[x] * 8, py, 16, 12) + end end end end -function CardFlip:cursorCell() +function CardFlip:cursorBounds() local x, y = self.cursorX, self.cursorY - local col - if x == 0 then col = LEVEL_PAIR_COL - elseif x == 1 then col = LEVEL_COL - else col = MON_COL[x] end - local row - if y == 0 then row = MON_PAIR_ROW - elseif y == 1 then row = MON_ROW - else row = LEVEL_ROW[y] end - return col, row + if y == 0 then + -- Pokemon Pair: spans 4 columns (32px), 1 row (8px) + local px = (MON_COL[x] or 12) * 8 + local py = MON_PAIR_ROW * 8 + return px, py, 32, 8 + elseif y == 1 then + -- Single Pokemon: 2x2 tiles (16x16 px) + local px = (MON_COL[x] or 12) * 8 + local py = MON_ROW * 8 + return px, py, 16, 16 + elseif x == 0 then + -- Level Pair: 1 column (8px), spans 24px across the full pair + local pair = math.floor((y - 2) / 2) + local px = LEVEL_PAIR_COL * 8 + local py = 24 + pair * 24 + return px, py, 8, 24 + elseif x == 1 then + -- Single Level: 1 column (8px), 12px tall for each stacked card + local pair = math.floor((y - 2) / 2) + local isBottom = ((y - 2) % 2 == 1) + local px = LEVEL_COL * 8 + local py = 24 + pair * 24 + (isBottom and 12 or 0) + return px, py, 8, 12 + else + -- Exact Card: 2 columns (16px), 12px tall for each stacked card + local pair = math.floor((y - 2) / 2) + local isBottom = ((y - 2) % 2 == 1) + local px = (MON_COL[x] or 12) * 8 + local py = 24 + pair * 24 + (isBottom and 12 or 0) + return px, py, 16, 12 + end end --- CardFlip_DisplayCardFaceUp: the level digit at the box origin + (3,1) and the --- 3x3 Pokepic one row further down. +local FACE_DOWN_TILES = { + { 0x08, 0x09, 0x09, 0x09, 0x0a }, + { 0x0b, 0x28, 0x2b, 0x28, 0x0c }, + { 0x0b, 0x2c, 0x2d, 0x2e, 0x0c }, + { 0x0b, 0x2f, 0x30, 0x31, 0x0c }, + { 0x0b, 0x32, 0x33, 0x34, 0x0c }, + { 0x0d, 0x0e, 0x0e, 0x0e, 0x0f }, +} + +local FACE_UP_TILES = { + { 0x18, 0x19, 0x19, 0x19, 0x1a }, + { 0x1b, 0x35, 0x28, 0x28, 0x1c }, + { 0x0b, 0x28, 0x28, 0x28, 0x0c }, + { 0x0b, 0x28, 0x28, 0x28, 0x0c }, + { 0x0b, 0x28, 0x28, 0x28, 0x0c }, + { 0x1d, 0x1e, 0x1e, 0x1e, 0x1f }, +} + +local MON_ANCHORS = { + [0] = 24, -- Pikachu (tiles 24..32 in card_flip_2) + [1] = 33, -- Jigglypuff (tiles 33..41 in card_flip_2) + [2] = 42, -- Poliwag (tiles 42..50 in card_flip_2) + [3] = 51, -- Oddish (tiles 51..59 in card_flip_2) +} + +-- Draw face-down, flipping, or face-up card at (2, 0) or (2, 6) function CardFlip:drawCards() + local s1, s2 = self:sheets() + for slot = 1, 2 do local box = CARD_BOX[slot] - Chrome.box(box.x, box.y, CARD_BOX_W, CARD_BOX_H) + local bx, by = box.x * 8, box.y * 8 local chosen = (slot - 1) == self.which - if self.faceUp and chosen then - Chrome.print(tostring(CardFlip.level(self.faceUp) + 1), box.x + 3, - box.y + 1) - Chrome.print(CardFlip.MON_LABELS[CardFlip.mon(self.faceUp)], - box.x + 1, box.y + 3) - elseif self.phase == "choose" and chosen then - Chrome.cursor(box.x, box.y + 3) + local isFlipping = (self.phase == "flipping") and chosen + local isFaceUp = (self.faceUp and chosen) + + if isFaceUp or (isFlipping and (self.flipTimer or 0) >= 4) then + local activeCard = self.faceUp or self.targetCard or 0 + local lvl = CardFlip.level(activeCard) + 1 + local mon = CardFlip.mon(activeCard) + local monPal = CARDFLIP_PALS.bg[mon + 1] or CARDFLIP_PALS.bg[1] + + s1.palette = CARDFLIP_PALS.bg[0] + for cy = 1, 6 do + for cx = 1, 5 do + local tid = FACE_UP_TILES[cy][cx] + s1:draw(tid, box.x + cx - 1, box.y + cy - 1) + end + end + + -- Level digit at (box.x + 3, box.y + 1) + if isFaceUp or (isFlipping and (self.flipTimer or 0) >= 8) then + Chrome.print(tostring(lvl), box.x + 3, box.y + 1) + + -- Draw 3x3 Pokemon pic from card_flip_2 (s2) at (box.x + 1, box.y + 2) + s2.palette = monPal + local anchor = MON_ANCHORS[mon] or 24 + for py = 0, 2 do + for px = 0, 2 do + local tid = anchor + py * 3 + px + s2:draw(tid, box.x + 1 + px, box.y + 2 + py) + end + end + end + else + s1.palette = CARDFLIP_PALS.bg[0] + for cy = 1, 6 do + for cx = 1, 5 do + local tid = FACE_DOWN_TILES[cy][cx] + s1:draw(tid, box.x + cx - 1, box.y + cy - 1) + end + end + + if self.phase == "choose" and chosen then + -- Authentic OAM red selection box around 5x6 card (40x48 px) + self:drawOamBox(bx, by, 40, 48) + end end end end function CardFlip:drawPanel() - Chrome.clear() self:drawBoard() self:drawCards() - Chrome.textbox(COIN_BOX_X, COIN_BOX_Y, COIN_BOX_W - 2, COIN_BOX_H - 2) - Chrome.print("COIN", COIN_LABEL_X, COIN_LABEL_Y) - Chrome.print(Chrome.number(self:coins(), 4, true), COIN_VALUE_X, COIN_VALUE_Y) + + -- Dialogue / Message box at (0, 12), 10 wide, 6 tall (interior 8x4) if self.lines then - Chrome.textbox(TEXT_BOX_X, TEXT_BOX_Y, TEXT_BOX_W - 2, TEXT_BOX_H - 2) + Chrome.textbox(TEXT_BOX_X, TEXT_BOX_Y, 8, 4) for i, line in ipairs(self.lines) do Chrome.print(line, TEXT_X, TEXT_Y + (i - 1) * TEXT_LINE) end end + + -- Coin box at (9, 15), 11 wide, 3 tall (interior 9x1) + Chrome.textbox(COIN_BOX_X, COIN_BOX_Y, 9, 1) + Chrome.print("COIN", COIN_LABEL_X, COIN_LABEL_Y) + Chrome.print(Chrome.number(self:coins(), 4, true), COIN_VALUE_X, COIN_VALUE_Y) + if self.phase == "bet" then - local col, row = self:cursorCell() - Chrome.cursor(col - 1, row) + self.betBlink = (self.betBlink or 0) + 1 + if (self.betBlink % 32) < 24 then + local px, py, w, h = self:cursorBounds() + self:drawOamBox(px, py, w, h) + end end + if self.phase == "ask" or self.phase == "again" then -- YesNoBox: a 6x5 box at (14,7) with YES at (16,8) and NO at (16,10). Chrome.textbox(14, 7, 4, 3) @@ -652,3 +982,4 @@ function CardFlip:drawWidescreen(winW, winH) end return CardFlip + diff --git a/src/ui/gen2/Chrome.lua b/src/ui/gen2/Chrome.lua index 709a21f0..12f8a802 100644 --- a/src/ui/gen2/Chrome.lua +++ b/src/ui/gen2/Chrome.lua @@ -66,6 +66,15 @@ function Chrome.fitOrigin(winW, winH, scale) local x, y, w, h = playfieldRect(winW, winH) return x + math.floor((w - Chrome.SCREEN_W * 8 * scale) / 2), y + math.floor((h - Chrome.SCREEN_H * 8 * scale) / 2) + - Chrome.positionLift(winW, winH, scale) +end + +function Chrome.positionLift(winW, winH, scale) + local ok, ScreenPosition = pcall(require, "src.core.ScreenPosition") + if not ok or ScreenPosition.skinActive(winW, winH) then return 0 end + local _, _, _, h = playfieldRect(winW, winH) + return ScreenPosition.lift(h, Chrome.SCREEN_H * 8 * (scale + or Chrome.fitScale(winW, winH)), ScreenPosition.safeTop()) end -- A bordered box, tile coords. Leaves the draw color black for text. diff --git a/src/ui/gen2/Credits.lua b/src/ui/gen2/Credits.lua index 8a04726b..97ca86ba 100644 --- a/src/ui/gen2/Credits.lua +++ b/src/ui/gen2/Credits.lua @@ -189,11 +189,18 @@ defineString("CREDIT_END", "END") -- STAFF and everything after it is a heading. Anything BELOW this id is a -- person as far as ParseCredits is concerned. -Credits.STAFF = defineString("STAFF", { - " #MON", - " GOLD VERSION", - " PORT STAFF", -}) +-- Credits_Staff differs per edition, including the centring spaces +-- (data/credits_strings.asm:54-60). +Credits.STAFF = defineString("STAFF", + require("src.core.GameVersion").get() == "silver" and { + " #MON", + " SILVER VERSION", + " PORT STAFF", + } or { + " #MON", + " GOLD VERSION", + " PORT STAFF", + }) defineString("DIRECTOR", " DIRECTOR") defineString("PROGRAMMING", " PROGRAMMING") defineString("ENGINE_DESIGN", " ENGINE DESIGN") diff --git a/src/ui/gen2/GoldSilverIntro.lua b/src/ui/gen2/GoldSilverIntro.lua index 4d9b6ac3..b8eeb39f 100644 --- a/src/ui/gen2/GoldSilverIntro.lua +++ b/src/ui/gen2/GoldSilverIntro.lua @@ -147,8 +147,8 @@ function GoldSilverIntro.new(game, opts) -- the movie's full 2335 frames over an empty screen and reads exactly -- like "the intro does not work". Say so instead: the fix is a -- re-import, and nothing else in the boot chain will mention it. - Logger.warn("gold intro: no intro.lua in the cache -- re-import Gold " - .. "or the movie plays blank") + Logger.warn("gen2 intro: no intro.lua in the cache -- re-import " + .. "this version or the movie plays blank") end self.images = {} self.sheets = {} diff --git a/src/ui/gen2/MainMenu.lua b/src/ui/gen2/MainMenu.lua index c8fb68fc..d6f97b70 100644 --- a/src/ui/gen2/MainMenu.lua +++ b/src/ui/gen2/MainMenu.lua @@ -56,7 +56,7 @@ function MainMenu.new(game, opts) self.save = opts.save if self.save == nil and opts.hasSave ~= false then - local loaded = Save.load("gold") + local loaded = Save.load() self.save = loaded end self.hasSave = opts.hasSave diff --git a/src/ui/gen2/NamePick.lua b/src/ui/gen2/NamePick.lua index e9f1011d..ccfe5ff8 100644 --- a/src/ui/gen2/NamePick.lua +++ b/src/ui/gen2/NamePick.lua @@ -34,8 +34,14 @@ local NamePick = {} NamePick.__index = NamePick NamePick.isOpaque = true --- data/player_names.asm PlayerNameArray, Gold's half of the IF. +-- data/player_names.asm PlayerNameArray, one half of the IF per edition. local PRESETS = { "GOLD", "HIRO", "TAYLOR", "KARL" } +local PRESETS_SILVER = { "SILVER", "KAMON", "OSCAR", "MAX" } + +local function presetsFor() + local silver = require("src.core.GameVersion").get() == "silver" + return silver and PRESETS_SILVER or PRESETS +end -- menu_coords 0, 0, 10, TEXTBOX_Y - 1 (TEXTBOX_Y = 12). local BOX_X1, BOX_Y1, BOX_X2, BOX_Y2 = 0, 0, 10, 11 @@ -60,7 +66,7 @@ function NamePick.new(game, opts) self.game = game self.onDone = opts.onDone self.items = { "NEW NAME" } - for _, name in ipairs(opts.presets or PRESETS) do + for _, name in ipairs(opts.presets or presetsFor()) do self.items[#self.items + 1] = name end -- `db 1 ; default option`: the cursor starts on NEW NAME, not on a preset. @@ -106,7 +112,7 @@ function NamePick:openNaming() if name and #name > 0 then self:choose(name) else - self:choose(self.items[2] or "GOLD") + self:choose(self.items[2] or presetsFor()[1]) end end, }) @@ -146,7 +152,7 @@ function NamePick:update(_dt) if self.fontOk then self:openNaming() else - self:choose(self.items[2] or "GOLD") + self:choose(self.items[2] or presetsFor()[1]) end else -- A preset returns through MovePlayerPicLeft, so the pic walks back @@ -227,5 +233,7 @@ function NamePick:drawWidescreen(winW, winH) end NamePick.PRESETS = PRESETS +NamePick.PRESETS_SILVER = PRESETS_SILVER +NamePick.presetsFor = presetsFor return NamePick diff --git a/src/ui/gen2/OakSpeech.lua b/src/ui/gen2/OakSpeech.lua index 00684b3a..a68f6ebb 100644 --- a/src/ui/gen2/OakSpeech.lua +++ b/src/ui/gen2/OakSpeech.lua @@ -72,8 +72,8 @@ end -- Naming presets are boot config the same way Gen 1 reads them -- (field.boot.namePresets), so a total conversion that replaces the list once --- replaces it for both games; NamePick.PRESETS (data/player_names.asm --- PlayerNameArray) is Gold's fallback. +-- replaces it for both games; NamePick.presetsFor (data/player_names.asm +-- PlayerNameArray) is the running edition's fallback. local function namePresets(game, who, fallback) local boot = game and game.data and game.data.field and game.data.field.boot @@ -351,9 +351,9 @@ function OakSpeech:openNamePick(step) picColors = self.playerColors, presets = step.presets or namePresets(self.game, step.presetsWho or step.who or "player", - step.presetsFallback or NamePick.PRESETS), + step.presetsFallback or NamePick.presetsFor()), onDone = function(name) - name = name or "GOLD" + name = name or NamePick.presetsFor()[1] self.game.save.player.name = name self.game.stack:pop() -- NamePick self.busy = false diff --git a/src/ui/gen2/OptionsMenu.lua b/src/ui/gen2/OptionsMenu.lua index 901f4d2a..0d3a3e11 100644 --- a/src/ui/gen2/OptionsMenu.lua +++ b/src/ui/gen2/OptionsMenu.lua @@ -196,6 +196,15 @@ local ROWS = { return VideoMode.normalize(options.videoMode) == "borderless" and "FULL" or "WINDOWED" end }, + { label = "SCREEN POS", key = "screenPos", port = true, + cycle = function(options, delta) + local ScreenPosition = require("src.core.ScreenPosition") + options.screenPos = ScreenPosition.cycle(options.screenPos, delta) + ScreenPosition.setMode(options.screenPos) + end, + text = function(options) + return require("src.core.ScreenPosition").label(options.screenPos) + end }, { id = "touchControls", label = "TOUCH PAD", port = true, text = function(options) local tc = options.touchControls diff --git a/src/ui/gen2/PcMenu.lua b/src/ui/gen2/PcMenu.lua index 06c724ad..3d283807 100644 --- a/src/ui/gen2/PcMenu.lua +++ b/src/ui/gen2/PcMenu.lua @@ -181,7 +181,7 @@ function PcMenu:beginChangeBox(index) self.saveTimer = 0 self.saved = nil local existed = self.saveExists - if existed == nil then existed = Save.exists("gold") end + if existed == nil then existed = Save.exists() end self.existed = existed end diff --git a/src/ui/gen2/SaveMenu.lua b/src/ui/gen2/SaveMenu.lua index bd745d63..31811fed 100644 --- a/src/ui/gen2/SaveMenu.lua +++ b/src/ui/gen2/SaveMenu.lua @@ -71,7 +71,7 @@ function SaveMenu.new(game, opts) self.onDone = opts.onDone self.writer = opts.writer or Save.save local existed = opts.existed - if existed == nil then existed = Save.exists("gold") end + if existed == nil then existed = Save.exists() end self.existed = existed -- confirm -> overwrite (only when a file exists) -> saving -> done self.phase = "confirm" diff --git a/src/ui/gen2/SlotMachine.lua b/src/ui/gen2/SlotMachine.lua index 46716336..83bd38fb 100644 --- a/src/ui/gen2/SlotMachine.lua +++ b/src/ui/gen2/SlotMachine.lua @@ -219,10 +219,16 @@ local TWO_LINES = { function SlotMachine.matchFirstTwo(bet, r1, r2) local building = SlotMachine.NO_MATCH + local matchingSevens = false for _, line in ipairs(TWO_LINES[(bet or 0) % 4] or {}) do - if r1[line[1]] == r2[line[2]] then building = r1[line[1]] end + if r1[line[1]] == r2[line[2]] then + building = r1[line[1]] + if building == SlotMachine.SEVEN then + matchingSevens = true + end + end end - return building, building == SlotMachine.SEVEN + return building, matchingSevens end -- ------------------------------------------------------------------- bias @@ -403,13 +409,14 @@ end -- matching SEVENs on a line this bet buys. function SlotMachine.spinReel2ToSevens(position, bet, stopped1) local strip = SlotMachine.REELS[2] + local pos = SlotMachine.advance(position) for _ = 1, SEARCH_LIMIT do - local window = { SlotMachine.window(strip, position) } + local window = { SlotMachine.window(strip, pos) } local building, sevens = SlotMachine.matchFirstTwo(bet, stopped1, window) - if building ~= SlotMachine.NO_MATCH and sevens then return position end - position = SlotMachine.advance(position) + if building ~= SlotMachine.NO_MATCH and sevens then return pos end + pos = SlotMachine.advance(pos) end - return position + return pos end -- ------------------------------------------------------- reel 3's theatre @@ -580,8 +587,8 @@ end -- ------------------------------------------------------------------ layout local COINS_X, COINS_Y = 5, 1 local PAYOUT_X, PAYOUT_Y = 11, 1 --- REEL_X_COORD / TILE_WIDTH. -local REEL_X = { 6, 10, 14 } +-- REEL_X_COORD / TILE_WIDTH (5, 9, 13) for the 3 reel apertures (columns 5-6, 9-10, 13-14) +local REEL_X = { 5, 9, 13 } -- Slots_UpdateReelPositionAndOAM's y ladder, converted from OAM space (which -- sits 16px above the screen) to tile rows: bottom, middle, top, and the -- fourth symbol that only half shows. @@ -617,7 +624,7 @@ SlotMachine.TEXTS = TEXTS -- _SlotsLinedUpText: "lined up!" / "Won @ coins!", with the -- matched symbol's 2x2 tiles printed to its left by .Text_PrintPayout. local function linedUpLines(payout) - return { "lined up!", ("Won %d coins!"):format(payout) } + return { " lined up!", ("Won %d coins!"):format(payout) } end -- Slots_PlaySFX's labels, spelled the pokegold way (Sound.GEN2_ALIASES is what @@ -729,30 +736,41 @@ function SlotMachine:startSpin() self.reel = 1 self.delay = 32 self.message = TEXTS.start + self.stopped = nil + self.matched = SlotMachine.NO_MATCH + self.matchingSevens = false + self.reel3Action = nil + self.golemAnim = nil + self.chanseyAnim = nil + self.reel2Pause = nil for i = 1, 3 do self.rate[i] = 4 -- ReelAction_NormalRate self.stops[i] = nil + self.distance[i] = 0 end self:sfx(SFX_START) end -- Slots_SpinReel, per reel per frame: the action jumptable runs only on a slot --- boundary, and the position advances when the distance's low nibble wraps. +-- boundary, and the position advances whenever 16 pixels are traversed. function SlotMachine:spinReels() for i = 1, 3 do local rate = self.rate[i] if rate > 0 then - self.distance[i] = (self.distance[i] + rate) % 256 - if self.distance[i] % 16 == 0 then + self.distance[i] = (self.distance[i] or 0) + rate + while self.distance[i] >= 16 do + self.distance[i] = self.distance[i] - 16 self.positions[i] = SlotMachine.advance(self.positions[i]) -- A reel with a resting slot chosen stops the moment it reaches it. if self.stops[i] and self.positions[i] == self.stops[i] then self.rate[i] = 0 + self.distance[i] = 0 self.stopped = self.stopped or {} self.stopped[i] = { SlotMachine.window(SlotMachine.REELS[i], self.positions[i]) } self:sfx(SFX_STOP) self:reelStopped(i) + break end end end @@ -775,11 +793,17 @@ function SlotMachine:pressStop() self.stops[1] = SlotMachine.stopReel1(here, self.bias) elseif i == 2 then local r1 = self.stopped[1] - if SlotMachine.reel2SkipsToSeven(self.bet, self.bias, r1, self.random) then + local hereWindow = { SlotMachine.window(SlotMachine.REELS[2], here) } + local _, hereSevens = SlotMachine.matchFirstTwo(self.bet, r1, hereWindow) + local doSkip = SlotMachine.reel2SkipsToSeven(self.bet, self.bias, r1, self.random) + if hereSevens and not doSkip then + self.stops[2] = here + elseif doSkip then -- ReelAction_SetUpReel2SkipTo7 pauses the reel for 32 frames and then -- fast-spins it at double rate; the pause is the tell. self.stops[2] = SlotMachine.spinReel2ToSevens(here, self.bet, r1) - self.rate[2] = 8 + self.reel2Pause = 32 + self.rate[2] = 0 -- paused during the 32-frame tell else self.stops[2] = SlotMachine.stopReel2(here, self.bias, self.bet, r1) end @@ -789,32 +813,54 @@ function SlotMachine:pressStop() self.matchingSevens = sevens local action = SlotMachine.reel3Action(sevens, self.bias, self.random) self.reel3Action = action - if action == SlotMachine.REEL3_STOP then + if action == SlotMachine.REEL3_STOP or action == "stop" then self.stops[3] = SlotMachine.stopReel3(here, self.bias, self.bet, r1, r2) - elseif action == SlotMachine.REEL3_SLOW then + elseif action == SlotMachine.REEL3_SLOW or action == "slowAdvance" then self.stops[3] = SlotMachine.slowAdvance(here, self.bias, self.bet, r1, r2) - self.rate[3] = 1 -- ReelAction_QuarterRate - elseif action == SlotMachine.REEL3_GOLEM then + self.rate[3] = 1 -- ReelAction_QuarterRate slow crawl + elseif action == SlotMachine.REEL3_GOLEM or action == "golem" then local count = SlotMachine.golemCount(here, self.bias, self.bet, r1, r2, self.random) + if count == 0 then count = 3 end local target = here for _ = 1, count do target = SlotMachine.advance(target) end self.stops[3] = target self.golems = count - self.rate[3] = 8 + self.golemAnim = { + count = count, + state = "falling", + var1 = 48, + x = 100, + y = 44 - 112, + animFrame = 0, + animTimer = 0, + } + self.rate[3] = 0 -- reel 3 stepped by each golem impact else local target = SlotMachine.eggDrops(here, self.bet, r1, r2) self.stops[3] = target - self.rate[3] = 16 -- ReelAction_QuadrupleRate, the egg drop + self.chanseyAnim = { + state = "walking", + xcoord = 0, + x = -24, + y = 44, + animTimer = 0, + animPose = 0, + } + self.rate[3] = 0 -- paused until Chansey drops egg end end -- A reel already sitting on its resting slot has nowhere to turn. - if self.stops[i] == self.positions[i] then - self.rate[i] = 0 - self.stopped = self.stopped or {} - self.stopped[i] = self:reelWindow(i) - self:sfx(SFX_STOP) - self:reelStopped(i) + -- Only trigger immediate halt if no active pause tell or special animation is running. + if not self.reel2Pause and not self.golemAnim and not self.chanseyAnim then + if self.stops[i] == self.positions[i] then + self.rate[i] = 0 + self.distance[i] = 0 + self.stopped = self.stopped or {} + self.stopped[i] = self:reelWindow(i) + self:sfx(SFX_STOP) + self:reelStopped(i) + end end end @@ -823,6 +869,9 @@ function SlotMachine:reelStopped(i) self.reel = i + 1 return end + self.golemAnim = nil + self.chanseyAnim = nil + self.reel2Pause = nil -- SlotsAction_FlashIfWin: a win flashes the object palette for 16 frames -- before the payout is counted out; a loss skips straight past it. local r1, r2, r3 = self.stopped[1], self.stopped[2], self.stopped[3] @@ -906,13 +955,134 @@ function SlotMachine:update(_dt) if phase == "spinning" then -- SlotsAction_WaitStart clears hJoypadSum first, so a press held from the -- bet menu cannot stop reel one. - if self.delay > 0 then + if (self.delay or 0) > 0 then self.delay = self.delay - 1 self:spinReels() return end + if self.reel2Pause and self.reel2Pause > 0 then + self.reel2Pause = self.reel2Pause - 1 + if self.reel2Pause <= 0 then + self.reel2Pause = nil + self.rate[2] = 8 -- resume fast-spin after the 32-frame tell + end + end + + if self.golemAnim then + local g = self.golemAnim + -- Cycle animation frames every 8 ticks (~7.5 fps) matching the original pacing + g.animTimer = (g.animTimer or 0) + 1 + if g.animTimer >= 8 then + g.animTimer = 0 + g.animFrame = ((g.animFrame or 0) + 1) % 4 + end + + if g.state == "falling" then + if g.var1 > 32 then + g.var1 = g.var1 - 1 + local angle = (g.var1 * math.pi) / 32 + local yOffset = math.floor(112 * math.sin(angle) + 0.5) + g.y = 44 + yOffset + g.x = 100 + else + -- Landed on Reel 3! + g.y = 44 + g.x = 100 + g.state = "rolling" + g.xoffset = 0 + g.animTimer = 0 + g.animFrame = 0 + self:sfx("Sfx_PlacePuzzlePieceDown") + -- Advance reel 3 by 1 slot per Golem impact + self.positions[3] = SlotMachine.advance(self.positions[3]) + self.distance[3] = 0 + end + elseif g.state == "rolling" then + g.xoffset = (g.xoffset or 0) + 1 + g.x = 100 - g.xoffset + + if g.xoffset >= 88 then + -- Rolled past reel 1 (100 - 88 = 12px) off the screen -> restart or end + g.count = g.count - 1 + if g.count > 0 then + g.state = "falling" + g.var1 = 48 + g.x = 100 + g.y = 44 - 112 + else + -- All Golems finished; halt reel 3 at target + self.golemAnim = nil + self.rate[3] = 0 + self.distance[3] = 0 + self.stopped = self.stopped or {} + self.stopped[3] = self:reelWindow(3) + self:sfx(SFX_STOP) + self:reelStopped(3) + end + end + end + end + + if self.chanseyAnim then + local c = self.chanseyAnim + if c.state == "walking" then + c.xcoord = (c.xcoord or 0) + 1 + c.x = c.xcoord - 24 + c.y = 44 + + -- Cycle walking poses 0->1->2->3 (maps to Chansey 1->2->3->4) every 6 frames + c.animTimer = (c.animTimer or 0) + 1 + if c.animTimer >= 6 then + c.animTimer = 0 + c.animPose = ((c.animPose or 0) + 1) % 4 + end + + if c.xcoord % 16 == 0 then self:sfx("Sfx_JumpOverLedge") end + + if c.x >= 88 then + -- Reached reel 3! Switch to tell pause (pose 4 = arm raised) + c.x = 88 + c.state = "egg_pause" + c.delay = 14 + c.animPose = 3 + end + elseif c.state == "egg_pause" then + c.delay = (c.delay or 14) - 1 + if c.delay <= 0 then + -- Switch to Chansey 5 (egg drop pose) and spawn egg + c.animPose = 4 + c.state = "egg_drop" + c.eggTimer = 0 + c.eggStartX = c.x + 14 + c.eggStartY = c.y + 8 + c.eggX = c.eggStartX + c.eggY = c.eggStartY + c.eggVisible = true + self:sfx("Sfx_Present") + end + elseif c.state == "egg_drop" then + c.eggTimer = (c.eggTimer or 0) + 1 + local t = c.eggTimer / 16 + if t > 1 then t = 1 end + c.eggX = c.eggStartX + t * (108 - c.eggStartX) + c.eggY = c.eggStartY + t * (56 - c.eggStartY) - math.sin(t * math.pi) * 8 + + if c.eggTimer >= 16 then + -- Egg landed on Reel 3! + c.eggVisible = false + c.state = "spinning" + self:sfx("Sfx_PlacePuzzlePieceDown") + self.rate[3] = 16 -- fast drop reel 3 to jackpot + end + end + end + self.message = nil - if input:wasPressed("a") then self:pressStop() end + if input:wasPressed("a") then + if not self.stops[self.reel] and not self.reel2Pause and not self.golemAnim and not self.chanseyAnim then + self:pressStop() + end + end self:spinReels() return end @@ -959,57 +1129,302 @@ end -- ------------------------------------------------------------------- draw -- --- The cart's reel art is unextracted (see the header), so a symbol draws as its --- two-letter label inside a 2x2 cell. A `slots` entry in menu_gfx.lua switches --- this to the real tiles without any other change. -function SlotMachine:sheet() - if self.sheetCache == nil then - local data = self.game and self.game.data - local gfx = data and data.gen2MenuGfx and data.gen2MenuGfx.slots - if gfx and gfx.image then - local TileSheet = require("src.ui.gen2.TileSheet") - self.sheetCache = TileSheet.new({ path = gfx.image, wide = gfx.wide or 16, - firstTile = gfx.firstTile or 0 }) +-- Authentic Color Game Boy palettes and tile graphics matching pret/pokegold +local TileSheet = require("src.ui.gen2.TileSheet") +local GbcPalette = require("src.render.GbcPalette") + +local GBC_PALS = { + bg = { + [0] = { { 255, 255, 255 }, { 198, 206, 231 }, { 198, 198, 74 }, { 0, 0, 0 } }, -- 0: Base Frame + [1] = { { 255, 255, 255 }, { 247, 82, 49 }, { 198, 198, 74 }, { 0, 0, 0 } }, -- 1: Vileplume / Active Lights + [2] = { { 255, 255, 255 }, { 123, 255, 0 }, { 198, 198, 74 }, { 0, 0, 0 } }, -- 2: Bet 3 Indicators + [3] = { { 255, 255, 255 }, { 255, 123, 255 }, { 198, 198, 74 }, { 0, 0, 0 } }, -- 3: Bet 2 Indicators + [4] = { { 255, 255, 255 }, { 123, 173, 255 }, { 198, 198, 74 }, { 0, 0, 0 } }, -- 4: Bet 1 Indicators + [5] = { { 255, 255, 90 }, { 255, 255, 49 }, { 198, 198, 74 }, { 0, 0, 0 } }, -- 5: Yellow Highlights + [6] = { { 255, 255, 255 }, { 132, 156, 239 }, { 206, 181, 0 }, { 0, 0, 0 } }, -- 6: Textbox frame + [7] = { { 255, 255, 255 }, { 173, 173, 173 }, { 107, 107, 107 }, { 0, 0, 0 } }, -- 7: Inactive / Gray + }, + obj = { + [0] = { { 255, 255, 255 }, { 247, 82, 49 }, { 255, 0, 0 }, { 0, 0, 0 } }, -- 0: Seven (Red) + [1] = { { 255, 255, 255 }, { 99, 206, 8 }, { 41, 115, 0 }, { 0, 0, 0 } }, -- 1: Pokeball (Green/Red) + [2] = { { 255, 255, 255 }, { 99, 206, 8 }, { 247, 82, 49 }, { 0, 0, 0 } }, -- 2: Cherry + [3] = { { 255, 255, 255 }, { 255, 255, 49 }, { 165, 123, 24 }, { 0, 0, 0 } }, -- 3: Pikachu (Yellow) + [4] = { { 255, 255, 255 }, { 255, 255, 49 }, { 123, 173, 255 }, { 0, 0, 0 } }, -- 4: Squirtle (Blue/Yellow) + [5] = { { 255, 255, 255 }, { 255, 255, 49 }, { 165, 123, 24 }, { 0, 0, 0 } }, -- 5: Staryu / Golem (Rock) + [6] = { { 255, 255, 255 }, { 255, 198, 173 }, { 255, 107, 255 }, { 0, 0, 0 } }, -- 6: Chansey (Pink) + [7] = { { 255, 255, 255 }, { 255, 255, 255 }, { 0, 0, 0 }, { 0, 0, 0 } }, -- 7: Flashing + } +} + +local TILEMAP = nil +local function getTilemap() + if TILEMAP == nil then + local path = "assets/generated/slots/gold_slots.tilemap" + local f = io.open(path, "rb") + if f then + local data = f:read("*a") + f:close() + TILEMAP = {} + for i = 1, #data do + TILEMAP[i] = string.byte(data, i) + end else - self.sheetCache = false + TILEMAP = false end end - return self.sheetCache or nil + return TILEMAP or nil end -local function cell(tx, ty, label) - local G = love.graphics - G.setColor(0, 0, 0, 1) - G.rectangle("line", tx * 8, ty * 8, 16, 16) - Chrome.print(label, tx, ty + 1) +function SlotMachine:sheets() + if self.sheet1 == nil then + self.sheet1 = TileSheet.new({ path = "assets/generated/slots/gold_slots_1.png", wide = 2, firstTile = 0 }) + self.sheet2 = TileSheet.new({ path = "assets/generated/slots/gold_slots_2.png", wide = 2, firstTile = 0 }) + self.sheet3 = TileSheet.new({ path = "assets/generated/slots/gold_slots_3.png", wide = 3, firstTile = 0 }) + end + return self.sheet1, self.sheet2, self.sheet3 +end + +function SlotMachine:drawBackground() + local s1, s2 = self:sheets() + local tm = getTilemap() + if not tm or not s1:available() then + -- Fallback simple background if assets unavailable + Chrome.clear() + return + end + + local bet = self.bet or 0 + + for ty = 0, 11 do + for tx = 0, 19 do + local idx = ty * 20 + tx + 1 + local tileId = tm[idx] + + -- Palette attribution matching _CGB_SlotMachine + local pal = 0 + if (tx <= 2 or tx >= 17) and ty >= 2 and ty <= 11 then + if ty >= 6 and ty <= 7 then pal = 4 + elseif ty >= 4 and ty <= 9 then pal = 3 + else pal = 2 end + elseif tx >= 4 and tx <= 15 and ty >= 2 and ty <= 3 then + pal = 1 -- Vileplume + elseif (tx == 3 or tx == 16) and ty >= 2 and ty <= 11 then + local isLit = false + if ty == 6 or ty == 7 then isLit = (bet >= 1) + elseif ty == 4 or ty == 5 or ty == 8 or ty == 9 then isLit = (bet >= 2) + elseif ty == 2 or ty == 3 or ty == 10 or ty == 11 then isLit = (bet >= 3) + end + if isLit then + pal = 1 + -- Use lit lights tile + if tileId == 0x23 then tileId = 0x14 + elseif tileId == 0x24 then tileId = 0x15 end + else + pal = 0 + end + end + + local colors = GBC_PALS.bg[pal] + s1.palette = colors + s2.palette = colors + + if tileId < 0x25 then + s1:draw(tileId, tx, ty) + else + s2:draw(tileId - 0x25, tx, ty) + end + end + end end function SlotMachine:drawReels() + local _, s2 = self:sheets() + local G = love.graphics + for i = 1, 3 do local strip = SlotMachine.REELS[i] - local position = self.positions[i] - -- .LoadOAM reads FOUR consecutive strip entries from REEL_POSITION and lays - -- them bottom upward, which is what the three repeated entries at the end - -- of each strip are for: position 14 reads indices 14, 15, 16 and 17 - -- without wrapping. Only the lower three are on a pay line; the fourth - -- half-shows at the top of the window. - for row = 1, 4 do - local symbol = strip[position + row] - cell(REEL_X[i], REEL_ROW[row], SlotMachine.LABELS[symbol] or "?") + local pos = self.positions[i] + local a = pos + if a == 0 then a = 0x0f end + a = (a - 1) % 16 + local rx = REEL_X[i] * 8 + local dy = math.floor(self.distance[i] or 0) + + -- Draw 4 consecutive 2x2 symbols from bottom to top, exactly matching SlotMachine.window + for row = 0, 3 do + local sym = strip[a + row + 1] + local py = 64 - (row * 16) + dy + local pal = GBC_PALS.obj[math.floor(sym / 4)] or GBC_PALS.obj[0] + s2.palette = pal + + -- 2x2 tiles in 2-wide sheet: + -- sym + 0 = top-left (col 0, row 0) + -- sym + 1 = top-right (col 1, row 0) + -- sym + 2 = bottom-left (col 0, row 1) + -- sym + 3 = bottom-right (col 1, row 1) + local t0 = s2:quad(sym + 0) + local t1 = s2:quad(sym + 1) + local t2 = s2:quad(sym + 2) + local t3 = s2:quad(sym + 3) + local img = s2:image() + + if img and t0 and t1 and t2 and t3 then + local function drawSym() + G.draw(img, t0, rx, py) + G.draw(img, t1, rx + 8, py) + G.draw(img, t2, rx, py + 8) + G.draw(img, t3, rx + 8, py + 8) + end + if GbcPalette.available() then + GbcPalette.with(pal, drawSym) + else + drawSym() + end + else + -- Fallback label + cell(REEL_X[i], REEL_ROW[row + 1], SlotMachine.LABELS[sym] or "?") + end end end end -function SlotMachine:drawLights() - local lit = {} - -- Slots_IlluminateBetLights lights the rows for THIS bet and every smaller - -- one: `dec a / jr z` falls through from three to two to one. - for bet = 1, (self.bet or 0) do - for _, row in ipairs(LIGHT_ROWS[bet] or {}) do lit[row] = true end +function SlotMachine:actorsImage() + if self.actorsLoaded == nil then + local Assets = require("src.render.Assets") + local ok, img = pcall(Assets.image, "assets/generated/slots/gold_slots_actors.png") + if not (ok and img) then + ok, img = pcall(Assets.image, "assets/generated/slots/gold_slots_3.png") + end + self.actorsLoaded = (ok and img) or false + if self.actorsLoaded then + local G = love.graphics + -- 24x240 sheet: + -- Y=0: Golem 1 (Standing, 24x32) + -- Y=32: Golem 2 (Ball, 24x32) + -- Y=64: Chansey 1 (Standing / Step 1, 24x32) + -- Y=96: Chansey 2 (Step 2, 24x32) + -- Y=128: Chansey 3 (Step 3, 24x32) + -- Y=160: Chansey 4 (Arm raised / Step 4, 24x32) + -- Y=192: Chansey 5 (Egg Drop pose, 24x32) + -- Y=224: Egg (8x16 at X=0) + self.quadGolemStand = G.newQuad(0, 0, 24, 32, 24, 240) + self.quadGolemBall = G.newQuad(0, 32, 24, 32, 24, 240) + self.quadChansey1 = G.newQuad(0, 64, 24, 32, 24, 240) + self.quadChansey2 = G.newQuad(0, 96, 24, 32, 24, 240) + self.quadChansey3 = G.newQuad(0, 128, 24, 32, 24, 240) + self.quadChansey4 = G.newQuad(0, 160, 24, 32, 24, 240) + self.quadChanseyDrop = G.newQuad(0, 192, 24, 32, 24, 240) + self.quadEgg = G.newQuad(0, 224, 8, 16, 24, 240) + end end - for _, row in ipairs({ 2, 4, 6, 8, 10 }) do - for _, col in ipairs(LIGHT_COLS) do - Chrome.print(lit[row] and "*" or "-", col, row) + return self.actorsLoaded or nil +end + +-- Redraw the top Vileplume row (rows 2..3) and bottom frame brackets (rows 10..11) +-- over the reels with solid backdrop to naturally mask any sprite overhang like the Game Boy hardware does. +function SlotMachine:drawOverlays() + local s1, s2 = self:sheets() + local tm = getTilemap() + if not tm or not s1:available() then return end + + local G = love.graphics + -- Solid backdrop over header (rows 0..3) and footer (rows 10..11) between columns 4..15 + G.setColor(198 / 255, 198 / 255, 74 / 255, 1) + G.rectangle("fill", 4 * 8, 2 * 8, 12 * 8, 2 * 8) + G.rectangle("fill", 4 * 8, 10 * 8, 12 * 8, 2 * 8) + G.setColor(1, 1, 1, 1) + + for _, ty in ipairs({ 2, 3, 10, 11 }) do + for tx = 4, 15 do + local idx = ty * 20 + tx + 1 + local tileId = tm[idx] + local pal = (ty <= 3) and 1 or 0 + local colors = GBC_PALS.bg[pal] + s1.palette = colors + s2.palette = colors + + if tileId < 0x25 then + s1:draw(tileId, tx, ty) + else + s2:draw(tileId - 0x25, tx, ty) + end + end + end + + -- Draw Golem sprite animation + if self.golemAnim then + local actors = self:actorsImage() + local g = self.golemAnim + if actors then + local quad = self.quadGolemBall + local scaleX = 1 + local scaleY = 1 + if g.state == "falling" then + quad = self.quadGolemBall + elseif g.state == "rolling" then + -- Frameset_SlotsGolem: 0=Standing, 1=Ball, 2=StandingYFlip, 3=BallXFlip + local rotFrame = (g.animFrame or 0) % 4 + if rotFrame == 0 then + quad = self.quadGolemStand + scaleX = 1 + scaleY = 1 + elseif rotFrame == 1 then + quad = self.quadGolemBall + scaleX = 1 + scaleY = 1 + elseif rotFrame == 2 then + quad = self.quadGolemStand + scaleX = 1 + scaleY = -1 + elseif rotFrame == 3 then + quad = self.quadGolemBall + scaleX = -1 + scaleY = 1 + end + end + G.setColor(1, 1, 1, 1) + local function drawGolem() + -- Draw rotated around center (ox=12, oy=16) + G.draw(actors, quad, + math.floor(g.x + 12), + math.floor(g.y + 16), + 0, scaleX, scaleY, 12, 16) + end + if GbcPalette.available() then + GbcPalette.with(GBC_PALS.obj[5], drawGolem) + else + drawGolem() + end + end + end + + -- Draw Chansey & Egg sprite animation + if self.chanseyAnim then + local actors = self:actorsImage() + local c = self.chanseyAnim + if actors then + local quad = self.quadChansey1 + if c.state == "walking" then + local walkCycle = { self.quadChansey1, self.quadChansey2, self.quadChansey3, self.quadChansey4 } + quad = walkCycle[((c.animPose or 0) % 4) + 1] or self.quadChansey1 + elseif c.state == "egg_pause" then + quad = self.quadChansey4 + elseif c.state == "egg_drop" or c.state == "spinning" then + quad = self.quadChanseyDrop + end + + G.setColor(1, 1, 1, 1) + local function drawChansey() + G.draw(actors, quad, math.floor(c.x), math.floor(c.y or 44)) + if c.eggVisible and c.eggX and c.eggY then + G.draw(actors, self.quadEgg, math.floor(c.eggX), math.floor(c.eggY)) + end + end + if GbcPalette.available() then + GbcPalette.with(GBC_PALS.obj[6], drawChansey) + else + drawChansey() + end end end end @@ -1022,39 +1437,79 @@ function SlotMachine:drawMessage() end if self.matched and self.matched ~= SlotMachine.NO_MATCH and self.phase == "payoutText" then - cell(PAYOUT_SYMBOL_X, PAYOUT_SYMBOL_Y, - SlotMachine.LABELS[self.matched] or "?") + local _, s2 = self:sheets() + local sym = self.matched + local pal = GBC_PALS.obj[math.floor(sym / 4)] or GBC_PALS.obj[0] + s2.palette = pal + local t0 = s2:quad(sym + 0) + local t1 = s2:quad(sym + 1) + local t2 = s2:quad(sym + 2) + local t3 = s2:quad(sym + 3) + local img = s2:image() + local G = love.graphics + local px, py = PAYOUT_SYMBOL_X * 8, PAYOUT_SYMBOL_Y * 8 + if img and t0 and t1 and t2 and t3 then + local function drawWin() + G.setColor(1, 1, 1, 1) + G.draw(img, t0, px, py) + G.draw(img, t1, px + 8, py) + G.draw(img, t2, px, py + 8) + G.draw(img, t3, px + 8, py + 8) + end + G.setColor(1, 1, 1, 1) + if GbcPalette.available() then + GbcPalette.with(pal, drawWin) + else + drawWin() + end + else + cell(PAYOUT_SYMBOL_X, PAYOUT_SYMBOL_Y, SlotMachine.LABELS[self.matched] or "?") + end end end function SlotMachine:drawPanel() - Chrome.clear() - self:drawLights() + self:drawBackground() + self:drawReels() + self:drawOverlays() + -- PRINTNUM_LEADINGZEROS | 2 bytes, 4 digits, for both counters. Chrome.print(Chrome.number(self:coins(), 4, true), COINS_X, COINS_Y) Chrome.print(Chrome.number(self.payoutLeft or 0, 4, true), PAYOUT_X, PAYOUT_Y) - self:drawReels() + if self.phase == "bet" then - Chrome.textbox(BET_BOX_X, BET_BOX_Y, BET_BOX_W - 2, BET_BOX_H - 2) + -- Left speech textbox for "Bet how many coins?" + Chrome.textbox(0, 12, 12, 4) + Chrome.print(TEXTS.betHowMany[1], 1, 14) + Chrome.print(TEXTS.betHowMany[2], 1, 16) + + -- Right menu for bet choices (14, 10 to 19, 17) + Chrome.textbox(14, 10, 4, 6) for i, label in ipairs(BET_ROWS) do - local ty = BET_LABEL_Y + (i - 1) * BET_SPACING - if i == self.betIndex then Chrome.cursor(BET_LABEL_X - 1, ty) end - Chrome.print(label, BET_LABEL_X, ty) + local ty = 12 + (i - 1) * 2 + if i == self.betIndex then Chrome.cursor(15, ty) end + Chrome.print(label, 16, ty) end - if not self.message then - Chrome.textbox(TEXT_BOX_X, TEXT_BOX_Y, TEXT_BOX_W - 2, TEXT_BOX_H - 2) - for i, line in ipairs(TEXTS.betHowMany) do + + if self.message then + -- If "Not enough coins." message is shown, overlay full speech box + Chrome.textbox(0, 12, 18, 4) + for i, line in ipairs(self.message) do Chrome.print(line, TEXT_X, TEXT_Y + (i - 1) * TEXT_LINE) end end - end - self:drawMessage() - if self.phase == "again" then - -- PlaceYesNoBox `lb bc, 14, 12`: a 6x5 box at (14,12) with YES at (16,13). + elseif self.phase == "again" then + -- Speech box: "Play again?" + Chrome.textbox(0, 12, 18, 4) + Chrome.print(TEXTS.playAgain[1], TEXT_X, TEXT_Y) + + -- PlaceYesNoBox at (14, 12): 6x5 box with YES at (16,13), NO at (16,15) Chrome.textbox(14, 12, 4, 3) Chrome.print("YES", 16, 13) Chrome.print("NO", 16, 15) Chrome.cursor(15, 13 + (self.againChoice - 1) * 2) + else + self:drawMessage() end end diff --git a/src/ui/gen2/TitleState.lua b/src/ui/gen2/TitleState.lua index fe8f1cd7..32b36d69 100644 --- a/src/ui/gen2/TitleState.lua +++ b/src/ui/gen2/TitleState.lua @@ -2,6 +2,7 @@ -- wing-flap (Frameset_GSIntroHoOhLugia), spark trails, A/Start to continue. -- drawWidescreen fills the window with sky/clouds so widescreen has no -- pillarbox voids; the 160x144 art stays aspect-centered on top. +-- Every Gold/Silver difference arrives as a title.lua key, defaulted to Gold. -- src/render/Assets.lua is the mod-override choke point: a raw -- love.graphics.newImage skips overrides/ and AssetTransform output. @@ -16,7 +17,7 @@ local TitleState = {} TitleState.__index = TitleState TitleState.isOpaque = true --- title_bg_gold.pal mid-sky shade (sampled from composed title_screen.png). +-- title_bg_gold.pal mid-sky shade, for a cache built before title.sky existed. local SKY = { 123 / 255, 165 / 255, 255 / 255, 1 } -- ...and its grey stand-in, for when COLOR is not GBC. The title art is the -- one thing in the port baked with its colours in (see the extractor), so the @@ -61,6 +62,15 @@ function TitleState.new(game, opts) self.hoohY = tonumber(title.hoohY) or 56 self.cloudY = tonumber(title.cloudY) or 88 self.cloudScrollEvery = tonumber(title.cloudScrollEvery) or 8 + -- AnimSeq_GSIntroHoOhLugia (engine/sprite_anims/functions.asm:820-838). + self.hoohBobAmplitude = tonumber(title.hoohBobAmplitude) or 2 + self.hoohBobStep = tonumber(title.hoohBobStep) or 1 + local sky = title.sky + self.sky = (type(sky) == "table" and #sky >= 3) + and { sky[1], sky[2], sky[3], 1 } or SKY + local below = title.below + self.below = (type(below) == "table" and #below >= 3) + and { below[1], below[2], below[3], 1 } or { 1, 1, 1, 1 } self.hoohColor, self.hoohGray = {}, {} local paths = title.hoohFrames @@ -80,8 +90,12 @@ function TitleState.new(game, opts) self.sequence = title.hoohSequence or { { 1, 10 }, { 2, 9 }, { 3, 10 }, { 4, 10 }, { 3, 9 }, { 5, 10 }, } + -- A frame shows duration + 1 ticks: GetSpriteAnimFrame stores the byte on + -- the advancing tick and only decrements on the ones after + -- (engine/sprite_anims/core.asm:400-434). Both editions' framesets total + -- 64 ticks with it, locking the wing beat to the 64-tick sine bob. self.seqIndex = 1 - self.seqLeft = self.sequence[1] and self.sequence[1][2] or 10 + self.seqLeft = (self.sequence[1] and self.sequence[1][2] or 10) + 1 self.frame = 1 -- AnimSeq_GSIntroHoOhLugia's SPRITEANIMSTRUCT_VAR1. @@ -89,12 +103,21 @@ function TitleState.new(game, opts) self.frameCounter = 0 self.cloudScroll = 0 self.trails = {} - -- UpdateTitleTrailSprite / TitleTrailCoords (intro_menu.asm), in pixels. - self.trailSpawns = { + -- UpdateTitleTrailSprite / TitleTrailCoords (intro_menu.asm:1069-1124), in + -- pixels. + self.trailSpawns = title.trailSpawns or { { 80, 88 }, { 104, 88 }, { 104, 88 }, { 120, 88 }, { 120, 88 }, { 88, 88 }, } self.trailSpawnIndex = 1 + -- AnimSeq_GSTitleTrail (engine/sprite_anims/functions.asm:720-818). + self.trailMode = title.trailMode or "gold" + self.trailSpawnEvery = tonumber(title.trailSpawnEvery) or 4 + self.trailStepX = tonumber(title.trailStepX) or 4 + self.trailStepY = tonumber(title.trailStepY) or 1 + self.trailBobAmplitude = tonumber(title.trailBobAmplitude) or 2 + self.trailPhaseStep = tonumber(title.trailPhaseStep) or 3 + self.trailPhase = tonumber(title.trailPhase) -- How far past the 160px frame trails may fly (GB pixels); set each draw. self.trailMaxX = 200 self.musicStarted = false @@ -119,47 +142,56 @@ function TitleState:enter() end end --- AnimSeq_GSIntroHoOhLugia (engine/sprite_anims/functions.asm): VAR1 counts up --- one per frame and the struct's Y offset becomes `d * sin(VAR1 * pi/32)` with --- d = 2 on Gold (Silver counts DOWN with d = 8). Sprites_Sine hands back the --- byte the ASM leaves in a, so the down half of the wave arrives in two's --- complement and has to be read as a signed pixel delta here. -function TitleState:hoohBob() - local value = SpriteAnims.sine(self.hoohPhase, 2) - if value >= 0x80 then value = value - 0x100 end +-- Sprites_Sine hands back the byte the ASM leaves in a, so the down half of +-- the wave arrives in two's complement and is a signed pixel delta here. +local function signed(value) + if value >= 0x80 then return value - 0x100 end return value end +-- AnimSeq_GSIntroHoOhLugia (engine/sprite_anims/functions.asm:820-838). +function TitleState:hoohBob() + return signed(SpriteAnims.sine(self.hoohPhase, self.hoohBobAmplitude)) +end + function TitleState:advanceHooh() - self.hoohPhase = (self.hoohPhase + 1) % 256 + self.hoohPhase = (self.hoohPhase + self.hoohBobStep) % 256 self.seqLeft = self.seqLeft - 1 if self.seqLeft > 0 then return end self.seqIndex = self.seqIndex + 1 if self.seqIndex > #self.sequence then self.seqIndex = 1 end local step = self.sequence[self.seqIndex] self.frame = step[1] - self.seqLeft = step[2] + self.seqLeft = step[2] + 1 end function TitleState:spawnTrail() if not (self.trailColor or self.trailGray) then return end - if self.frameCounter % 4 ~= 0 then return end + if #self.trailSpawns == 0 then return end + if self.frameCounter % self.trailSpawnEvery ~= 0 then return end local spawn = self.trailSpawns[self.trailSpawnIndex] self.trailSpawnIndex = self.trailSpawnIndex % #self.trailSpawns + 1 if not spawn then return end self.trails[#self.trails + 1] = { - x = spawn[1], y = spawn[2], phase = love.math.random(0, 255), + x = spawn[1], y = spawn[2], + phase = self.trailPhase or love.math.random(0, 255), } end function TitleState:stepTrails() local alive = {} local maxX = self.trailMaxX or 200 + local silver = self.trailMode == "silver" for _, t in ipairs(self.trails) do - t.x = t.x + 4 - t.y = t.y + 1 - t.phase = t.phase + 3 - t.drawY = t.y + math.floor(math.sin(t.phase / 16) * 2) + t.x = t.x + self.trailStepX + t.y = t.y + self.trailStepY + t.phase = t.phase + self.trailPhaseStep + if silver then + t.drawY = t.y + signed(SpriteAnims.sine(t.phase, self.trailBobAmplitude)) + else + t.drawY = t.y + + math.floor(math.sin(t.phase / 16) * self.trailBobAmplitude) + end if t.x < maxX then alive[#alive + 1] = t end end self.trails = alive @@ -254,14 +286,17 @@ function TitleState:drawWidescreen(winW, winH) -- Let trails fly into the side bands. self.trailMaxX = math.ceil((winW - ox) / scale) + 16 - -- Sky above the cloud line, paper white below : edge to edge. The fill has - -- to match whichever baked set is showing, or the surround would stay blue - -- around a grey screen. - local sky = self:gray() and SKY_GRAY or SKY + -- Sky above the cloud line, title.below under it (Gold's white cloud + -- field, Silver's black sea) : edge to edge. The fill has to match + -- whichever baked set is showing, or the surround would stay blue around a + -- grey screen. + local sky = self:gray() and SKY_GRAY or self.sky G.setColor(sky[1], sky[2], sky[3], 1) G.rectangle("fill", 0, 0, winW, math.max(0, cloudTop)) - G.setColor(1, 1, 1, 1) + local below = self.below + G.setColor(below[1], below[2], below[3], 1) G.rectangle("fill", 0, cloudTop, winW, winH - cloudTop) + G.setColor(1, 1, 1, 1) -- Clouds across the full window width, aligned to the GB cloud band. G.push() diff --git a/src/ui/kit/Theme.lua b/src/ui/kit/Theme.lua index 6773e3e0..d786b85c 100644 --- a/src/ui/kit/Theme.lua +++ b/src/ui/kit/Theme.lua @@ -64,6 +64,7 @@ local PAL = { railBlue = { 70, 150, 255 }, railGold = { 255, 203, 5 }, -- Yellow cartridge (bright) railAmber = { 218, 145, 32 }, -- Gold cartridge (deeper metal) + railSilver = { 190, 198, 210 }, -- Silver cartridge (cool light metal) } -- Semantic aliases kept so ported call sites read the same as before. PAL.cardBorder = PAL.line @@ -321,10 +322,12 @@ function Theme.meter(x, y, w, h, pct, c) end -- The 4px version rail across the top of both windows: the only brand --- colour on screen (Red / Blue / Yellow / Gold cartridge colours). +-- colour on screen (Red / Blue / Yellow / Gold / Silver cartridge colours). function Theme.versionRail(x, y, w, h) if not G then return end - local bars = { PAL.railRed, PAL.railBlue, PAL.railGold, PAL.railAmber } + local bars = { + PAL.railRed, PAL.railBlue, PAL.railGold, PAL.railAmber, PAL.railSilver, + } local seg = w / #bars for i, c in ipairs(bars) do Theme.fill(x + (i - 1) * seg, y, seg, h, c, 1) diff --git a/src/world/gen2/World.lua b/src/world/gen2/World.lua index e9ec799b..527d7d66 100644 --- a/src/world/gen2/World.lua +++ b/src/world/gen2/World.lua @@ -1569,11 +1569,11 @@ function World:readVar(varId) end -- Script_checkver: 0 for Gold, 1 for Silver (constants/misc_constants.asm --- GS_VERSION). Only the Goldenrod prize counters and a handful of gift mons --- read it, and the port has no Silver cache yet, so an unset version is Gold. +-- GS_VERSION). function World:gsVersion() + local GameVersion = require("src.core.GameVersion") local save = self.game and self.game.save - local version = (save and save.version) or "gold" + local version = (save and save.version) or GameVersion.get() return version == "silver" and 1 or 0 end @@ -6633,8 +6633,9 @@ function World:nameRival(onDone) if save then save.rival = save.rival or {} -- NameRival ends on `ld hl, wRivalName / ld de, .DefaultName / call - -- InitName`, and .DefaultName is "SILVER@" on Gold - -- (engine/events/specials.asm:80-91). The default has to be written + -- InitName`, and .DefaultName is "SILVER@" on Gold and "GOLD@" on + -- Silver (engine/events/specials.asm:80-94). The default has to be + -- written -- HERE rather than ridden in from the seed: wRivalName starts as -- InitializeNPCNames' "???" (src/core/gen2/Save.lua), which is what the -- pre-naming Cherrygrove battle prints. @@ -6646,7 +6647,7 @@ function World:nameRival(onDone) if name and name:gsub(" ", "") ~= "" then save.rival.name = name else - save.rival.name = "SILVER" + save.rival.name = self:gsVersion() == 1 and "GOLD" or "SILVER" end end if onDone then onDone(name) end @@ -10026,8 +10027,11 @@ function World:draw() G.clear(0.07, 0.05, 0.02, 1) if not self.mapImage or not self.player then - G.setColor(0.85, 0.57, 0.13, 1) - G.printf("POKEMON GOLD", 0, math.floor(h * 0.38), w, "center") + local silver = self:gsVersion() == 1 + if silver then G.setColor(0.74, 0.78, 0.83, 1) + else G.setColor(0.85, 0.57, 0.13, 1) end + G.printf(silver and "POKEMON SILVER" or "POKEMON GOLD", + 0, math.floor(h * 0.38), w, "center") G.setColor(0.92, 0.90, 0.82, 1) G.printf(self.status or "No map.", 0, math.floor(h * 0.48), w, "center") @@ -10064,6 +10068,15 @@ function World:draw() if self.shake then self.camera.y = self.camera.y + (self.shake.phase or 0) end + local ScreenPosition = require("src.core.ScreenPosition") + local posLift = 0 + if not ScreenPosition.skinActive(w, h) then + posLift = ScreenPosition.lift(h, 144 * self:fitScale(), + ScreenPosition.safeTop()) + end + if posLift > 0 then + self.camera.y = self.camera.y + posLift / s + end local override = pipelineId and self:drawPipeline(pipelineId, w, h, s) or nil @@ -10089,7 +10102,7 @@ function World:draw() local pad = POKEPIC.pad[math.floor(pw / 8)] or POKEPIC.pad[7] G.push() G.translate(math.floor((w - 160 * sPic) / 2), - math.floor((h - 144 * sPic) / 2)) + math.floor((h - 144 * sPic) / 2) - posLift) G.scale(sPic, sPic) G.setColor(1, 1, 1, 1) local function body() @@ -10140,8 +10153,10 @@ function World:draw() -- The survey overlay is a developer aid, not part of the game: POKEPORT_DEV -- (or the F3 toggle) shows it, a normal boot does not. if self.showDebugHud then - G.setColor(0.85, 0.57, 0.13, 1) - G.printf("POKEMON GOLD", 0, 10, w, "center") + local silver = self:gsVersion() == 1 + if silver then G.setColor(0.74, 0.78, 0.83, 1) + else G.setColor(0.85, 0.57, 0.13, 1) end + G.printf(silver and "POKEMON SILVER" or "POKEMON GOLD", 0, 10, w, "center") G.setColor(0.92, 0.90, 0.82, 1) local label = string.format("%s (%d,%d) %s · %s · zoom %s", self.map.id, p.cellX, p.cellY, p.facing, diff --git a/tests/engine/android_shortcuts_payload_test.lua b/tests/engine/android_shortcuts_payload_test.lua index 46da7e7c..9af54c09 100644 --- a/tests/engine/android_shortcuts_payload_test.lua +++ b/tests/engine/android_shortcuts_payload_test.lua @@ -41,12 +41,21 @@ end -- Mock isReady RomImporter.isReady = function(v) return v == "red" or v == "gold" or v == "blue" or v == "yellow" + or v == "silver" end local ok = RomImporter.syncAndroidShortcuts("gold") check(ok == true, "syncAndroidShortcuts returns true on Android") check(#capturedShortcuts == 4, "syncAndroidShortcuts caps at 4 items") check(capturedShortcuts[1] == "gold", "activeVersion 'gold' is placed first") +check(capturedShortcuts[2] == "red" and capturedShortcuts[3] == "blue" + and capturedShortcuts[4] == "yellow", + "the rest follow GameVersion.ORDER until the cap") + +capturedShortcuts = nil +RomImporter.syncAndroidShortcuts("silver") +check(#capturedShortcuts == 4, "a fifth ready game does not widen the payload") +check(capturedShortcuts[1] == "silver", "activeVersion 'silver' is placed first") -- Test with subset of ready games (e.g. only Red and Gold) RomImporter.isReady = function(v) @@ -78,4 +87,4 @@ end love.system.getLaunchGame = savedGetLaunchGame love.system.updateShortcuts = nil -print("8/8 checks passed (android_shortcuts_payload_test)") +T.finish("android_shortcuts_payload_test") diff --git a/tests/engine/gate_gen2_mod_api.lua b/tests/engine/gate_gen2_mod_api.lua index db7159ea..beabb51c 100644 --- a/tests/engine/gate_gen2_mod_api.lua +++ b/tests/engine/gate_gen2_mod_api.lua @@ -23,6 +23,7 @@ T.eq(GameVersion.generation("red"), 1, "Red is Gen 1") T.eq(GameVersion.generation("blue"), 1, "Blue is Gen 1") T.eq(GameVersion.generation("yellow"), 1, "Yellow is Gen 1") T.eq(GameVersion.generation("gold"), 2, "Gold is Gen 2") +T.eq(GameVersion.generation("silver"), 2, "Silver is Gen 2") -- ------- 2. manifest: gen2compat is opt-in and defaults off diff --git a/tests/engine/launcher_gold_touch_rows.lua b/tests/engine/launcher_gold_touch_rows.lua index 441e92b8..d54713b4 100644 --- a/tests/engine/launcher_gold_touch_rows.lua +++ b/tests/engine/launcher_gold_touch_rows.lua @@ -39,45 +39,54 @@ end local edited = 0 local hooks = { editTouchControls = function() edited = edited + 1 end } -local gold = LauncherSettings.open(hooks, "gold") -check(has(gold, "TOUCH PAD"), "Gold's gear offers TOUCH PAD") -check(has(gold, "VIBRATION"), "and VIBRATION") -check(has(gold, "TOUCH CONTROLS"), "and the layout editor") -check(has(gold, "VOID FILL"), "and VOID FILL") +for _, version in ipairs({ "gold", "silver" }) do + local model = LauncherSettings.open(hooks, version) + check(has(model, "TOUCH PAD"), version .. " gear offers TOUCH PAD") + check(has(model, "VIBRATION"), version .. " and VIBRATION") + check(has(model, "TOUCH CONTROLS"), version .. " and the layout editor") + check(has(model, "VOID FILL"), version .. " and VOID FILL") -local voidFill = findRow(gold, "VOID FILL") -eq(voidFill.value(), "FADE ", "VOID FILL defaults to FADE") -voidFill.step(1) -eq(gold.opts.gold.voidFill, "water", "right stores water in the gold block") -eq(voidFill.value(), "WATER", "and the row reads WATER") -voidFill.step(-1) -eq(gold.opts.gold.voidFill, "fade", "left restores fade") + local voidFill = findRow(model, "VOID FILL") + eq(voidFill.value(), "FADE ", version .. " VOID FILL defaults to FADE") + voidFill.step(1) + eq(model.opts.gold.voidFill, "water", + version .. " right stores water in the gold block") + eq(voidFill.value(), "WATER", version .. " and the row reads WATER") + voidFill.step(-1) + eq(model.opts.gold.voidFill, "fade", version .. " left restores fade") --- Every write has to land in the gold block: the flat keys beside it are --- Red's, and Gold's boot never reads them (src/core/gen2/Save.lua:299). --- loadOptions seeds the flat Gen 1 defaults, so the check is that the Gold --- rows leave them exactly as they found them. -local flatPad = gold.opts.touchControls -local flatBuzz = gold.opts.haptics + -- Every write has to land in the gen2 block: the flat keys beside it are + -- Red's, and no Gen 2 boot reads them (src/core/gen2/Save.lua:299). + -- loadOptions seeds the flat Gen 1 defaults, so the check is that the Gen 2 + -- rows leave them exactly as they found them. + local flatPad = model.opts.touchControls + local flatBuzz = model.opts.haptics -local pad = findRow(gold, "TOUCH PAD") -local before = pad.value() -pad.step(1) -check(pad.value() ~= before, "stepping TOUCH PAD flips it") -check(type(gold.opts.gold) == "table", "into the gold block") -eq(gold.opts.gold.touchControls.enabled, false, "which now carries enabled") -eq(gold.opts.touchControls, flatPad, "leaving the flat Gen 1 key alone") + local pad = findRow(model, "TOUCH PAD") + local before = pad.value() + pad.step(1) + check(pad.value() ~= before, version .. " stepping TOUCH PAD flips it") + check(type(model.opts.gold) == "table", version .. " into the gold block") + eq(model.opts.gold.touchControls.enabled, false, + version .. " which now carries enabled") + eq(model.opts.touchControls, flatPad, + version .. " leaving the flat Gen 1 key alone") + eq(model.opts.silver, nil, + version .. " and inventing no second Gen 2 block beside it") -local buzz = findRow(gold, "VIBRATION") -local buzzBefore = buzz.value() -buzz.step(1) -check(buzz.value() ~= buzzBefore, "stepping VIBRATION moves the level") -eq(gold.opts.gold.haptics, TouchControls.normalizeHaptics(gold.opts.gold.haptics), - "VIBRATION stores a level the shared module knows") -eq(gold.opts.haptics, flatBuzz, "also without touching Red's") + local buzz = findRow(model, "VIBRATION") + local buzzBefore = buzz.value() + buzz.step(1) + check(buzz.value() ~= buzzBefore, version .. " stepping VIBRATION moves the level") + eq(model.opts.gold.haptics, + TouchControls.normalizeHaptics(model.opts.gold.haptics), + version .. " VIBRATION stores a level the shared module knows") + eq(model.opts.haptics, flatBuzz, version .. " also without touching Red's") -findRow(gold, "TOUCH CONTROLS").action() -eq(edited, 1, "the editor row reaches the host hook") + edited = 0 + findRow(model, "TOUCH CONTROLS").action() + eq(edited, 1, version .. " the editor row reaches the host hook") +end -- The Gen 1 gear is untouched by the extraction: same three rows, still on -- the flat table. @@ -94,6 +103,8 @@ eq(findRow(red, "TOUCH PAD").value() ~= nil, true, "and reading it back") -- rather than dead, on both sides. eq(has(LauncherSettings.open(nil, "gold"), "TOUCH CONTROLS"), false, "no hook, no editor row on Gold") +eq(has(LauncherSettings.open(nil, "silver"), "TOUCH CONTROLS"), false, + "nor on Silver") eq(has(LauncherSettings.open(nil, "red"), "TOUCH CONTROLS"), false, "nor on Red") -- The Edit row hands the screen to the host, and the host has to know WHICH diff --git a/tests/engine/launcher_scroll_test.lua b/tests/engine/launcher_scroll_test.lua index c74e4cce..0816897c 100644 --- a/tests/engine/launcher_scroll_test.lua +++ b/tests/engine/launcher_scroll_test.lua @@ -155,28 +155,34 @@ modImp.mods = mods modImp._ensureMods = function() return mods end LauncherView.draw(modImp) LauncherView.draw(modImp) -check((modImp._modScrollMax or 0) > 0, - "60 mods overflow the list viewport inside the panel") -local list = modImp._modListRect -check(list.x + list.w - <= modImp._tabRegionRect.x + modImp._tabRegionRect.w - Kit.scrollBarW(), - "the rows stop short of the region's scrollbar gutter") -pointer(list.x + 10, list.y + 10) +local reg = modImp._tabRegionRect +check((modImp._tabScrollMax.mods or 0) > reg.h, + "60 installed mods are ONE continuous list: the region's travel spans " + .. "the whole list, not one page of it") +eq(modImp._pages.mods, nil, "and no page state is ever minted for it") +pointer(reg.x + 10, reg.y + 40) modImp._wheelY = -1 LauncherView.draw(modImp) -check((modImp.modScroll or 0) > 0, "a notch over the mod list scrolls the list") -eq(modImp._tabScroll.mods or 0, 0, "not the panel region around it") -eq(modImp._pageScroll, 0, "and not the page behind that") +check((modImp._tabScroll.mods or 0) > 0, + "a notch over the region scrolls the list like any other tab") +eq(modImp._pageScroll, 0, "without reaching the page behind it") + +modImp._tabScroll.mods = modImp._tabScrollMax.mods +LauncherView.draw(modImp) +pointer(reg.x + 10, reg.y + reg.h * 0.5) +modImp._wheelY = -1 +LauncherView.draw(modImp) +eq(modImp._pages.mods, nil, + "bottoming the list out never auto-advances any pager") +eq(modImp._tabScroll.mods, modImp._tabScrollMax.mods, + "the list just rests at its end") modImp._modActions = "mod1" -local shielded = modImp.modScroll -local shieldedPage = modImp._pageScroll -pointer(list.x + 10, list.y + 10) +local shieldedAt = modImp._tabScroll.mods modImp._wheelY = -1 LauncherView.draw(modImp) -eq(modImp.modScroll, shielded, "a shielded mod list ignores the notch") -eq(modImp._tabScroll.mods or 0, 0, "and so does the region under the scrim") -eq(modImp._pageScroll, shieldedPage, "and the page behind that") +eq(modImp._tabScroll.mods, shieldedAt, + "a shielded mod list ignores the notch under the scrim") modImp._modActions = nil modImp._wheelY = 0 LauncherView.draw(modImp) @@ -225,32 +231,27 @@ dragMods.mods = mods dragMods._ensureMods = function() return mods end LauncherView.draw(dragMods) LauncherView.draw(dragMods) -local dlist = dragMods._modListRect -local dListMax = dragMods._modScrollMax +local dreg = dragMods._tabRegionRect local dRegionMax = dragMods._tabScrollMax.mods -check(dListMax > 0 and dRegionMax > 0, - "the mods tab has both an inner list and a region to scroll") -LauncherView.touchpressed(dragMods, 7, dlist.x + 20, dlist.y + 30) -LauncherView.touchmoved(dragMods, 7, dlist.x + 20, dlist.y + 30 - 60) -eq(dragMods.modScroll, math.min(60, dListMax), - "the first pixels of the drag move the list") -eq(dragMods._tabScroll.mods or 0, 0, "and nothing else") -LauncherView.touchmoved(dragMods, 7, dlist.x + 20, - dlist.y + 30 - 60 - dListMax - dRegionMax * 2) -eq(dragMods.modScroll, dListMax, "carrying on saturates the list") -eq(dragMods._tabScroll.mods, dRegionMax, - "then the same gesture walks the region to its bottom") +check(dRegionMax > 0, "the mods region scrolls its overscan like any tab") +LauncherView.touchpressed(dragMods, 7, dreg.x + 20, dreg.y + 30) +LauncherView.touchmoved(dragMods, 7, dreg.x + 20, dreg.y + 30 - 60) +eq(dragMods._tabScroll.mods, math.min(60, dRegionMax), + "a drag moves the region by the finger's travel") +LauncherView.touchmoved(dragMods, 7, dreg.x + 20, + dreg.y + 30 - 60 - dRegionMax * 2) +eq(dragMods._tabScroll.mods, dRegionMax, "carrying on saturates the region") check((dragMods._pageScroll or 0) > 0, "and only then reaches the page") -LauncherView.touchreleased(dragMods, 7, dlist.x + 20, dlist.y - 900) +LauncherView.touchreleased(dragMods, 7, dreg.x + 20, dreg.y - 900) dragMods._skins = { { id = "s1", source = "user", controls = 8, pages = 1 } } for i = 2, 12 do dragMods._skins[i] = { id = "s" .. i, source = "user", controls = 8, pages = 1 } end dragMods._ensureSkins = function() return dragMods._skins end -dragMods.modScroll = 0 -local heldModScroll = dragMods.modScroll -local overList = dlist.y + 30 +local heldModsPage = dragMods._pages.mods or 1 +local heldModsAt = dragMods._tabScroll.mods +local overList = dreg.y + 60 dragMods:_switchTab("skins") LauncherView.draw(dragMods) LauncherView.draw(dragMods) @@ -260,8 +261,10 @@ LauncherView.touchpressed(dragMods, 9, sreg.x + 20, overList) LauncherView.touchmoved(dragMods, 9, sreg.x + 20, overList - 200) check((dragMods._tabScroll.skins or 0) > 0, "a drag on the skins tab scrolls the skins tab") -eq(dragMods.modScroll, heldModScroll, - "and leaves the mod list where the player parked it") +eq(dragMods._pages.mods or 1, heldModsPage, + "and leaves the mod list on the page the player parked it") +eq(dragMods._tabScroll.mods, heldModsAt, + "with its region offset held for the return trip") LauncherView.touchreleased(dragMods, 9, sreg.x + 20, sreg.y - 400) love.graphics.polygon = love.graphics.polygon or function() end @@ -308,8 +311,8 @@ local view = read("src/import/LauncherView.lua") check(view:find("Kit.scrollBegin(", 1, true) ~= nil, "the panel dispatch opens a scroll region") check(view:find("Kit.scrollEnd(", 1, true) ~= nil, "and closes it") -check(view:find("modListWantsWheel", 1, true) ~= nil, - "the nested mod list is asked before the region takes a notch") +check(view:find("modListWantsWheel", 1, true) == nil, + "no nested list steals the wheel from the region any more") check(view:find("start.region", 1, true) ~= nil, "a touch drag that began in the region scrolls the region") check(view:find("Kit.scrollGutter(", 1, true) ~= nil, diff --git a/tests/engine/mod_targets_tests.lua b/tests/engine/mod_targets_tests.lua index dd1c9a7f..87f8fc93 100644 --- a/tests/engine/mod_targets_tests.lua +++ b/tests/engine/mod_targets_tests.lua @@ -32,11 +32,13 @@ do "a version id names exactly that game") eq(table.concat(ModTargets.expand("GEN1"), ","), "red,blue,yellow", "gen1 is every Gen 1 game, case-insensitive") - eq(table.concat(ModTargets.expand("gen2"), ","), "gold", + eq(table.concat(ModTargets.expand("gen2"), ","), "gold,silver", "gen2 is every Gen 2 game") + eq(table.concat(ModTargets.expand("silver"), ","), "silver", + "and each of them names itself") eq(table.concat(ModTargets.expand("all"), ","), table.concat(GameVersion.ORDER, ","), "all is the launcher order itself") - eq(ModTargets.expand("silver"), nil, "a game this engine has no cache for") + eq(ModTargets.expand("crystal"), nil, "a game this engine has no cache for") eq(ModTargets.expand("gen9"), nil, "a generation with no games is unknown") eq(ModTargets.expand(7), nil, "a non-string token is not a game") end @@ -56,7 +58,7 @@ end do eq(list(mf({})), "red,blue,yellow", "a manifest with no games key is Gen 1, which is what it was tested as") - eq(list(mf({ gen2compat = true })), "red,blue,yellow,gold", + eq(list(mf({ gen2compat = true })), "red,blue,yellow,gold,silver", "gen2compat keeps Gen 1 and adds Gen 2") eq(mf({}).gen2compat, false, "and the derived flag agrees") eq(mf({ gen2compat = true }).gen2compat, true, "both ways") @@ -67,14 +69,14 @@ end do local gen2 = mf({ games = { "gen2" } }) - eq(list(gen2), "gold", "games can name Gen 2 alone") + eq(list(gen2), "gold,silver", "games can name Gen 2 alone") eq(gen2.gen2compat, true, "which IS the gen2compat claim the gate reads") local both = mf({ games = { "gen1", "gen2" } }) - eq(list(both), "red,blue,yellow,gold", "or both generations") + eq(list(both), "red,blue,yellow,gold,silver", "or both generations") local one = mf({ games = { "blue" } }) eq(list(one), "blue", "or one single game") eq(one.gen2compat, false, "a Gen 1 game is not a Gen 2 claim") - eq(list(mf({ games = { "red" }, gen2compat = true })), "red,gold", + eq(list(mf({ games = { "red" }, gen2compat = true })), "red,gold,silver", "an old gen2compat beside a new games list still adds its game") end @@ -224,10 +226,12 @@ do local bad = ModProfile.decode(require("src.core.SaveSerializer").encode({ format = "g1rmodlist", formatVersion = 1, profile = { name = "P", enabledByVersion = { - gold = { a = true }, silver = { a = true }, red = "nope" } }, + gold = { a = true }, silver = { a = true }, crystal = { a = true }, + red = "nope" } }, })) eq(bad.enabledByVersion.gold.a, true, "a shared file's known game is kept") - eq(bad.enabledByVersion.silver, nil, "an unknown game is dropped on read") + eq(bad.enabledByVersion.silver.a, true, "every one of them, not just the first") + eq(bad.enabledByVersion.crystal, nil, "an unknown game is dropped on read") eq(bad.enabledByVersion.red, nil, "and so is a bucket that is not a table") end diff --git a/tests/engine/rom_importer_nx_saves_inbox_test.lua b/tests/engine/rom_importer_nx_saves_inbox_test.lua index 5bdba458..5a22437e 100644 --- a/tests/engine/rom_importer_nx_saves_inbox_test.lua +++ b/tests/engine/rom_importer_nx_saves_inbox_test.lua @@ -59,7 +59,7 @@ package.loaded["src.import.RomImporter"] = nil RomImporter = require("src.import.RomImporter") local function clearSavesInbox() - for _, ver in ipairs({ "red", "blue", "yellow", "gold" }) do + for _, ver in ipairs({ "red", "blue", "yellow", "gold", "silver" }) do local dir = "imports/saves/" .. ver for _, name in ipairs(love.filesystem.getDirectoryItems(dir) or {}) do love.filesystem.remove(dir .. "/" .. name) @@ -137,6 +137,8 @@ check(createdDirs["imports/saves/yellow"] == true, "RES-01: ensureSavesInboxDir creates imports/saves/yellow/") check(createdDirs["imports/saves/gold"] == true, "RES-01: ensureSavesInboxDir creates imports/saves/gold/") +check(createdDirs["imports/saves/silver"] == true, + "RES-01: ensureSavesInboxDir creates imports/saves/silver/") -- NXSAV-02: notice/hint includes save dir + per-game imports/saves// MTP path ri = freshImporter() diff --git a/tests/engine/screen_position.lua b/tests/engine/screen_position.lua new file mode 100644 index 00000000..c0b679c1 --- /dev/null +++ b/tests/engine/screen_position.lua @@ -0,0 +1,113 @@ +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.harness") +local check, eq = T.check, T.eq +love = love or require("tests.love_stub") + +local ScreenPosition = require("src.core.ScreenPosition") +local TouchSkin = require("src.core.TouchSkin") +local Renderer = require("src.render.Renderer") +local Chrome = require("src.ui.gen2.Chrome") +local Zoom = require("src.render.Zoom") + +eq(ScreenPosition.normalize(nil), "center", "no setting is CENTER") +eq(ScreenPosition.normalize("junk"), "center", "garbage degrades to CENTER") +eq(ScreenPosition.normalize("top"), "top", "top passes through") +eq(ScreenPosition.label("upper"), "UPPER", "upper reads UPPER") + +local seen, v = {}, "center" +for _ = 1, 3 do + seen[#seen + 1] = ScreenPosition.label(v) + v = ScreenPosition.cycle(v, 1) +end +eq(table.concat(seen, ","), "CENTER,UPPER,TOP", "the row cycles CENTER,UPPER,TOP") +eq(ScreenPosition.cycle("top", 1), "center", "and wraps back to CENTER") +eq(ScreenPosition.cycle("center", -1), "top", "stepping back lands on TOP") + +ScreenPosition.setMode("center") +eq(ScreenPosition.lift(640, 288), 0, "CENTER never lifts") + +ScreenPosition.setMode("top") +eq(ScreenPosition.lift(640, 288), 176, "TOP lifts the centered origin to 0") +eq(ScreenPosition.lift(288, 288), 0, "no slack, no lift") +eq(ScreenPosition.lift(144, 288), 0, "negative slack, no lift") +eq(ScreenPosition.lift(640, 288, 40), 136, "TOP stops at the safe-area inset") +eq(ScreenPosition.lift(640, 288, 999), 0, + "a safe inset past center degrades to centered") + +ScreenPosition.setMode("upper") +eq(ScreenPosition.lift(640, 288), 88, "UPPER lands halfway between") +eq(ScreenPosition.lift(640, 288, 40), 88, "a small inset leaves UPPER alone") +eq(ScreenPosition.lift(640, 288, 120), 56, "a large inset pushes UPPER down") + +ScreenPosition.applyOptions({ screenPos = "top" }) +eq(ScreenPosition.mode, "top", "applyOptions takes the stored key") +ScreenPosition.applyOptions(nil) +eq(ScreenPosition.mode, "center", "applyOptions without options is CENTER") + +local function setWindow(w, h) + love.graphics.getDimensions = function() return w, h end + love.graphics.getPixelDimensions = function() return w, h end +end + +setWindow(360, 640) +TouchSkin.setActive(nil) +Renderer:init() +Zoom.offset = 0 + +ScreenPosition.setMode("center") +local r = Renderer:frameRects() +eq(r.Sp, 2, "360x640 fits two whole GB pixels") +eq(r.lift, 0, "CENTER: no lift") +eq(r.oy, 176, "CENTER: the letterbox is centered") +local _, vhCenter = Renderer:worldViewSize() + +ScreenPosition.setMode("top") +r = Renderer:frameRects() +eq(r.lift, 176, "TOP: the full centered slack lifts away") +eq(r.oy, 0, "TOP: the letterbox sits at the top edge") +eq(r.uoy, 0, "TOP: the UI letterbox follows") +eq(r.ox, math.floor((360 - 320) / 2), "TOP: horizontal centering is untouched") +local _, vhTop = Renderer:worldViewSize() +eq(vhTop, vhCenter + 2 * math.ceil(176 / 2), + "TOP: the world canvas grows to keep the bottom covered") + +ScreenPosition.setMode("upper") +r = Renderer:frameRects() +eq(r.oy, 88, "UPPER: the letterbox centers in the upper half") + +ScreenPosition.setMode("top") +local ox, oy = Chrome.fitOrigin(360, 640) +eq(oy, 0, "TOP: Gold's letterbox sits at the top edge") +eq(ox, math.floor((360 - 320) / 2), "TOP: Gold's horizontal centering is untouched") +ScreenPosition.setMode("center") +local _, cy = Chrome.fitOrigin(360, 640) +eq(cy, 176, "CENTER: Gold's letterbox is centered") + +ScreenPosition.setMode("top") +local skin = assert(TouchSkin.parse([[ +overlays = 1 +overlay0_name = "bezel" +overlay0_full_screen = true +overlay0_normalized = true +overlay0_viewport = "0.1,0.1,0.8,0.4" +overlay0_descs = 1 +overlay0_desc0 = "nul,0.5,0.5,rect,0.02,0.02" +]])) +TouchSkin.setActive(skin) +TouchSkin.setOverlayLive(false) +r = Renderer:frameRects() +eq(r.cut, true, "the skin viewport cuts the playfield") +eq(r.lift, 0, "a skin viewport disables the lift") +eq(select(1, ScreenPosition.skinActive(360, 640)), true, + "skinActive sees the viewport") +local _, sy = Chrome.fitOrigin(360, 640) +local _, cy2 = (function() + ScreenPosition.setMode("center") + return Chrome.fitOrigin(360, 640) +end)() +eq(sy, cy2, "with a skin the Gold origin ignores the mode") +TouchSkin.setActive(nil) +ScreenPosition.setMode("center") + +T.finish("screen_position") diff --git a/tests/engine/uwp_baseroms_test.lua b/tests/engine/uwp_baseroms_test.lua index 83f13e11..abe735a3 100644 --- a/tests/engine/uwp_baseroms_test.lua +++ b/tests/engine/uwp_baseroms_test.lua @@ -81,7 +81,8 @@ local function importer(ready) }, RomImporter) end -local allReady = importer({ red = true, blue = true, yellow = true, gold = true }) +local allReady = importer({ red = true, blue = true, yellow = true, gold = true, + silver = true }) allReady:_queueBaseRomScan() eq(allReady.baseRomScan.state, "done", "ready launcher skips discovery") eq(listings, 0, "ready launcher does not enumerate baseroms") @@ -123,7 +124,8 @@ eq(picks, 0, "missing detected ROM does not open the picker unexpectedly") missing:choose("red") eq(picks, 1, "the next import attempt falls back to the native picker") -local rescanned = importer({ red = true, blue = true, yellow = true, gold = true }) +local rescanned = importer({ red = true, blue = true, yellow = true, gold = true, + silver = true }) rescanned.baseRoms.red = { path = "baseroms/z-red.gb", name = "z-red.gb" } rescanned:reimport("red") check(rescanned.baseRoms.red == nil, "re-import clears the detected ROM") diff --git a/tests/gen2_gamecorner_test.lua b/tests/gen2_gamecorner_test.lua index 5b82fc71..a120e925 100644 --- a/tests/gen2_gamecorner_test.lua +++ b/tests/gen2_gamecorner_test.lua @@ -1233,6 +1233,118 @@ check("a full wallet fills it", Chrome.money(999999), "\xc2\xa5999999") check("the coin field keeps its leading zeroes", Chrome.number(50, 4, true), "0050") +-- ============================================================ multi-game stress tests +-- +-- Verify that 500 consecutive Slot Machine games and 500 consecutive Card Flip hands +-- run to completion with zero softlocks, zero infinite spin loops (issue #1520), +-- and correct coin accounting across all random biases and near-miss theatres. + +local mockSave = { + player = { name = "GOLD", coins = 5000 }, +} + +local mockInput = { + pressed = {}, + wasPressed = function(self, key) + local v = self.pressed[key] + self.pressed[key] = false + return v or false + end, + press = function(self, key) + self.pressed[key] = true + end, +} + +local mockSlotGame = { + save = mockSave, + input = mockInput, + data = {}, +} + +-- Test Slot Machine 500-spin continuous loop +local sm = SlotMachine.new(mockSlotGame, { lucky = true }) +local completedSpins = 0 + +for spin = 1, 500 do + mockSave.player.coins = 5000 -- ensure test player always has coins + if sm.phase == "quit" or sm.phase == "ranOut" then + sm = SlotMachine.new(mockSlotGame, { lucky = true }) + end + + -- Enter bet phase + check("slot machine in bet phase at spin start", sm.phase, "bet") + mockInput:press("a") -- bet 3 coins + sm:update(1/60) + + check("slot machine entered spinning phase", sm.phase, "spinning") + + local frameCount = 0 + local maxFrames = 5000 -- safety bound per spin + + while sm.phase == "spinning" and frameCount < maxFrames do + frameCount = frameCount + 1 + if frameCount % 30 == 0 then + mockInput:press("a") -- press stop button + end + sm:update(1/60) + end + + check(("spin %d must not hang in spinning"):format(spin), frameCount < maxFrames, true) + + -- Resolve flash and payout phases + while (sm.phase == "flash" or sm.phase == "payoutText") and frameCount < maxFrames do + frameCount = frameCount + 1 + if sm.phase == "payoutText" and sm.matched == SlotMachine.NO_MATCH then + mockInput:press("a") + end + sm:update(1/60) + end + + check(("spin %d reached again phase"):format(spin), sm.phase == "again" or sm.phase == "bet", true) + if sm.phase == "again" then + mockInput:press("a") -- choose YES to play again + sm:update(1/60) + completedSpins = completedSpins + 1 + elseif sm.phase == "bet" then + completedSpins = completedSpins + 1 + end +end + +check("all 500 slot machine spins completed without softlock", completedSpins >= 490, true) + +-- Test Card Flip 500-hand continuous loop +local cf = CardFlip.new(mockSlotGame) +local completedHands = 0 + +for hand = 1, 500 do + mockSave.player.coins = 5000 + if cf.phase == "quit" then + cf = CardFlip.new(mockSlotGame) + end + + local frameCount = 0 + local maxFrames = 500 + + while cf.phase ~= "again" and cf.phase ~= "quit" and frameCount < maxFrames do + frameCount = frameCount + 1 + if cf.phase == "ask" or cf.phase == "message" or cf.phase == "result" + or cf.phase == "choose" or cf.phase == "bet" then + mockInput:press("a") + end + cf:update(1/60) + end + + check(("hand %d completed in bounds"):format(hand), frameCount < maxFrames, true) + + if cf.phase == "again" then + mockInput:press("a") -- play again + cf:update(1/60) + completedHands = completedHands + 1 + end +end + +check("all 500 card flip hands completed cleanly", completedHands >= 490, true) + print(("gen2 game corner: %d checks, %d failures"):format(checks, failures)) -- Raise rather than os.exit: tests/run_tests.lua dofiles this file, so an exit -- here would take the whole tier down and silently skip every suite after it. diff --git a/tests/gen2_halloffame_test.lua b/tests/gen2_halloffame_test.lua index 720bd4b3..a849d1a5 100644 --- a/tests/gen2_halloffame_test.lua +++ b/tests/gen2_halloffame_test.lua @@ -31,10 +31,14 @@ require("src.core.Logger").warn = function() end local Core = require("src.core.gen2.HallOfFame") local Credits = require("src.ui.gen2.Credits") +local GameVersion = require("src.core.GameVersion") local HallOfFame = require("src.ui.gen2.HallOfFame") local Save = require("src.core.gen2.Save") local Screens = require("src.ui.Screens") +local priorVersion = GameVersion.get() +GameVersion.set("gold") + -- ---- fixtures ------------------------------------------------------------- local POKEMON = { @@ -711,4 +715,6 @@ else eq(creditsData.palettes[1][1][1], 255, "and set 1 colour 0 is white") end +GameVersion.set(priorVersion) + S.finish() diff --git a/tests/gen2_hof_continue_test.lua b/tests/gen2_hof_continue_test.lua index 807e9e87..453eed31 100644 --- a/tests/gen2_hof_continue_test.lua +++ b/tests/gen2_hof_continue_test.lua @@ -76,6 +76,8 @@ local function makeWorld() local game = { data = { audio = {}, screens = registry }, save = { + version = "gold", + generation = 2, player = { name = "GOLD" }, party = { { species = "TYPHLOSION", level = 50, hp = 120, otId = 33333 } }, diff --git a/tests/gen2_save_test.lua b/tests/gen2_save_test.lua index e2b80b2b..6493b63f 100644 --- a/tests/gen2_save_test.lua +++ b/tests/gen2_save_test.lua @@ -28,6 +28,9 @@ love.filesystem = { local GameVersion = require("src.core.GameVersion") local Save = require("src.core.gen2.Save") +local priorVersion = GameVersion.get() +GameVersion.set("gold") + local failures, checks = 0, 0 local function check(name, got, want) checks = checks + 1 @@ -48,6 +51,11 @@ check("main file", main, "save_gold.lua") check("backup file", backup, "save_gold.lua.bak") check("staged file", tmp, "save_gold.lua.tmp") check("gold suffix", GameVersion.saveSuffix("gold"), "_gold") +local sMain, sBackup, sTmp = Save.filenames("silver") +check("silver main file", sMain, "save_silver.lua") +check("silver backup file", sBackup, "save_silver.lua.bak") +check("silver staged file", sTmp, "save_silver.lua.tmp") +check("silver suffix", GameVersion.saveSuffix("silver"), "_silver") -- Red keeps its historical un-suffixed name, so the two can never collide. check("red is unsuffixed", GameVersion.saveSuffix("red"), "") @@ -104,7 +112,13 @@ check("defaults are copied", Save.defaultOptions().textSpeed, "MID") -- A sparse or hand-edited save must come back indexable rather than crashing -- the first screen that reads it. local sparse = Save.normalize({ player = {} }) -check("normalized version", sparse.version, "gold") +check("normalized version", sparse.version, GameVersion.get()) +check("a Gen 2 version already on the table is kept", + Save.normalize({ version = "silver", player = {} }).version, "silver") +check("and a Gen 1 one is replaced by the running edition", + Save.normalize({ version = "red", player = {} }).version, "gold") +check("as is a version this engine does not have", + Save.normalize({ version = "crystal", player = {} }).version, "gold") check("party exists", type(sparse.party), "table") check("inventory exists", type(sparse.inventory), "table") check("pokedex seen exists", type(sparse.pokedex.seen), "table") @@ -478,6 +492,46 @@ do files = {} end +-- ------- the same save, run as Silver +-- +-- The blank name is the edition's own first PlayerNameArray row +-- (data/player_names.asm:12-23). +do + GameVersion.set("silver") + files = {} + + local born = Save.newGame({}) + check("a silver boot stamps silver", born.version, "silver") + check("still generation 2", born.generation, 2) + check("with the edition's own preset name", born.player.name, "SILVER") + check("gold's preset is unchanged", Save.defaultPlayerName("gold"), "GOLD") + check("and silver's is its own", Save.defaultPlayerName("silver"), "SILVER") + check("the running edition answers with no argument", + Save.defaultPlayerName(), "SILVER") + + check("a version-less save takes the running edition", + Save.normalize({ player = {} }).version, "silver") + check("and a gold save keeps gold on a silver boot", + Save.normalize({ version = "gold", player = {} }).version, "gold") + + check("no silver save yet", Save.exists("silver"), false) + check("silver save wrote", Save.save(born), true) + check("silver save exists now", Save.exists("silver"), true) + check("gold is untouched by it", Save.exists("gold"), false) + + local main = Save.filenames("silver") + check("the silver file is silver's own", main, "saves/silver/slot1.lua") + check("and it is the file on disk", files[main] ~= nil, true) + local back = Save.load("silver") + check("silver round-trips", back and back.player.name, "SILVER") + check("keeping its stamp", back.version, "silver") + + files = {} + GameVersion.set("gold") +end + +GameVersion.set(priorVersion) + print(("gen2 save: %d checks, %d failures"):format(checks, failures)) -- Raise rather than os.exit: tests/run_tests.lua dofiles this file, so an -- exit here takes the whole tier down with it and silently skips every diff --git a/tests/rom_manifest_generator_test.py b/tests/rom_manifest_generator_test.py new file mode 100644 index 00000000..07a88d1c --- /dev/null +++ b/tests/rom_manifest_generator_test.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""ROM-free regression tests for the manifest generator's version pin and +its documented, deliberate overrides. No pokered checkout or ROM needed.""" + +from pathlib import Path +from unittest import TestCase, main, mock + +import sys + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools")) +import make_rom_manifest # noqa: E402 + + +class CheckPokeredRevisionTest(TestCase): + def test_matching_revision_is_silent(self): + with mock.patch.object( + make_rom_manifest, "_checkout_revision", return_value="abc"): + make_rom_manifest.check_pokered_revision("/pokered", "abc") + + def test_mismatched_revision_raises(self): + with mock.patch.object( + make_rom_manifest, "_checkout_revision", return_value="abc"): + with self.assertRaises(SystemExit): + make_rom_manifest.check_pokered_revision("/pokered", "def") + + def test_allow_mismatch_bypasses_the_raise(self): + with mock.patch.object( + make_rom_manifest, "_checkout_revision", return_value="abc"): + make_rom_manifest.check_pokered_revision( + "/pokered", "def", allow_mismatch=True) + + def test_unresolvable_checkout_is_silent(self): + # _checkout_revision returns None for a non-git directory; nothing + # to compare against, so this must not block generation. + with mock.patch.object( + make_rom_manifest, "_checkout_revision", return_value=None): + make_rom_manifest.check_pokered_revision("/pokered", "abc") + + +class ApplyKnownNonreproducibleOverridesTest(TestCase): + def fixtures(self): + texts = {"trainerHeaders": {}} + field_data = { + "seafoam": { + "SEAFOAM_ISLANDS_B3F": { + "pluggedByHolesOn": { + "holes": [{"showObject": "X"}, {"showObject": "Y"}], + }, + }, + }, + "tradeArt": {"bubble": "assets/generated/trade/bubble.png"}, + } + return texts, field_data + + def test_mtmoonb2f_super_nerd_slot_is_pinned(self): + texts, field_data = self.fixtures() + make_rom_manifest.apply_known_nonreproducible_overrides( + texts, field_data) + + self.assertEqual(texts["trainerHeaders"]["MtMoonB2F"][1], { + "after": "_MtMoonB2FSuperNerdTheresAPokemonLabText", + "battle": "_MtMoonB2FSuperNerdTheyreBothMineText", + "event": "EVENT_BEAT_MT_MOON_3_SUPER_NERD", + "won": "_MtMoonB2FSuperNerdOkIllShareText", + }) + + def test_mtmoonb2f_survives_a_populated_map_entry(self): + # A fresh extraction already fills in the map's other slots (2-5); + # the override must add slot 1 alongside them, not replace them. + texts, field_data = self.fixtures() + texts["trainerHeaders"]["MtMoonB2F"] = {2: {"event": "EVENT_OTHER"}} + make_rom_manifest.apply_known_nonreproducible_overrides( + texts, field_data) + + self.assertIn(1, texts["trainerHeaders"]["MtMoonB2F"]) + self.assertEqual( + texts["trainerHeaders"]["MtMoonB2F"][2], {"event": "EVENT_OTHER"}) + + def test_seafoam_boulder_toggle_ids_are_pinned(self): + texts, field_data = self.fixtures() + make_rom_manifest.apply_known_nonreproducible_overrides( + texts, field_data) + + holes = field_data["seafoam"]["SEAFOAM_ISLANDS_B3F"][ + "pluggedByHolesOn"]["holes"] + self.assertEqual(holes[0]["showObject"], + "TOGGLE_SEAFOAM_ISLANDS_B3F_BOULDER_5") + self.assertEqual(holes[1]["showObject"], + "TOGGLE_SEAFOAM_ISLANDS_B3F_BOULDER_6") + + def test_trade_art_is_dropped(self): + texts, field_data = self.fixtures() + make_rom_manifest.apply_known_nonreproducible_overrides( + texts, field_data) + + self.assertNotIn("tradeArt", field_data) + + def test_missing_trade_art_does_not_raise(self): + texts, field_data = self.fixtures() + del field_data["tradeArt"] + make_rom_manifest.apply_known_nonreproducible_overrides( + texts, field_data) + + +if __name__ == "__main__": + main() diff --git a/tests/save_editor_gen2_tests.lua b/tests/save_editor_gen2_tests.lua index db9474d8..b1877437 100644 --- a/tests/save_editor_gen2_tests.lua +++ b/tests/save_editor_gen2_tests.lua @@ -71,13 +71,17 @@ local data = { maps = {}, } -local function newState() +local function newState(version) + version = version or "gold" + local prior = GameVersion.get() + GameVersion.set(version) local S = State.new() S.data = data S.cat = Catalog.build(data) S.save = Save2.newGame() - S.version = "gold" + S.version = version Gen.ensureBoxes(S.save) + GameVersion.set(prior) return S end @@ -85,10 +89,19 @@ do GameVersion.set("gold") eq(Gen.of({ generation = 2 }), 2, "Gen.of generation field") eq(Gen.of({ version = "gold" }), 2, "Gen.of version gold") + eq(Gen.of({ version = "silver" }), 2, "Gen.of version silver") eq(Gen.of(SaveData.newGame()), 1, "Gen.of gen1 newGame") eq(Gen.of(Save2.newGame()), 2, "Gen.of gold newGame") end +do + GameVersion.set("silver") + eq(Save2.newGame().version, "silver", "a silver boot stamps silver") + eq(Save2.newGame().player.name, "SILVER", "with silver's preset name") + eq(Gen.of(Save2.newGame()), 2, "Gen.of silver newGame") + GameVersion.set("gold") +end + do local S = newState() check(Ops.speciesUsable(S, "CYNDAQUIL"), "spa/spd species is usable") @@ -103,36 +116,37 @@ do check(not Ops.speciesUsable(S1, "BROKEN"), "partial record is not usable") end -do - local S = newState() +for _, version in ipairs({ "gold", "silver" }) do + local S = newState(version) Ops.partyAdd(S) - eq(#S.save.party, 1, "partyAdd on gold") + eq(#S.save.party, 1, "partyAdd on " .. version) local mon = S.save.party[1] - eq(mon.species, "CYNDAQUIL", "first catalog species") - check(mon.experience ~= nil, "gold mon has experience") - check(mon.exp == nil or mon.experience ~= nil, "does not rely on gen1 exp") + eq(mon.species, "CYNDAQUIL", version .. " first catalog species") + check(mon.experience ~= nil, version .. " mon has experience") + check(mon.exp == nil or mon.experience ~= nil, + version .. " does not rely on gen1 exp") check(mon.stats.specialAttack and mon.stats.specialDefense, - "gold stats have spa/spd") - check(mon.happiness ~= nil, "gold mon has happiness") - eq(mon.ot, S.save.player.name, "stampOT copies player name") + version .. " stats have spa/spd") + check(mon.happiness ~= nil, version .. " mon has happiness") + eq(mon.ot, S.save.player.name, version .. " stampOT copies player name") Ops.setLevel(S, mon, 20) - eq(mon.level, 20, "setLevel 20") - check(mon.experience > 0, "experience resynced") + eq(mon.level, 20, version .. " setLevel 20") + check(mon.experience > 0, version .. " experience resynced") Ops.setHappiness(S, mon, 200) - eq(mon.happiness, 200, "happiness 200") + eq(mon.happiness, 200, version .. " happiness 200") Ops.setPokerus(S, mon, 15) - eq(mon.pokerus, 15, "pokerus byte") + eq(mon.pokerus, 15, version .. " pokerus byte") Ops.setHeldItem(S, mon, "POTION") - eq(mon.item, "POTION", "held item") + eq(mon.item, "POTION", version .. " held item") - eq(mon.name, "CYNDAQUIL", "new mon copies species display name") + eq(mon.name, "CYNDAQUIL", version .. " new mon copies species display name") Ops.setSpecies(S, mon, "TOTODILE") - eq(mon.species, "TOTODILE", "setSpecies id") - eq(mon.name, "TOTODILE", "setSpecies rewrites the Gold display name") - check(mon.nickname == nil, "setSpecies does not invent a nickname") - eq(mon.types[1], "WATER", "setSpecies rewrites copied types") + eq(mon.species, "TOTODILE", version .. " setSpecies id") + eq(mon.name, "TOTODILE", version .. " setSpecies rewrites the display name") + check(mon.nickname == nil, version .. " setSpecies does not invent a nickname") + eq(mon.types[1], "WATER", version .. " setSpecies rewrites copied types") end do diff --git a/tests/save_editor_mod_tests.lua b/tests/save_editor_mod_tests.lua index 9ba1d482..1fac297f 100644 --- a/tests/save_editor_mod_tests.lua +++ b/tests/save_editor_mod_tests.lua @@ -149,12 +149,22 @@ local ok, err = pcall(function() local Ops = require("Ops") check(not ModTargets.supports({}, "gold"), "legacy gen1-only fixture does not support gold") + check(not ModTargets.supports({}, "silver"), + "nor silver") check(not ModTargets.supports({ games = { "red" } }, "gold"), "explicit gen1 games list does not support gold") + check(not ModTargets.supports({ games = { "red" } }, "silver"), + "nor silver") check(ModTargets.supports({ games = { "gold" } }, "gold"), "gold-targeted manifest supports gold") + check(not ModTargets.supports({ games = { "gold" } }, "silver"), + "and names one edition, not the generation") + check(ModTargets.supports({ games = { "gold", "silver" } }, "silver"), + "a whole-Gen-2 games list supports silver") check(ModTargets.supports({ gen2compat = true }, "gold"), "gen2compat legacy still supports gold") + check(ModTargets.supports({ gen2compat = true }, "silver"), + "and silver with it") local goldS = { data = { diff --git a/tests/test_surfing_minigame.lua b/tests/test_surfing_minigame.lua index 8607a92f..af4cb669 100644 --- a/tests/test_surfing_minigame.lua +++ b/tests/test_surfing_minigame.lua @@ -40,14 +40,16 @@ local mockGame = { print("Running SurfingMinigame unit tests...") --- Test 1: Initialization +-- Test 1: Initialization & Title Screen transition local mg = SurfingMinigame.new(mockGame) +assert_eq(mg.routine, -1, "Initial routine must be ROUTINE_TITLE (-1)") +mg:startFromTitle() +assert_eq(mg.routine, 0, "Routine must advance to ROUTINE_START_GAME (0) after startFromTitle()") assert_eq(mg.hp, 6000, "Initial HP must be 6000 (60.00s)") assert_eq(mg.speed, 0.25, "Initial speed must be 0.25") assert_eq(mg.distance, 0, "Initial distance must be 0") -assert_eq(mg.routine, 0, "Initial routine must be ROUTINE_START_GAME (0)") assert_eq(mg.pikaState, 0, "Initial Pikachu state must be PIKA_STATE_RIDING (0)") -print("✓ Initial state test passed") +print("✓ Initial state & Title transition test passed") -- Test 2: Start banner transition to RunGame for _ = 1, 40 do @@ -65,6 +67,8 @@ assert_eq(mg.hp, initialHp - 1, "HP should decrease by 1 each frame") print("✓ Auto acceleration and HP countdown test passed") -- Test 4: Landing Evaluation Matrix +local old_getWaveTile = mg.getWaveTileUnderPika +mg.getWaveTileUnderPika = function() return 0x01 end -- force open water mg.frameSet = 5 assert_eq(mg:evaluateLanding(), "rough", "Angle 5 on open water should be rough landing") mg.frameSet = 6 @@ -77,6 +81,7 @@ for f = 8, 14 do mg.frameSet = f assert_eq(mg:evaluateLanding(), "wipeout", "Upside-down frame " .. f .. " must be wipeout") end +mg.getWaveTileUnderPika = old_getWaveTile print("✓ Landing evaluation matrix test passed (including upside-down frames 8..14)") -- Test 5: Stunt Scoring @@ -145,6 +150,128 @@ end assert_eq(mg.radness, 0, "Radness should be tallied down to 0") assert_eq(mg.totalScore, 300, "Total score should be 300 (100 HP + 200 Radness)") assert_eq(mg.routine, 10, "Routine should advance to ROUTINE_WAIT_LAST (10)") -print("✓ Results tally countdown test passed") +-- Test 8: Crossing finish line while jumping upside-down crashes into water and rights Pikachu before results +local mg8 = SurfingMinigame.new(mockGame, nil, true) +mg8.routine = 1 -- ROUTINE_RUN_GAME +mg8.distanceFixed = (24 * 128 - 2) * 256 +mg8.speedFixed = 512 +mg8.pikaState = 1 -- PIKA_STATE_JUMPING +mg8.frameSet = 11 -- Upside down +mg8.pikaY = 60 +mg8.jumpDescending = true +mg8.jumpArcMagnitude = 4 +mg8.radness = 150 +local preScore = mg8.radness + +-- Update to cross the finish line +mg8:update() +assert_eq(mg8.routine, 2, "Routine should advance to ROUTINE_WAIT_RESULTS (2) upon crossing finish") +assert_eq(mg8.pikaState, 1, "Pikachu should remain mid-air immediately after crossing line") + +-- Update until Pikachu lands in water +while mg8.pikaState == 1 do + mg8:update() +end +assert_eq(mg8.pikaState, 3, "Upside-down landing post-finish line must trigger PIKA_STATE_CRASHED (3)") +assert_eq(mg8.radness, preScore, "Radness score must NOT change after crossing finish line") +assert_eq(mg8.crashTimer, 96, "Crash timer must be initialized to 96 frames") + +-- Update while crashed to verify recovery +while mg8.pikaState == 3 do + mg8:update() +end +assert_eq(mg8.pikaState, 0, "Pikachu must recover back to PIKA_STATE_RIDING (0) and right itself on the board") +assert_eq(mg8.frameSet, 4, "Pikachu frameSet must be reset to upright (4)") + +-- Let coasting finish and verify transition to results +while mg8.routine == 2 do + mg8:update() +end +assert_eq(mg8.routine, 3, "Routine should advance to ROUTINE_SCROLL_RESULTS (3) only after Pikachu is upright") +print("✓ Mid-air upside-down finish line crossing crash & recovery test passed") + +-- Test 9: Crossing finish line while upright jumping lands cleanly and proceeds +local mg9 = SurfingMinigame.new(mockGame, nil, true) +mg9.routine = 1 +mg9.distanceFixed = (24 * 128 - 2) * 256 +mg9.speedFixed = 512 +mg9.pikaState = 1 +mg9.frameSet = 4 -- Clean flat +mg9.pikaY = 60 +mg9.jumpDescending = true +mg9.jumpArcMagnitude = 4 +mg9.radness = 200 +preScore = mg9.radness + +mg9:update() +assert_eq(mg9.routine, 2, "Routine should advance to ROUTINE_WAIT_RESULTS (2)") + +while mg9.pikaState == 1 do + mg9:update() +end +assert_eq(mg9.pikaState, 2, "Upright landing post-finish line must trigger PIKA_STATE_LANDING (2)") +assert_eq(mg9.radness, preScore, "Radness score must NOT change post-finish") + +while mg9.pikaState == 2 do + mg9:update() +end +assert_eq(mg9.pikaState, 0, "Pikachu must return to PIKA_STATE_RIDING (0)") +print("✓ Mid-air upright finish line crossing test passed") + +-- Test 10: Crossing finish line while already crashed recovers before results +local mg10 = SurfingMinigame.new(mockGame, nil, true) +mg10.routine = 1 +mg10.distanceFixed = (24 * 128 - 2) * 256 +mg10.speedFixed = 512 +mg10.pikaState = 3 -- PIKA_STATE_CRASHED +mg10.crashTimer = 50 + +mg10:update() +assert_eq(mg10.routine, 2, "Routine should advance to ROUTINE_WAIT_RESULTS (2)") +assert_eq(mg10.pikaState, 3, "Pikachu should still be crashed") + +while mg10.pikaState == 3 do + mg10:update() +end +assert_eq(mg10.pikaState, 0, "Pikachu must recover upright before proceeding to results") +print("✓ Pre-crashed finish line crossing recovery test passed") + +-- Test 11: Decoupled timestep accumulator (60Hz and 144Hz framerate consistency) +local mg11_60 = SurfingMinigame.new(mockGame, nil, true) +mg11_60.routine = 1 -- ROUTINE_RUN_GAME +for _ = 1, 60 do + mg11_60:update(1 / 60) +end +assert(mg11_60.t == 59 or mg11_60.t == 60, "60Hz update over 1s must produce approx 60 ticks (got " .. mg11_60.t .. ")") + +local mg11_144 = SurfingMinigame.new(mockGame, nil, true) +mg11_144.routine = 1 -- ROUTINE_RUN_GAME +for _ = 1, 144 do + mg11_144:update(1 / 144) +end +assert(mg11_144.t == 59 or mg11_144.t == 60, "144Hz update over 1s must produce approx 60 ticks (got " .. mg11_144.t .. ")") +print("✓ Decoupled 59.7275Hz timestep accumulator test passed") + +-- Test 12: Landing continuity on slopes (no position jumps while landing) +local mg12 = SurfingMinigame.new(mockGame, nil, true) +mg12.routine = 1 +mg12.pikaState = 2 -- PIKA_STATE_LANDING +mg12.landingTimer = 20 +mg12.speedFixed = 256 +-- Place on a rising wave pattern +mg12.cols[5] = { pat = SurfingMinigame.WAVE_PATTERNS[0x06], hl = 110, hr = 100 } +mg12.distanceFixed = (5 * 16 - 80) * 256 +local startY = mg12.pikaY +mg12:update() +assert(mg12.pikaY ~= startY, "pikaY must continuously follow wave surface height while in PIKA_STATE_LANDING") +print("✓ Landing slope height tracking continuity test passed") + +-- Test 13: Fixed speed enforcement (minigames must always run at 1X speed) +assert(mg12.isFixedSpeed == true, "SurfingMinigame must have isFixedSpeed flag enabled") +assert(mg12.isMinigame == true, "SurfingMinigame must have isMinigame flag enabled") +local mockStack = { states = { mg12 } } +local Game = require("src.core.Game") +assert(Game.isFixedSpeedInStack(mockStack) == true, "Game.isFixedSpeedInStack must return true for SurfingMinigame") +print("✓ Minigame fixed speed enforcement test passed") print("All SurfingMinigame unit tests passed successfully!") diff --git a/tools/build_rom_data.py b/tools/build_rom_data.py index 8c4a9325..e76c17c2 100755 --- a/tools/build_rom_data.py +++ b/tools/build_rom_data.py @@ -2037,8 +2037,48 @@ def extract_field(rom, symbols, manifest, out_dir, assets_dir): _decode_2bpp(bytes(reordered), 40, 16), os.path.join(assets_dir, "credits/the_end.png")) - raw_2bpp( - "WorldMapTileGraphics", 32, 32, "townmap/tiles.png") + if _has_symbol(symbols, "SurfingPikachu1Graphics1"): + raw_2bpp("SurfingPikachu1Graphics1", 40, 104, "minigame/surf_1a.png", transparent=False) + raw_2bpp("SurfingPikachu1Graphics2", 128, 128, "minigame/surf_1b.png", transparent=True) + raw_2bpp("SurfingPikachu1Graphics3", 96, 96, "minigame/surf_1c.png", transparent=True) + + beach_intro = rom.bytes(62, 0x50bc, 240) + use_ctrl_pad = rom.bytes(62, 0x51ac, 15) + to_surf_rad = rom.bytes(62, 0x51bb, 13) + title_map = rom.bytes(62, 0x51c8, 72) + screen = [0xff] * (20 * 18) + for i in range(240): + screen[6 * 20 + i] = beach_intro[i] + for r in range(6): + for c in range(12): + screen[r * 20 + (4 + c)] = title_map[r * 12 + c] + for r in range(3): + for c in range(15): + screen[(7 + r) * 20 + (3 + c)] = 0xff + for i in range(15): + screen[7 * 20 + 3 + i] = use_ctrl_pad[i] + for i in range(13): + screen[9 * 20 + 4 + i] = to_surf_rad[i] + + sym3 = _symbol(symbols, "SurfingPikachu1Graphics3") + raw_gfx3 = rom.bytes(sym3.bank, sym3.address, 144 * 16) + tiles = [_decode_2bpp(raw_gfx3[i*16:(i+1)*16], 8, 8) for i in range(144)] + blank = Image.new("RGBA", (8, 8), (255, 255, 255, 255)) + title_bg = Image.new("RGBA", (160, 144), (255, 255, 255, 255)) + for r in range(18): + for c in range(20): + t_id = screen[r * 20 + c] + if t_id == 0xff: + tile_img = blank + elif t_id >= 0x80: + idx = t_id - 0x80 + tile_img = tiles[idx] if idx < 144 else blank + else: + idx = 128 + t_id + tile_img = tiles[idx] if idx < 144 else blank + title_bg.paste(tile_img, (c * 8, r * 8)) + _save_png(title_bg, os.path.join(assets_dir, "minigame/title_bg.png")) + raw_2bpp("WorldMapTileGraphics", 32, 32, "townmap/tiles.png") raw_1bpp( "TownMapCursor", 16, 16, "townmap/cursor.png", transparent=True) diff --git a/tools/extract/text.py b/tools/extract/text.py index 68d10c33..165ab0c7 100644 --- a/tools/extract/text.py +++ b/tools/extract/text.py @@ -133,7 +133,13 @@ def parse_text_file(path, texts, rel): continue if s in ("ENDC", "vc_patch_end"): continue - m = re.match(r"(_\w+)::?\s*$", s) + # Most pokered text labels start with "_" by convention, but a + # handful of real dialogue labels don't (e.g. text/ViridianCity.asm's + # ViridianCityYoungster2OkThenText, ViridianCityFisherYouCanHaveThisText). + # text/*.asm, data/text/text_*.asm and dex_text.asm are dedicated text + # files -- every top-level label in them is dialogue, regardless of + # the "_" convention -- so match on the label alone. + m = re.match(r"(\w+)::?\s*$", s) if m: flush() label = m.group(1) diff --git a/tools/generate_android_icons.py b/tools/generate_android_icons.py index 9cf412c8..43db6e53 100644 --- a/tools/generate_android_icons.py +++ b/tools/generate_android_icons.py @@ -25,6 +25,7 @@ SHELL_COLORS = { "blue": {"main": (35, 125, 235), "dark": (20, 85, 175), "light": (80, 165, 255)}, "yellow": {"main": (255, 205, 10), "dark": (210, 160, 0), "light": (255, 230, 80)}, "gold": {"main": (225, 170, 40), "dark": (170, 120, 20), "light": (245, 200, 80)}, + "silver": {"main": (185, 190, 200), "dark": (130, 135, 145), "light": (225, 230, 240)}, } def extract_cleaned_love_emblem(): @@ -186,7 +187,7 @@ def main(): fg = create_adaptive_foreground(sizes["adaptive"]) fg.save(os.path.join(drawable_dir, "ic_launcher_foreground.png"), "PNG") - for ver in ("red", "blue", "yellow", "gold"): + for ver in ("red", "blue", "yellow", "gold", "silver"): cart = render_3d_cartridge(ver, sizes["shortcut"]) cart.save(os.path.join(drawable_dir, f"ic_shortcut_{ver}.png"), "PNG") diff --git a/tools/make_blue_manifest.py b/tools/make_blue_manifest.py index 0e7adc22..cc85d760 100644 --- a/tools/make_blue_manifest.py +++ b/tools/make_blue_manifest.py @@ -113,7 +113,7 @@ def main(): blue = derive(red, pokered, os.path.abspath(args.symbols)) with open(args.out, "w", encoding="utf-8", newline="\n") as f: - json.dump(blue, f, ensure_ascii=False, indent=2, sort_keys=True) + json.dump(blue, f, ensure_ascii=True, indent=2, sort_keys=True) f.write("\n") print(f"wrote {args.out}") diff --git a/tools/make_rom_manifest.py b/tools/make_rom_manifest.py index 1b4ce7b4..3b46e01b 100755 --- a/tools/make_rom_manifest.py +++ b/tools/make_rom_manifest.py @@ -12,6 +12,7 @@ import argparse import json import os import re +import subprocess import sys import tempfile @@ -21,6 +22,43 @@ from extract import (battle_anims, constants, field, font, items, maps, # noqa: pokemon, sprites, text, tilesets, trainers, util) from rom_data import CANONICAL_RED_SHA1, SymbolTable # noqa: E402 +# The pret/pokered commit this manifest was last verified against. Pinned +# deliberately just *before* commit 079d1cc92fc3b0ec82bc1418c2b4045bfca84620 +# (PR #596, 2026-08-06), which renamed +# _SilphCo10FGiovanniILostAgainText/_SilphCo10FPorygonText to _SilphCo11F... +# -- data/scripts/victories.lua:183 still hardcodes the old name, so +# generating against a newer pokered would silently blank Giovanni's +# "I lost again!?" rematch line. Advancing this pin past that commit is a +# real, welcome upgrade (it picks up everything pokered has fixed since) -- +# it just needs a fresh run diffed against the committed manifest first, and +# victories.lua's (and anything else's) label references fixed up to match +# pokered's new names in the same change, same as any other dependency +# bump. Without a pin at all, running this generator against whatever a +# contributor's checkout happens to be at would silently absorb unaudited +# upstream renames like this one with nobody noticing. +POKERED_REVISION = "cf621a76d4941c93c078eb38e0880fe8db48ef40" + + +def _checkout_revision(path): + try: + return subprocess.run( + ["git", "rev-parse", "HEAD"], cwd=path, check=True, + capture_output=True, text=True).stdout.strip() + except (OSError, subprocess.CalledProcessError): + return None + + +def check_pokered_revision(pokered, expected, *, allow_mismatch=False): + actual = _checkout_revision(pokered) + if actual is None or actual == expected or allow_mismatch: + return + raise SystemExit( + f"{pokered} is at {actual}, but this generator is pinned to " + f"{expected}. Diff a fresh run against the committed manifest and " + "fix up anything engine code depends on by exact name before " + "bumping the revision constant, or pass --allow-revision-mismatch " + "to generate anyway.") + def simple_constants(pokered, relpath, stop_at=None): return util.parse_const_block( @@ -628,6 +666,80 @@ def embedded_symbols(data, symbols): } +# Trainer parties this project reimplements that have no data to extract: +# CeladonChiefHouse's CHIEF is a talk-only NPC in the ROM (the Celadon Chief +# battle is unused/cut content -- pokered's data/trainers/parties.asm has +# `ChiefData:` followed by `; none`, zero Pokemon). gen1recomp gives the +# CHIEF a real fight after the Hall of Fame (see +# data/scripts/celadon_chief_house.lua and src/import/RomExtractor.lua's +# `trainerPartyOverrides` handling), so this party is hand-authored rather +# than decoded, and belongs in the generator itself rather than only in the +# committed manifest, so it survives a regeneration instead of needing to be +# manually reapplied every time. +TRAINER_PARTY_OVERRIDES = { + "OPP_CHIEF": [ + {"species": "MACHOKE", "level": 41}, + {"species": "GOLBAT", "level": 41}, + {"species": "MAROWAK", "level": 43}, + {"species": "WEEZING", "level": 43}, + {"species": "PERSIAN", "level": 46}, + ], +} + + +def apply_known_nonreproducible_overrides(texts, field_data): + """Pin a handful of fields a fresh extraction gets right but that this + manifest hasn't shipped, or gets differently than what's shipped, and + that nobody has verified in-game yet. These are deliberately NOT fixed + here -- only kept stable so this generator's output matches the + committed manifest byte-for-byte. Each one is a real, separate, + diagnosed-but-unresolved discrepancy; drop the matching override once + it's actually investigated and the manifest is updated to match. + """ + # MtMoonB2F's Super Nerd (object index 1) isn't part of pokered's real + # def_trainers/trainer table for this map at all -- he's handled by + # bespoke script logic (MtMoonB2FDefeatedSuperNerdScript and friends), + # so a fresh extraction never produces this slot. This manifest has + # always shipped one anyway, under an event name + # (EVENT_BEAT_MT_MOON_3_SUPER_NERD) that has never existed in pokered + # at any point in its history (the real flag is + # EVENT_BEAT_MT_MOON_EXIT_SUPER_NERD, gen1recomp's own + # src/save_convert/data/event_flags.lua flag 1401) -- fabricated data, + # not pokered drift. OverworldState:trainerDefeated() checks + # Game.save.defeatedTrainers[npc.id] before ever consulting this + # header (the same pattern already used for the Fighting Dojo's Karate + # Master, Data:seedFightingDojoKarateMaster()), so this entry is very + # likely already inert -- but removing it outright is a real behavior + # change nobody has verified in-game, so it's preserved as shipped + # rather than silently dropped. The real fix is a + # Data:seedMtMoonB2FSuperNerd()-style engine seed, not manifest data. + texts["trainerHeaders"].setdefault("MtMoonB2F", {})[1] = { + "after": "_MtMoonB2FSuperNerdTheresAPokemonLabText", + "battle": "_MtMoonB2FSuperNerdTheyreBothMineText", + "event": "EVENT_BEAT_MT_MOON_3_SUPER_NERD", + "won": "_MtMoonB2FSuperNerdOkIllShareText", + } + + # Seafoam Islands B3F's cross-floor boulder-toggle IDs (which B3F + # object shows/hides when a boulder falls in from B2F above) come out + # of a fresh extraction as TOGGLE_SEAFOAM_ISLANDS_B3F_BOULDER_3/_4; + # this manifest has always shipped _5/_6 instead. Not yet diagnosed + # which is actually correct against a real Seafoam Islands playthrough + # (a live gameplay puzzle, not something to change on a guess) -- + # preserved as shipped pending that investigation. + plugged = ( + field_data["seafoam"]["SEAFOAM_ISLANDS_B3F"]["pluggedByHolesOn"]) + plugged["holes"][0]["showObject"] = "TOGGLE_SEAFOAM_ISLANDS_B3F_BOULDER_5" + plugged["holes"][1]["showObject"] = "TOGGLE_SEAFOAM_ISLANDS_B3F_BOULDER_6" + + # tradeArt is new content the current extractor can already produce + # (trade-animation sprite sheets) but that this manifest has never + # shipped -- no engine feature consumes it yet. Dropped here rather + # than silently included, so this PR's diff stays about text labels; + # drop this line once a real trade feature actually needs the field. + field_data.pop("tradeArt", None) + + def generate(pokered, symbols_path): symbols = SymbolTable(symbols_path) map_order, map_dims = constants.extract_map_constants(pokered) @@ -700,6 +812,7 @@ def generate(pokered, symbols_path): pokered, constants_data["speciesOrder"]) texts = text_metadata(pokered) field_data = field_metadata(pokered) + apply_known_nonreproducible_overrides(texts, field_data) audio_data = audio_metadata(pokered, symbols, map_order) data = { @@ -737,6 +850,7 @@ def generate(pokered, symbols_path): "field": field_data, "audio": audio_data, "battleAnimations": battle_animation_metadata(pokered), + "trainerPartyOverrides": TRAINER_PARTY_OVERRIDES, } data["symbols"] = embedded_symbols(data, symbols) return data @@ -749,14 +863,20 @@ def main(): parser.add_argument( "--out", default=os.path.join(os.path.dirname(__file__), "rom_manifest.json")) + parser.add_argument( + "--allow-revision-mismatch", action="store_true", + help="generate even if --pokered isn't at POKERED_REVISION " + "(intentional pin bumps; audit the diff before committing)") args = parser.parse_args() pokered = os.path.abspath(args.pokered) if not os.path.isfile(os.path.join(pokered, "main.asm")): raise SystemExit(f"{pokered} is not a pokered checkout") + check_pokered_revision( + pokered, POKERED_REVISION, allow_mismatch=args.allow_revision_mismatch) data = generate(pokered, os.path.abspath(args.symbols)) with open(args.out, "w", encoding="utf-8", newline="\n") as f: - json.dump(data, f, ensure_ascii=False, indent=2, sort_keys=True) + json.dump(data, f, ensure_ascii=True, indent=2, sort_keys=True) f.write("\n") print(f"wrote {args.out}") diff --git a/tools/make_silver_manifest.py b/tools/make_silver_manifest.py new file mode 100644 index 00000000..9b9c70f1 --- /dev/null +++ b/tools/make_silver_manifest.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""Derive the Pokemon Silver import manifest from the shipped Gold manifest. + +Gold and Silver are assembled from one pokegold source tree; every constant +block, charmap entry, map id, and symbol NAME is identical between the two +builds, and the edition differences (wild encounter tables, the alternate +front pics in gfx/pics_silver.asm, the title screen art, preset player +names, a handful of map texts) are all ROM data that the importer decodes +from the Silver cart at import time. So rather than re-parsing pokegold +from scratch like tools/make_gold_manifest.py does, this takes the shipped +Gold manifest verbatim and overrides only what genuinely differs: + + * romSha1 -- Silver's ROM hash (pret/pokegold's byte-exact build). + * symbols -- every symbol re-resolved from pokesilver.sym, because the + edition-selected data blocks have different sizes and shift + their neighbours (e.g. the compressed front pics). + +Usage: python3 tools/make_silver_manifest.py +Default paths: Gold manifest beside this script; symbols at +/Users/bryanbassett/Documents/development/pokegold-symbols/pokesilver.sym +(a pokegold checkout's own `make silver` build emits an identical one). +""" + +from __future__ import annotations + +import argparse +import copy +import json +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from rom_data import CANONICAL_SILVER_SHA1, SymbolTable # noqa: E402 + +DEV = "/Users/bryanbassett/Documents/development" +DEFAULT_GOLD = os.path.join(os.path.dirname(__file__), "rom_manifest_gold.json") +DEFAULT_OUT = os.path.join( + os.path.dirname(__file__), "rom_manifest_silver.json") +DEFAULT_SYMBOLS = os.path.join(DEV, "pokegold-symbols/pokesilver.sym") + + +def derive(gold, symbols_path): + """Return the Silver manifest derived from the Gold manifest dict.""" + silver = copy.deepcopy(gold) + silver["romSha1"] = CANONICAL_SILVER_SHA1 + + silver_symbols = SymbolTable(symbols_path) + resolved, missing = {}, [] + for name in gold["symbols"]: + symbol = silver_symbols.by_name.get(name) + if symbol is None: + missing.append(name) + continue + resolved[name] = [symbol.bank, symbol.address] + if missing: + raise SystemExit( + "pokesilver.sym is missing symbols the manifest needs: " + + ", ".join(sorted(missing)[:10]) + + (" ..." if len(missing) > 10 else "")) + silver["symbols"] = resolved + + # Sanity: the edition pic banks must actually have moved something. If + # every address matches Gold's, the --symbols file is almost certainly + # pokegold.sym, and the import would decode Gold data out of a Silver ROM. + if all(resolved[n] == gold["symbols"][n] for n in resolved): + raise SystemExit( + f"{symbols_path} resolves every symbol to Gold's address; " + "is it really pokesilver.sym?") + + return silver + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--gold", default=DEFAULT_GOLD, + help="shipped Gold manifest to derive from") + parser.add_argument("--symbols", default=DEFAULT_SYMBOLS, + help="pokesilver.sym symbol file") + parser.add_argument("--out", default=DEFAULT_OUT) + args = parser.parse_args() + + with open(args.gold, encoding="utf-8") as f: + gold = json.load(f) + + silver = derive(gold, os.path.abspath(args.symbols)) + with open(args.out, "w", encoding="utf-8", newline="\n") as f: + json.dump(silver, f, ensure_ascii=False, indent=2, sort_keys=True) + f.write("\n") + print(f"wrote {args.out}") + + +if __name__ == "__main__": + main() diff --git a/tools/make_yellow_manifest.py b/tools/make_yellow_manifest.py index de491f63..843f7d36 100755 --- a/tools/make_yellow_manifest.py +++ b/tools/make_yellow_manifest.py @@ -29,6 +29,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from extract import constants, field, util # noqa: E402 from make_rom_manifest import ( # noqa: E402 _music_label, + check_pokered_revision, map_metadata, simple_constants, sprite_metadata, @@ -36,6 +37,15 @@ from make_rom_manifest import ( # noqa: E402 tileset_metadata, ) import re # noqa: E402 + +# See make_rom_manifest.py's POKERED_REVISION comment for why this pin +# matters -- pokeyellow can drift the same way pokered did for the Silph Co +# 10F/11F rename. Verified against this commit: symbols/text/trainerHeaders/ +# trainerPartyOverrides all come out byte-for-byte identical to the +# committed rom_manifest_yellow.json (field.oldManBattle does not -- a +# separate, pre-existing, undiagnosed discrepancy unrelated to text labels, +# left alone the same way field.seafoam/tradeArt were for Red/Blue). +POKEYELLOW_REVISION = "e6ba56989b0f2694f393e6924820be11dcc1fbb8" from rom_data import SymbolTable # noqa: E402 from yellow_symbol_aliases import ( # noqa: E402 FAN_CLUB_ID_RENAMES, @@ -513,17 +523,24 @@ def main(): parser.add_argument("--symbols", default=DEFAULT_SYMBOLS, help="pokeyellow.sym symbol file") parser.add_argument("--out", default=DEFAULT_OUT) + parser.add_argument( + "--allow-revision-mismatch", action="store_true", + help="generate even if --pokeyellow isn't at POKEYELLOW_REVISION") args = parser.parse_args() pokeyellow = os.path.abspath(args.pokeyellow) if not os.path.isfile(os.path.join(pokeyellow, "main.asm")): raise SystemExit(f"{pokeyellow} is not a pokeyellow checkout") + if POKEYELLOW_REVISION is not None: + check_pokered_revision( + pokeyellow, POKEYELLOW_REVISION, + allow_mismatch=args.allow_revision_mismatch) with open(args.red, encoding="utf-8") as f: red = json.load(f) yellow, meta = derive(red, pokeyellow, os.path.abspath(args.symbols)) with open(args.out, "w", encoding="utf-8", newline="\n") as f: - json.dump(yellow, f, ensure_ascii=False, indent=2, sort_keys=True) + json.dump(yellow, f, ensure_ascii=True, indent=2, sort_keys=True) f.write("\n") print(f"wrote {args.out}") diff --git a/tools/modkit.py b/tools/modkit.py index 64b212d5..6b934c61 100644 --- a/tools/modkit.py +++ b/tools/modkit.py @@ -741,7 +741,7 @@ def imported_data_dir(repo): return path if _generated_data_dir_ok(path) else None version = (os.environ.get("POKEPORT_VERSION") or "red").lower() - if version not in ("red", "blue", "yellow"): + if version not in ("red", "blue", "yellow", "gold", "silver"): version = "red" candidates = [ @@ -988,14 +988,25 @@ def cmd_add_release_workflow(args, repo): # ---------------------------------------------------------------- lint def ahash(image): - """Ink-mask hash over the 8x8 downscale: background (the lightest GB - shade) vs ink. Swapping the three ink shades -- the classic recolor -- - leaves the mask intact, which is exactly what MK302 wants to catch.""" + """Ink-mask hash over the 8x8 downscale: background vs ink, split at + THIS image's own average brightness rather than a fixed shade. Swapping + the three ink shades -- the classic recolor -- leaves the mask intact, + which is exactly what MK302 wants to catch. + + A fixed cutoff (e.g. "<=200 is ink") only makes sense for sprites with a + light background to split against; a mostly-opaque 16x16 icon has almost + no pixel above that cutoff, so every such icon collapsed onto the same + "all ink" hash and was flagged as a near-duplicate of anything else that + also collapsed -- which was most of them, boulder.png included. + Thresholding against the image's own mean keeps the split meaningful + (and roughly balanced) no matter how light or dark the source is.""" from PIL import Image small = image.convert("L").resize((8, 8), Image.LANCZOS) raw = (small.get_flattened_data() if hasattr(small, "get_flattened_data") else small.getdata()) - return sum((1 << i) for i, p in enumerate(raw) if p <= 200) + raw = list(raw) + average = sum(raw) / len(raw) + return sum((1 << i) for i, p in enumerate(raw) if p <= average) def hamming(a, b): @@ -2393,7 +2404,8 @@ UPVALUE_CALL = re.compile( UPVALUE_ARGS = re.compile( r"""^\s*([A-Za-z_]\w*)\s*\.\s*(\w+)\s*,\s*["'](\w+)["']""") VERSION_MATCH = re.compile( - r"""[=~]=\s*["'](red|blue|yellow)["']|["'](red|blue|yellow)["']\s*[=~]=""") + r"""[=~]=\s*["'](red|blue|yellow|gold|silver)["']""" + r"""|["'](red|blue|yellow|gold|silver)["']\s*[=~]=""") def _line_of(body, offset): diff --git a/tools/rom_data.py b/tools/rom_data.py index c889723a..2e9375d5 100644 --- a/tools/rom_data.py +++ b/tools/rom_data.py @@ -11,8 +11,9 @@ from dataclasses import dataclass CANONICAL_RED_SHA1 = "ea9bcae617fdf159b045185467ae58b2e4a48b9a" CANONICAL_BLUE_SHA1 = "d7037c83e1ae5b39bde3c30787637ba1d4c48ce2" CANONICAL_YELLOW_SHA1 = "cc7d03262ebfaf2f06772c1a480c7d9d5f4a38e1" -# Gold is Gen 2: a 2 MiB cart, twice the size of the Gen 1 ROMs above. +# Gold and Silver are Gen 2: 2 MiB carts, twice the size of the Gen 1 ROMs above. CANONICAL_GOLD_SHA1 = "d8b8a3600a465308c9953dfa04f0081c05bdcb94" +CANONICAL_SILVER_SHA1 = "49b163f7e57702bc939d642a18f591de55d92dae" ROM_BANK_SIZE = 0x4000 diff --git a/tools/rom_manifest.json b/tools/rom_manifest.json index f40aa02c..55a4e8fa 100644 --- a/tools/rom_manifest.json +++ b/tools/rom_manifest.json @@ -4275,6 +4275,24 @@ "open": 14 } ], + "SILPH_CO_10F": [ + { + "block": 84, + "bx": 5, + "by": 4, + "event": "EVENT_SILPH_CO_10_UNLOCKED_DOOR", + "open": 14 + } + ], + "SILPH_CO_11F": [ + { + "block": 32, + "bx": 3, + "by": 6, + "event": "EVENT_SILPH_CO_11_UNLOCKED_DOOR", + "open": 3 + } + ], "SILPH_CO_2F": [ { "block": 84, @@ -4416,24 +4434,6 @@ "event": "EVENT_SILPH_CO_9_UNLOCKED_DOOR4", "open": 14 } - ], - "SILPH_CO_10F": [ - { - "block": 84, - "bx": 5, - "by": 4, - "event": "EVENT_SILPH_CO_10_UNLOCKED_DOOR", - "open": 14 - } - ], - "SILPH_CO_11F": [ - { - "block": 32, - "bx": 3, - "by": 6, - "event": "EVENT_SILPH_CO_11_UNLOCKED_DOOR", - "open": 3 - } ] }, "doorTiles": [ @@ -21758,6 +21758,18 @@ 24, 22665 ], + "SSAnneKitchenCook7EelsAuBarbecueText": [ + 32, + 21107 + ], + "SSAnneKitchenCook7PrimeBeefSteakText": [ + 32, + 21158 + ], + "SSAnneKitchenCook7SalmonDuSaladText": [ + 32, + 21043 + ], "SSAnneKitchen_h": [ 24, 22439 @@ -21970,6 +21982,18 @@ 21, 25848 ], + "SilphCo9FNurseDontGiveUpText": [ + 33, + 19513 + ], + "SilphCo9FNurseThankYouText": [ + 33, + 19528 + ], + "SilphCo9FNurseYouLookTiredText": [ + 33, + 19467 + ], "SilphCo9F_h": [ 23, 22447 @@ -22074,6 +22098,10 @@ 19, 20787 ], + "TMNotebookText": [ + 34, + 19453 + ], "TamerPic": [ 19, 23374 @@ -22358,6 +22386,18 @@ 13, 27425 ], + "ViridianCityFisherYouCanHaveThisText": [ + 41, + 17898 + ], + "ViridianCityYoungster2CaterpieAndWeedleDescriptionText": [ + 41, + 17627 + ], + "ViridianCityYoungster2OkThenText": [ + 41, + 17613 + ], "ViridianCity_h": [ 6, 17239 @@ -34071,7 +34111,17 @@ ] }, "labels": [ + "SSAnneKitchenCook7EelsAuBarbecueText", + "SSAnneKitchenCook7PrimeBeefSteakText", + "SSAnneKitchenCook7SalmonDuSaladText", "SilphCo2FSilphWorkerFPleaseTakeThisText", + "SilphCo9FNurseDontGiveUpText", + "SilphCo9FNurseThankYouText", + "SilphCo9FNurseYouLookTiredText", + "TMNotebookText", + "ViridianCityFisherYouCanHaveThisText", + "ViridianCityYoungster2CaterpieAndWeedleDescriptionText", + "ViridianCityYoungster2OkThenText", "_AIBattleUseItemText", "_AIBattleWithdrawText", "_AbandonLearningText", diff --git a/tools/rom_manifest_blue.json b/tools/rom_manifest_blue.json index f198ca5a..555b1fa6 100644 --- a/tools/rom_manifest_blue.json +++ b/tools/rom_manifest_blue.json @@ -21735,6 +21735,18 @@ 24, 22665 ], + "SSAnneKitchenCook7EelsAuBarbecueText": [ + 32, + 21107 + ], + "SSAnneKitchenCook7PrimeBeefSteakText": [ + 32, + 21158 + ], + "SSAnneKitchenCook7SalmonDuSaladText": [ + 32, + 21043 + ], "SSAnneKitchen_h": [ 24, 22439 @@ -21947,6 +21959,18 @@ 21, 25848 ], + "SilphCo9FNurseDontGiveUpText": [ + 33, + 19513 + ], + "SilphCo9FNurseThankYouText": [ + 33, + 19528 + ], + "SilphCo9FNurseYouLookTiredText": [ + 33, + 19467 + ], "SilphCo9F_h": [ 23, 22447 @@ -22051,6 +22075,10 @@ 19, 20787 ], + "TMNotebookText": [ + 34, + 19453 + ], "TamerPic": [ 19, 23374 @@ -22335,6 +22363,18 @@ 13, 27425 ], + "ViridianCityFisherYouCanHaveThisText": [ + 41, + 17898 + ], + "ViridianCityYoungster2CaterpieAndWeedleDescriptionText": [ + 41, + 17627 + ], + "ViridianCityYoungster2OkThenText": [ + 41, + 17613 + ], "ViridianCity_h": [ 6, 17239 @@ -34048,7 +34088,17 @@ ] }, "labels": [ + "SSAnneKitchenCook7EelsAuBarbecueText", + "SSAnneKitchenCook7PrimeBeefSteakText", + "SSAnneKitchenCook7SalmonDuSaladText", "SilphCo2FSilphWorkerFPleaseTakeThisText", + "SilphCo9FNurseDontGiveUpText", + "SilphCo9FNurseThankYouText", + "SilphCo9FNurseYouLookTiredText", + "TMNotebookText", + "ViridianCityFisherYouCanHaveThisText", + "ViridianCityYoungster2CaterpieAndWeedleDescriptionText", + "ViridianCityYoungster2OkThenText", "_AIBattleUseItemText", "_AIBattleWithdrawText", "_AbandonLearningText", diff --git a/tools/rom_manifest_silver.json b/tools/rom_manifest_silver.json new file mode 100644 index 00000000..2992385f --- /dev/null +++ b/tools/rom_manifest_silver.json @@ -0,0 +1,17702 @@ +{ + "charmap": { + "0": "", + "10": "ザ", + "100": "", + "101": "", + "102": "", + "103": "", + "104": "", + "105": "", + "106": "", + "107": "", + "108": "", + "109": "", + "11": "ジ", + "110": "ぃ", + "111": "ぅ", + "112": "", + "113": "", + "114": "“", + "115": "”", + "116": "·", + "117": "…", + "118": "ぁ", + "119": "ぇ", + "12": "ズ", + "120": "ぉ", + "121": "┌", + "122": "─", + "123": "┐", + "124": "│", + "125": "└", + "126": "┘", + "127": " ", + "128": "A", + "129": "B", + "13": "ゼ", + "130": "C", + "131": "D", + "132": "E", + "133": "F", + "134": "G", + "135": "H", + "136": "I", + "137": "J", + "138": "K", + "139": "L", + "14": "ゾ", + "140": "M", + "141": "N", + "142": "O", + "143": "P", + "144": "Q", + "145": "R", + "146": "S", + "147": "T", + "148": "U", + "149": "V", + "15": "ダ", + "150": "W", + "151": "X", + "152": "Y", + "153": "Z", + "154": "(", + "155": ")", + "156": ":", + "157": ";", + "158": "[", + "159": "]", + "16": "ヂ", + "160": "a", + "161": "b", + "162": "c", + "163": "d", + "164": "e", + "165": "f", + "166": "g", + "167": "h", + "168": "i", + "169": "j", + "17": "ヅ", + "170": "k", + "171": "l", + "172": "m", + "173": "n", + "174": "o", + "175": "p", + "176": "q", + "177": "r", + "178": "s", + "179": "t", + "18": "デ", + "180": "u", + "181": "v", + "182": "w", + "183": "x", + "184": "y", + "185": "z", + "186": "こ", + "187": "さ", + "188": "し", + "189": "す", + "19": "ド", + "190": "せ", + "191": "そ", + "192": "Ä", + "193": "Ö", + "194": "Ü", + "195": "ä", + "196": "ö", + "197": "ü", + "198": "に", + "199": "ぬ", + "20": "", + "200": "ね", + "201": "の", + "202": "は", + "203": "ひ", + "204": "ふ", + "205": "へ", + "206": "ほ", + "207": "ま", + "208": "'d", + "209": "'l", + "210": "'m", + "211": "'r", + "212": "'s", + "213": "'t", + "214": "'v", + "215": "ら", + "216": "り", + "217": "る", + "218": "れ", + "219": "ろ", + "22": "", + "220": "わ", + "221": "を", + "222": "ん", + "223": "←", + "224": "'", + "225": "", + "226": "", + "227": "-", + "228": "゚", + "229": "゙", + "230": "?", + "231": "!", + "232": ".", + "233": "&", + "234": "é", + "235": "→", + "236": "▷", + "237": "▶", + "238": "▼", + "239": "♂", + "24": "", + "240": "¥", + "241": "×", + "242": ".", + "243": "/", + "244": ",", + "245": "♀", + "246": "0", + "247": "1", + "248": "2", + "249": "3", + "25": "バ", + "250": "4", + "251": "5", + "252": "6", + "253": "7", + "254": "8", + "255": "9", + "26": "ビ", + "27": "ブ", + "28": "ボ", + "29": "", + "30": "", + "31": "", + "34": "", + "35": "", + "36": "", + "37": "", + "38": "が", + "39": "ぎ", + "40": "ぐ", + "41": "げ", + "42": "ご", + "43": "ざ", + "44": "じ", + "45": "ず", + "46": "ぜ", + "47": "ぞ", + "48": "だ", + "49": "ぢ", + "5": "ガ", + "50": "づ", + "51": "で", + "52": "ど", + "53": "", + "54": "", + "55": "", + "56": "", + "57": "", + "58": "ば", + "59": "び", + "6": "ギ", + "60": "ぶ", + "61": "べ", + "62": "ぼ", + "63": "", + "64": "パ", + "65": "ピ", + "66": "プ", + "67": "ポ", + "68": "ぱ", + "69": "ぴ", + "7": "グ", + "70": "ぷ", + "71": "ぺ", + "72": "ぽ", + "73": "", + "74": "", + "75": "<_CONT>", + "76": "", + "78": "", + "79": "", + "8": "ゲ", + "80": "@", + "81": "", + "82": "", + "83": "", + "84": "POKé", + "85": "", + "86": "<……>", + "87": "", + "88": "", + "89": "", + "9": "ゴ", + "90": "", + "91": "", + "92": "", + "93": "", + "94": "", + "95": "", + "96": "", + "97": "", + "98": "", + "99": "" + }, + "constants": { + "battleAnimBgPaletteOrder": [ + "PAL_BATTLE_BG_PLAYER", + "PAL_BATTLE_BG_ENEMY", + "PAL_BATTLE_BG_ENEMY_HP", + "PAL_BATTLE_BG_PLAYER_HP", + "PAL_BATTLE_BG_EXP", + "PAL_BATTLE_BG_5", + "PAL_BATTLE_BG_6", + "PAL_BATTLE_BG_TEXT" + ], + "battleAnimFramesetOrder": [ + "BATTLE_ANIM_FRAMESET_HIT_BIG", + "BATTLE_ANIM_FRAMESET_HIT", + "BATTLE_ANIM_FRAMESET_HIT_SMALL", + "BATTLE_ANIM_FRAMESET_PUNCH", + "BATTLE_ANIM_FRAMESET_KICK", + "BATTLE_ANIM_FRAMESET_PALM", + "BATTLE_ANIM_FRAMESET_FANG", + "BATTLE_ANIM_FRAMESET_PUNCH_SHAKE", + "BATTLE_ANIM_FRAMESET_BALL_POOF", + "BATTLE_ANIM_FRAMESET_POKE_BALL_1", + "BATTLE_ANIM_FRAMESET_POKE_BALL_2", + "BATTLE_ANIM_FRAMESET_POKE_BALL_3", + "BATTLE_ANIM_FRAMESET_POKE_BALL_4", + "BATTLE_ANIM_FRAMESET_POKE_BALL_5", + "BATTLE_ANIM_FRAMESET_DRAGON_RAGE", + "BATTLE_ANIM_FRAMESET_FLAMETHROWER", + "BATTLE_ANIM_FRAMESET_EMBER", + "BATTLE_ANIM_FRAMESET_BURNED", + "BATTLE_ANIM_FRAMESET_BLIZZARD", + "BATTLE_ANIM_FRAMESET_ICE", + "BATTLE_ANIM_FRAMESET_ICE_BEAM", + "BATTLE_ANIM_FRAMESET_POWDER_SNOW", + "BATTLE_ANIM_FRAMESET_RAZOR_LEAF_1", + "BATTLE_ANIM_FRAMESET_RAZOR_LEAF_2", + "BATTLE_ANIM_FRAMESET_EXPLOSION", + "BATTLE_ANIM_FRAMESET_BIG_ROCK", + "BATTLE_ANIM_FRAMESET_SMALL_ROCK", + "BATTLE_ANIM_FRAMESET_STRENGTH", + "BATTLE_ANIM_FRAMESET_SKULL_CROSSBONE", + "BATTLE_ANIM_FRAMESET_ACID", + "BATTLE_ANIM_FRAMESET_POISON_DROPLET_UNUSED", + "BATTLE_ANIM_FRAMESET_SLUDGE_BUBBLE", + "BATTLE_ANIM_FRAMESET_SLUDGE_BUBBLE_BURST", + "BATTLE_ANIM_FRAMESET_SMALL_BUBBLE", + "BATTLE_ANIM_FRAMESET_PULSING_BUBBLE", + "BATTLE_ANIM_FRAMESET_SURF", + "BATTLE_ANIM_FRAMESET_MUSIC_NOTE_1", + "BATTLE_ANIM_FRAMESET_MUSIC_NOTE_2", + "BATTLE_ANIM_FRAMESET_MUSIC_NOTE_3", + "BATTLE_ANIM_FRAMESET_WATER_GUN_1", + "BATTLE_ANIM_FRAMESET_WATER_GUN_2", + "BATTLE_ANIM_FRAMESET_WATER_GUN_3", + "BATTLE_ANIM_FRAMESET_HYDRO_PUMP", + "BATTLE_ANIM_FRAMESET_POWDER", + "BATTLE_ANIM_FRAMESET_BEAM", + "BATTLE_ANIM_FRAMESET_BEAM_TIP", + "BATTLE_ANIM_FRAMESET_ICE_BUILDUP", + "BATTLE_ANIM_FRAMESET_FROZEN", + "BATTLE_ANIM_FRAMESET_CIRCLING_SPARKLE", + "BATTLE_ANIM_FRAMESET_THUNDER_CENTER", + "BATTLE_ANIM_FRAMESET_THUNDER_LEFT", + "BATTLE_ANIM_FRAMESET_THUNDER_RIGHT", + "BATTLE_ANIM_FRAMESET_THUNDER_WAVE_DISABLE", + "BATTLE_ANIM_FRAMESET_THUNDER_WAVE_EXTRA", + "BATTLE_ANIM_FRAMESET_THUNDERBOLT_SPARKS", + "BATTLE_ANIM_FRAMESET_THUNDERBOLT_CORE", + "BATTLE_ANIM_FRAMESET_THUNDERSHOCK_SPARKS", + "BATTLE_ANIM_FRAMESET_THUNDERSHOCK_CORE", + "BATTLE_ANIM_FRAMESET_CLAMP", + "BATTLE_ANIM_FRAMESET_CLAMP_FLIPPED", + "BATTLE_ANIM_FRAMESET_BITE_1", + "BATTLE_ANIM_FRAMESET_BITE_2", + "BATTLE_ANIM_FRAMESET_CUT_DOWN_LEFT", + "BATTLE_ANIM_FRAMESET_CUT_DOWN_RIGHT", + "BATTLE_ANIM_FRAMESET_CUT_UP_RIGHT", + "BATTLE_ANIM_FRAMESET_CUT_LONG_DOWN_LEFT", + "BATTLE_ANIM_FRAMESET_CUT_LONG_DOWN_RIGHT", + "BATTLE_ANIM_FRAMESET_CHARGE_ORB_1", + "BATTLE_ANIM_FRAMESET_ABSORB_CENTER", + "BATTLE_ANIM_FRAMESET_GUST", + "BATTLE_ANIM_FRAMESET_VINE_WHIP_1", + "BATTLE_ANIM_FRAMESET_VINE_WHIP_2", + "BATTLE_ANIM_FRAMESET_RAZOR_WIND_1", + "BATTLE_ANIM_FRAMESET_RAZOR_WIND_2", + "BATTLE_ANIM_FRAMESET_SONICBOOM_JP", + "BATTLE_ANIM_FRAMESET_WARP", + "BATTLE_ANIM_FRAMESET_CHARGE_ORB_2", + "BATTLE_ANIM_FRAMESET_EGG", + "BATTLE_ANIM_FRAMESET_EGG_WOBBLE", + "BATTLE_ANIM_FRAMESET_EGG_CRACKED_TOP", + "BATTLE_ANIM_FRAMESET_EGG_CRACKED_BOTTOM", + "BATTLE_ANIM_FRAMESET_FOCUS", + "BATTLE_ANIM_FRAMESET_BIND_1", + "BATTLE_ANIM_FRAMESET_BIND_2", + "BATTLE_ANIM_FRAMESET_BIND_3", + "BATTLE_ANIM_FRAMESET_BIND_4", + "BATTLE_ANIM_FRAMESET_LEECH_SEED_1", + "BATTLE_ANIM_FRAMESET_LEECH_SEED_2", + "BATTLE_ANIM_FRAMESET_LEECH_SEED_3", + "BATTLE_ANIM_FRAMESET_SOUND_1", + "BATTLE_ANIM_FRAMESET_SOUND_2", + "BATTLE_ANIM_FRAMESET_SOUND_3", + "BATTLE_ANIM_FRAMESET_WAVE", + "BATTLE_ANIM_FRAMESET_CONFUSE_RAY_1", + "BATTLE_ANIM_FRAMESET_CONFUSE_RAY_2", + "BATTLE_ANIM_FRAMESET_LEER", + "BATTLE_ANIM_FRAMESET_REFLECT", + "BATTLE_ANIM_FRAMESET_CHICK_1", + "BATTLE_ANIM_FRAMESET_CHICK_2", + "BATTLE_ANIM_FRAMESET_AMNESIA_1", + "BATTLE_ANIM_FRAMESET_AMNESIA_2", + "BATTLE_ANIM_FRAMESET_AMNESIA_3", + "BATTLE_ANIM_FRAMESET_ASLEEP", + "BATTLE_ANIM_FRAMESET_DIG_SAND", + "BATTLE_ANIM_FRAMESET_DIG_PILE", + "BATTLE_ANIM_FRAMESET_SAND", + "BATTLE_ANIM_FRAMESET_STRING_SHOT_1", + "BATTLE_ANIM_FRAMESET_STRING_SHOT_2", + "BATTLE_ANIM_FRAMESET_STRING_SHOT_3", + "BATTLE_ANIM_FRAMESET_PARALYZED", + "BATTLE_ANIM_FRAMESET_PARALYZED_FLIPPED", + "BATTLE_ANIM_FRAMESET_HAZE", + "BATTLE_ANIM_FRAMESET_MIST", + "BATTLE_ANIM_FRAMESET_HORN", + "BATTLE_ANIM_FRAMESET_NEEDLE", + "BATTLE_ANIM_FRAMESET_FLOWER", + "BATTLE_ANIM_FRAMESET_BARRAGE_BALL", + "BATTLE_ANIM_FRAMESET_PAY_DAY", + "BATTLE_ANIM_FRAMESET_HEART", + "BATTLE_ANIM_FRAMESET_SPINNING_BONE", + "BATTLE_ANIM_FRAMESET_STAR", + "BATTLE_ANIM_FRAMESET_SPOON", + "BATTLE_ANIM_FRAMESET_SPARKLE", + "BATTLE_ANIM_FRAMESET_SKY_ATTACK", + "BATTLE_ANIM_FRAMESET_LICK", + "BATTLE_ANIM_FRAMESET_WITHDRAW_SHELL", + "BATTLE_ANIM_FRAMESET_SHRINKING_CHARGE_ORB", + "BATTLE_ANIM_FRAMESET_CONVERSION", + "BATTLE_ANIM_FRAMESET_SWORD", + "BATTLE_ANIM_FRAMESET_SPEED_LINE_1", + "BATTLE_ANIM_FRAMESET_SPEED_LINE_2", + "BATTLE_ANIM_FRAMESET_SPEED_LINE_3", + "BATTLE_ANIM_FRAMESET_SEISMIC_TOSS", + "BATTLE_ANIM_FRAMESET_SHARPEN", + "BATTLE_ANIM_FRAMESET_DEFENSE_CURL", + "BATTLE_ANIM_FRAMESET_METRONOME_HAND", + "BATTLE_ANIM_FRAMESET_AGILITY", + "BATTLE_ANIM_FRAMESET_COTTON", + "BATTLE_ANIM_FRAMESET_MILK_BOTTLE", + "BATTLE_ANIM_FRAMESET_SPIKE", + "BATTLE_ANIM_FRAMESET_ANGER_VEIN", + "BATTLE_ANIM_FRAMESET_HEAL_BELL", + "BATTLE_ANIM_FRAMESET_BATON_PASS", + "BATTLE_ANIM_FRAMESET_LOCK_ON_1", + "BATTLE_ANIM_FRAMESET_LOCK_ON_2", + "BATTLE_ANIM_FRAMESET_LOCK_ON_3", + "BATTLE_ANIM_FRAMESET_LOCK_ON_4", + "BATTLE_ANIM_FRAMESET_MIND_READER_1", + "BATTLE_ANIM_FRAMESET_MIND_READER_2", + "BATTLE_ANIM_FRAMESET_MIND_READER_3", + "BATTLE_ANIM_FRAMESET_MIND_READER_4", + "BATTLE_ANIM_FRAMESET_SAFEGUARD", + "BATTLE_ANIM_FRAMESET_MEGAPHONE_UNUSED", + "BATTLE_ANIM_FRAMESET_ITEM_BAG", + "BATTLE_ANIM_FRAMESET_SPIDER_WEB", + "BATTLE_ANIM_FRAMESET_UNUSED_CAKE", + "BATTLE_ANIM_FRAMESET_IMP", + "BATTLE_ANIM_FRAMESET_IMP_FLIPPED", + "BATTLE_ANIM_FRAMESET_CHERUB", + "BATTLE_ANIM_FRAMESET_PENCIL", + "BATTLE_ANIM_FRAMESET_ENCORE_HAND", + "BATTLE_ANIM_FRAMESET_ENCORE_HAND_FLIPPED", + "BATTLE_ANIM_FRAMESET_DESTINY_BOND", + "BATTLE_ANIM_FRAMESET_MORNING_SUN", + "BATTLE_ANIM_FRAMESET_GLIMMER", + "BATTLE_ANIM_FRAMESET_MOONLIGHT", + "BATTLE_ANIM_FRAMESET_CROSS_CHOP_1", + "BATTLE_ANIM_FRAMESET_CROSS_CHOP_2", + "BATTLE_ANIM_FRAMESET_ZAP_CANNON", + "BATTLE_ANIM_FRAMESET_CURSE_NAIL", + "BATTLE_ANIM_FRAMESET_FORESIGHT_SHINE", + "BATTLE_ANIM_FRAMESET_RAPID_SPIN", + "BATTLE_ANIM_FRAMESET_SWAGGER", + "BATTLE_ANIM_FRAMESET_MEAN_LOOK", + "BATTLE_ANIM_FRAMESET_UPSIDE_DOWN_PUNCH", + "BATTLE_ANIM_FRAMESET_GROWING_SPARKLE", + "BATTLE_ANIM_FRAMESET_RAIN", + "BATTLE_ANIM_FRAMESET_PSYCH_UP", + "BATTLE_ANIM_FRAMESET_SPIRAL_UNUSED", + "BATTLE_ANIM_FRAMESET_AEROBLAST", + "BATTLE_ANIM_FRAMESET_SANDSTORM", + "BATTLE_ANIM_FRAMESET_ENEMYFEET_1ROW", + "BATTLE_ANIM_FRAMESET_PLAYERHEAD_1ROW", + "BATTLE_ANIM_FRAMESET_ENEMYFEET_2ROW", + "BATTLE_ANIM_FRAMESET_PLAYERHEAD_2ROW" + ], + "battleAnimFuncOrder": [ + "BATTLE_ANIM_FUNC_NULL", + "BATTLE_ANIM_FUNC_USER_TO_TARGET", + "BATTLE_ANIM_FUNC_USER_TO_TARGET_DISAPPEAR", + "BATTLE_ANIM_FUNC_MOVE_IN_CIRCLE", + "BATTLE_ANIM_FUNC_WAVE_TO_TARGET", + "BATTLE_ANIM_FUNC_THROW_TO_TARGET", + "BATTLE_ANIM_FUNC_THROW_TO_TARGET_DISAPPEAR", + "BATTLE_ANIM_FUNC_DROP", + "BATTLE_ANIM_FUNC_USER_TO_TARGET_SPIN", + "BATTLE_ANIM_FUNC_SHAKE", + "BATTLE_ANIM_FUNC_FIRE_BLAST", + "BATTLE_ANIM_FUNC_RAZOR_LEAF", + "BATTLE_ANIM_FUNC_BUBBLE", + "BATTLE_ANIM_FUNC_SURF", + "BATTLE_ANIM_FUNC_SING", + "BATTLE_ANIM_FUNC_WATER_GUN", + "BATTLE_ANIM_FUNC_EMBER", + "BATTLE_ANIM_FUNC_POWDER", + "BATTLE_ANIM_FUNC_POKEBALL", + "BATTLE_ANIM_FUNC_POKEBALL_BLOCKED", + "BATTLE_ANIM_FUNC_RECOVER", + "BATTLE_ANIM_FUNC_THUNDER_WAVE", + "BATTLE_ANIM_FUNC_CLAMP_ENCORE", + "BATTLE_ANIM_FUNC_BITE", + "BATTLE_ANIM_FUNC_SOLAR_BEAM", + "BATTLE_ANIM_FUNC_GUST", + "BATTLE_ANIM_FUNC_RAZOR_WIND", + "BATTLE_ANIM_FUNC_KICK", + "BATTLE_ANIM_FUNC_ABSORB", + "BATTLE_ANIM_FUNC_EGG", + "BATTLE_ANIM_FUNC_MOVE_UP", + "BATTLE_ANIM_FUNC_WRAP", + "BATTLE_ANIM_FUNC_LEECH_SEED", + "BATTLE_ANIM_FUNC_SOUND", + "BATTLE_ANIM_FUNC_CONFUSE_RAY", + "BATTLE_ANIM_FUNC_DIZZY", + "BATTLE_ANIM_FUNC_AMNESIA", + "BATTLE_ANIM_FUNC_FLOAT_UP", + "BATTLE_ANIM_FUNC_DIG", + "BATTLE_ANIM_FUNC_STRING", + "BATTLE_ANIM_FUNC_PARALYZED", + "BATTLE_ANIM_FUNC_SPIRAL_DESCENT", + "BATTLE_ANIM_FUNC_POISON_GAS", + "BATTLE_ANIM_FUNC_HORN", + "BATTLE_ANIM_FUNC_NEEDLE", + "BATTLE_ANIM_FUNC_PETAL_DANCE", + "BATTLE_ANIM_FUNC_THIEF_PAYDAY", + "BATTLE_ANIM_FUNC_ABSORB_CIRCLE", + "BATTLE_ANIM_FUNC_BONEMERANG", + "BATTLE_ANIM_FUNC_SHINY", + "BATTLE_ANIM_FUNC_SKY_ATTACK", + "BATTLE_ANIM_FUNC_GROWTH_SWORDS_DANCE", + "BATTLE_ANIM_FUNC_SMOKE_FLAME_WHEEL", + "BATTLE_ANIM_FUNC_PRESENT_SMOKESCREEN", + "BATTLE_ANIM_FUNC_STRENGTH_SEISMIC_TOSS", + "BATTLE_ANIM_FUNC_SPEED_LINE", + "BATTLE_ANIM_FUNC_SLUDGE", + "BATTLE_ANIM_FUNC_METRONOME_HAND", + "BATTLE_ANIM_FUNC_METRONOME_SPARKLE_SKETCH", + "BATTLE_ANIM_FUNC_AGILITY", + "BATTLE_ANIM_FUNC_SACRED_FIRE", + "BATTLE_ANIM_FUNC_SAFEGUARD_PROTECT", + "BATTLE_ANIM_FUNC_LOCK_ON_MIND_READER", + "BATTLE_ANIM_FUNC_SPIKES", + "BATTLE_ANIM_FUNC_HEAL_BELL_NOTES", + "BATTLE_ANIM_FUNC_BATON_PASS", + "BATTLE_ANIM_FUNC_CONVERSION", + "BATTLE_ANIM_FUNC_ENCORE_BELLY_DRUM", + "BATTLE_ANIM_FUNC_SWAGGER_MORNING_SUN", + "BATTLE_ANIM_FUNC_HIDDEN_POWER", + "BATTLE_ANIM_FUNC_CURSE", + "BATTLE_ANIM_FUNC_PERISH_SONG", + "BATTLE_ANIM_FUNC_RAPID_SPIN", + "BATTLE_ANIM_FUNC_BETA_PURSUIT", + "BATTLE_ANIM_FUNC_RAIN_SANDSTORM", + "BATTLE_ANIM_FUNC_BATTLE_ANIM_OBJ_B0", + "BATTLE_ANIM_FUNC_PSYCH_UP", + "BATTLE_ANIM_FUNC_ANCIENT_POWER", + "BATTLE_ANIM_FUNC_ROCK_SMASH", + "BATTLE_ANIM_FUNC_COTTON" + ], + "battleAnimGfxOrder": [ + "BATTLE_ANIM_GFX_NONE", + "BATTLE_ANIM_GFX_HIT", + "BATTLE_ANIM_GFX_CUT", + "BATTLE_ANIM_GFX_FIRE", + "BATTLE_ANIM_GFX_WATER", + "BATTLE_ANIM_GFX_LIGHTNING", + "BATTLE_ANIM_GFX_PLANT", + "BATTLE_ANIM_GFX_SMOKE", + "BATTLE_ANIM_GFX_EXPLOSION", + "BATTLE_ANIM_GFX_ROCKS", + "BATTLE_ANIM_GFX_ICE", + "BATTLE_ANIM_GFX_POKE_BALL", + "BATTLE_ANIM_GFX_POISON", + "BATTLE_ANIM_GFX_BUBBLE", + "BATTLE_ANIM_GFX_NOISE", + "BATTLE_ANIM_GFX_POWDER", + "BATTLE_ANIM_GFX_BEAM", + "BATTLE_ANIM_GFX_SPEED", + "BATTLE_ANIM_GFX_CHARGE", + "BATTLE_ANIM_GFX_WIND", + "BATTLE_ANIM_GFX_WHIP", + "BATTLE_ANIM_GFX_EGG", + "BATTLE_ANIM_GFX_ROPE", + "BATTLE_ANIM_GFX_PSYCHIC", + "BATTLE_ANIM_GFX_REFLECT", + "BATTLE_ANIM_GFX_STATUS", + "BATTLE_ANIM_GFX_SAND", + "BATTLE_ANIM_GFX_WEB", + "BATTLE_ANIM_GFX_HAZE", + "BATTLE_ANIM_GFX_HORN", + "BATTLE_ANIM_GFX_FLOWER", + "BATTLE_ANIM_GFX_MISC", + "BATTLE_ANIM_GFX_SKY_ATTACK", + "BATTLE_ANIM_GFX_GLOBE", + "BATTLE_ANIM_GFX_SHAPES", + "BATTLE_ANIM_GFX_OBJECTS", + "BATTLE_ANIM_GFX_SHINE", + "BATTLE_ANIM_GFX_ANGELS", + "BATTLE_ANIM_GFX_WAVE", + "BATTLE_ANIM_GFX_AEROBLAST", + "BATTLE_ANIM_GFX_PLAYERHEAD", + "BATTLE_ANIM_GFX_ENEMYFEET" + ], + "battleAnimOamsetOrder": [ + "BATTLE_ANIM_OAMSET_00", + "BATTLE_ANIM_OAMSET_01", + "BATTLE_ANIM_OAMSET_02", + "BATTLE_ANIM_OAMSET_03", + "BATTLE_ANIM_OAMSET_04", + "BATTLE_ANIM_OAMSET_05", + "BATTLE_ANIM_OAMSET_06", + "BATTLE_ANIM_OAMSET_07", + "BATTLE_ANIM_OAMSET_08", + "BATTLE_ANIM_OAMSET_09", + "BATTLE_ANIM_OAMSET_0A", + "BATTLE_ANIM_OAMSET_0B", + "BATTLE_ANIM_OAMSET_0C", + "BATTLE_ANIM_OAMSET_0D", + "BATTLE_ANIM_OAMSET_0E", + "BATTLE_ANIM_OAMSET_0F", + "BATTLE_ANIM_OAMSET_10", + "BATTLE_ANIM_OAMSET_11", + "BATTLE_ANIM_OAMSET_12", + "BATTLE_ANIM_OAMSET_13", + "BATTLE_ANIM_OAMSET_14", + "BATTLE_ANIM_OAMSET_15", + "BATTLE_ANIM_OAMSET_16", + "BATTLE_ANIM_OAMSET_17", + "BATTLE_ANIM_OAMSET_18", + "BATTLE_ANIM_OAMSET_19", + "BATTLE_ANIM_OAMSET_1A", + "BATTLE_ANIM_OAMSET_1B", + "BATTLE_ANIM_OAMSET_1C", + "BATTLE_ANIM_OAMSET_1D", + "BATTLE_ANIM_OAMSET_1E", + "BATTLE_ANIM_OAMSET_1F", + "BATTLE_ANIM_OAMSET_20", + "BATTLE_ANIM_OAMSET_21", + "BATTLE_ANIM_OAMSET_22", + "BATTLE_ANIM_OAMSET_23", + "BATTLE_ANIM_OAMSET_24", + "BATTLE_ANIM_OAMSET_25", + "BATTLE_ANIM_OAMSET_26", + "BATTLE_ANIM_OAMSET_27", + "BATTLE_ANIM_OAMSET_28", + "BATTLE_ANIM_OAMSET_29", + "BATTLE_ANIM_OAMSET_2A", + "BATTLE_ANIM_OAMSET_2B", + "BATTLE_ANIM_OAMSET_2C", + "BATTLE_ANIM_OAMSET_2D", + "BATTLE_ANIM_OAMSET_2E", + "BATTLE_ANIM_OAMSET_2F", + "BATTLE_ANIM_OAMSET_30", + "BATTLE_ANIM_OAMSET_31", + "BATTLE_ANIM_OAMSET_32", + "BATTLE_ANIM_OAMSET_33", + "BATTLE_ANIM_OAMSET_34", + "BATTLE_ANIM_OAMSET_35", + "BATTLE_ANIM_OAMSET_36", + "BATTLE_ANIM_OAMSET_37", + "BATTLE_ANIM_OAMSET_38", + "BATTLE_ANIM_OAMSET_39", + "BATTLE_ANIM_OAMSET_3A", + "BATTLE_ANIM_OAMSET_3B", + "BATTLE_ANIM_OAMSET_3C", + "BATTLE_ANIM_OAMSET_3D", + "BATTLE_ANIM_OAMSET_3E", + "BATTLE_ANIM_OAMSET_3F", + "BATTLE_ANIM_OAMSET_40", + "BATTLE_ANIM_OAMSET_41", + "BATTLE_ANIM_OAMSET_42", + "BATTLE_ANIM_OAMSET_43", + "BATTLE_ANIM_OAMSET_44", + "BATTLE_ANIM_OAMSET_45", + "BATTLE_ANIM_OAMSET_46", + "BATTLE_ANIM_OAMSET_47", + "BATTLE_ANIM_OAMSET_48", + "BATTLE_ANIM_OAMSET_49", + "BATTLE_ANIM_OAMSET_4A", + "BATTLE_ANIM_OAMSET_4B", + "BATTLE_ANIM_OAMSET_4C", + "BATTLE_ANIM_OAMSET_4D", + "BATTLE_ANIM_OAMSET_4E", + "BATTLE_ANIM_OAMSET_4F", + "BATTLE_ANIM_OAMSET_50", + "BATTLE_ANIM_OAMSET_51", + "BATTLE_ANIM_OAMSET_52", + "BATTLE_ANIM_OAMSET_53", + "BATTLE_ANIM_OAMSET_54", + "BATTLE_ANIM_OAMSET_55", + "BATTLE_ANIM_OAMSET_56", + "BATTLE_ANIM_OAMSET_57", + "BATTLE_ANIM_OAMSET_58", + "BATTLE_ANIM_OAMSET_59", + "BATTLE_ANIM_OAMSET_5A", + "BATTLE_ANIM_OAMSET_5B", + "BATTLE_ANIM_OAMSET_5C", + "BATTLE_ANIM_OAMSET_5D", + "BATTLE_ANIM_OAMSET_5E", + "BATTLE_ANIM_OAMSET_5F", + "BATTLE_ANIM_OAMSET_60", + "BATTLE_ANIM_OAMSET_61", + "BATTLE_ANIM_OAMSET_62", + "BATTLE_ANIM_OAMSET_63", + "BATTLE_ANIM_OAMSET_64", + "BATTLE_ANIM_OAMSET_65", + "BATTLE_ANIM_OAMSET_66", + "BATTLE_ANIM_OAMSET_67", + "BATTLE_ANIM_OAMSET_68", + "BATTLE_ANIM_OAMSET_69", + "BATTLE_ANIM_OAMSET_6A", + "BATTLE_ANIM_OAMSET_6B", + "BATTLE_ANIM_OAMSET_6C", + "BATTLE_ANIM_OAMSET_6D", + "BATTLE_ANIM_OAMSET_6E", + "BATTLE_ANIM_OAMSET_6F", + "BATTLE_ANIM_OAMSET_70", + "BATTLE_ANIM_OAMSET_71", + "BATTLE_ANIM_OAMSET_72", + "BATTLE_ANIM_OAMSET_73", + "BATTLE_ANIM_OAMSET_74", + "BATTLE_ANIM_OAMSET_75", + "BATTLE_ANIM_OAMSET_76", + "BATTLE_ANIM_OAMSET_77", + "BATTLE_ANIM_OAMSET_78", + "BATTLE_ANIM_OAMSET_79", + "BATTLE_ANIM_OAMSET_7A", + "BATTLE_ANIM_OAMSET_7B", + "BATTLE_ANIM_OAMSET_7C", + "BATTLE_ANIM_OAMSET_7D", + "BATTLE_ANIM_OAMSET_7E", + "BATTLE_ANIM_OAMSET_7F", + "BATTLE_ANIM_OAMSET_80", + "BATTLE_ANIM_OAMSET_81", + "BATTLE_ANIM_OAMSET_82", + "BATTLE_ANIM_OAMSET_83", + "BATTLE_ANIM_OAMSET_84", + "BATTLE_ANIM_OAMSET_85", + "BATTLE_ANIM_OAMSET_86", + "BATTLE_ANIM_OAMSET_87", + "BATTLE_ANIM_OAMSET_88", + "BATTLE_ANIM_OAMSET_89", + "BATTLE_ANIM_OAMSET_8A", + "BATTLE_ANIM_OAMSET_8B", + "BATTLE_ANIM_OAMSET_8C", + "BATTLE_ANIM_OAMSET_8D", + "BATTLE_ANIM_OAMSET_8E", + "BATTLE_ANIM_OAMSET_8F", + "BATTLE_ANIM_OAMSET_90", + "BATTLE_ANIM_OAMSET_91", + "BATTLE_ANIM_OAMSET_92", + "BATTLE_ANIM_OAMSET_93", + "BATTLE_ANIM_OAMSET_94", + "BATTLE_ANIM_OAMSET_95", + "BATTLE_ANIM_OAMSET_96", + "BATTLE_ANIM_OAMSET_97", + "BATTLE_ANIM_OAMSET_98", + "BATTLE_ANIM_OAMSET_99", + "BATTLE_ANIM_OAMSET_9A", + "BATTLE_ANIM_OAMSET_9B", + "BATTLE_ANIM_OAMSET_9C", + "BATTLE_ANIM_OAMSET_9D", + "BATTLE_ANIM_OAMSET_9E", + "BATTLE_ANIM_OAMSET_9F", + "BATTLE_ANIM_OAMSET_A0", + "BATTLE_ANIM_OAMSET_A1", + "BATTLE_ANIM_OAMSET_A2", + "BATTLE_ANIM_OAMSET_A3", + "BATTLE_ANIM_OAMSET_A4", + "BATTLE_ANIM_OAMSET_A5", + "BATTLE_ANIM_OAMSET_A6", + "BATTLE_ANIM_OAMSET_A7", + "BATTLE_ANIM_OAMSET_A8", + "BATTLE_ANIM_OAMSET_A9", + "BATTLE_ANIM_OAMSET_AA", + "BATTLE_ANIM_OAMSET_AB", + "BATTLE_ANIM_OAMSET_AC", + "BATTLE_ANIM_OAMSET_AD", + "BATTLE_ANIM_OAMSET_AE", + "BATTLE_ANIM_OAMSET_AF", + "BATTLE_ANIM_OAMSET_B0", + "BATTLE_ANIM_OAMSET_B1", + "BATTLE_ANIM_OAMSET_B2", + "BATTLE_ANIM_OAMSET_B3", + "BATTLE_ANIM_OAMSET_B4", + "BATTLE_ANIM_OAMSET_B5", + "BATTLE_ANIM_OAMSET_B6", + "BATTLE_ANIM_OAMSET_B7", + "BATTLE_ANIM_OAMSET_B8", + "BATTLE_ANIM_OAMSET_B9", + "BATTLE_ANIM_OAMSET_BA", + "BATTLE_ANIM_OAMSET_BB", + "BATTLE_ANIM_OAMSET_BC", + "BATTLE_ANIM_OAMSET_BD", + "BATTLE_ANIM_OAMSET_BE", + "BATTLE_ANIM_OAMSET_BF", + "BATTLE_ANIM_OAMSET_C0", + "BATTLE_ANIM_OAMSET_C1", + "BATTLE_ANIM_OAMSET_C2", + "BATTLE_ANIM_OAMSET_C3", + "BATTLE_ANIM_OAMSET_C4", + "BATTLE_ANIM_OAMSET_C5", + "BATTLE_ANIM_OAMSET_C6", + "BATTLE_ANIM_OAMSET_C7", + "BATTLE_ANIM_OAMSET_C8", + "BATTLE_ANIM_OAMSET_C9", + "BATTLE_ANIM_OAMSET_CA", + "BATTLE_ANIM_OAMSET_CB", + "BATTLE_ANIM_OAMSET_CC", + "BATTLE_ANIM_OAMSET_CD", + "BATTLE_ANIM_OAMSET_CE", + "BATTLE_ANIM_OAMSET_CF", + "BATTLE_ANIM_OAMSET_D0", + "BATTLE_ANIM_OAMSET_D1", + "BATTLE_ANIM_OAMSET_D2", + "BATTLE_ANIM_OAMSET_D3", + "BATTLE_ANIM_OAMSET_D4", + "BATTLE_ANIM_OAMSET_D5", + "BATTLE_ANIM_OAMSET_D6", + "BATTLE_ANIM_OAMSET_D7" + ], + "battleAnimObPaletteOrder": [ + "PAL_BATTLE_OB_ENEMY", + "PAL_BATTLE_OB_PLAYER", + "PAL_BATTLE_OB_GRAY", + "PAL_BATTLE_OB_YELLOW", + "PAL_BATTLE_OB_RED", + "PAL_BATTLE_OB_GREEN", + "PAL_BATTLE_OB_BLUE", + "PAL_BATTLE_OB_BROWN" + ], + "battleAnimObjectOrder": [ + "BATTLE_ANIM_OBJ_HIT_BIG_YFIX", + "BATTLE_ANIM_OBJ_HIT_YFIX", + "BATTLE_ANIM_OBJ_HIT_SMALL_YFIX", + "BATTLE_ANIM_OBJ_HIT_BIG", + "BATTLE_ANIM_OBJ_HIT", + "BATTLE_ANIM_OBJ_HIT_SMALL", + "BATTLE_ANIM_OBJ_PUNCH", + "BATTLE_ANIM_OBJ_KICK", + "BATTLE_ANIM_OBJ_PALM", + "BATTLE_ANIM_OBJ_FANG", + "BATTLE_ANIM_OBJ_PUNCH_SHAKE", + "BATTLE_ANIM_OBJ_EMBER", + "BATTLE_ANIM_OBJ_DRAGON_RAGE", + "BATTLE_ANIM_OBJ_FLAMETHROWER", + "BATTLE_ANIM_OBJ_FIRE_SPIN", + "BATTLE_ANIM_OBJ_FIRE_BLAST", + "BATTLE_ANIM_OBJ_BURNED", + "BATTLE_ANIM_OBJ_BLIZZARD", + "BATTLE_ANIM_OBJ_ICE", + "BATTLE_ANIM_OBJ_ICE_BEAM", + "BATTLE_ANIM_OBJ_RAZOR_LEAF", + "BATTLE_ANIM_OBJ_POKE_BALL", + "BATTLE_ANIM_OBJ_POKE_BALL_BLOCKED", + "BATTLE_ANIM_OBJ_EXPLOSION1", + "BATTLE_ANIM_OBJ_EXPLOSION2", + "BATTLE_ANIM_OBJ_ACID", + "BATTLE_ANIM_OBJ_SLUDGE", + "BATTLE_ANIM_OBJ_BETA_BALL_POOF", + "BATTLE_ANIM_OBJ_BALL_POOF", + "BATTLE_ANIM_OBJ_BIG_ROCK", + "BATTLE_ANIM_OBJ_SMALL_ROCK", + "BATTLE_ANIM_OBJ_STRENGTH", + "BATTLE_ANIM_OBJ_SEISMIC_TOSS", + "BATTLE_ANIM_OBJ_BUBBLE", + "BATTLE_ANIM_OBJ_SURF", + "BATTLE_ANIM_OBJ_SING", + "BATTLE_ANIM_OBJ_WATER_GUN", + "BATTLE_ANIM_OBJ_HYDRO_PUMP", + "BATTLE_ANIM_OBJ_POWDER", + "BATTLE_ANIM_OBJ_BEAM", + "BATTLE_ANIM_OBJ_BEAM_TIP", + "BATTLE_ANIM_OBJ_ICE_BUILDUP", + "BATTLE_ANIM_OBJ_FROZEN", + "BATTLE_ANIM_OBJ_MASTER_BALL_SPARKLE", + "BATTLE_ANIM_OBJ_RECOVER", + "BATTLE_ANIM_OBJ_THUNDER_CENTER", + "BATTLE_ANIM_OBJ_THUNDER_LEFT", + "BATTLE_ANIM_OBJ_THUNDER_RIGHT", + "BATTLE_ANIM_OBJ_THUNDER_WAVE", + "BATTLE_ANIM_OBJ_SPARKS_CIRCLE_BIG", + "BATTLE_ANIM_OBJ_THUNDERBOLT_BALL", + "BATTLE_ANIM_OBJ_SPARKS_CIRCLE", + "BATTLE_ANIM_OBJ_THUNDERSHOCK_BALL", + "BATTLE_ANIM_OBJ_CLAMP", + "BATTLE_ANIM_OBJ_BITE", + "BATTLE_ANIM_OBJ_CUT_DOWN_LEFT", + "BATTLE_ANIM_OBJ_CUT_DOWN_RIGHT", + "BATTLE_ANIM_OBJ_CUT_UP_RIGHT", + "BATTLE_ANIM_OBJ_CUT_LONG_DOWN_LEFT", + "BATTLE_ANIM_OBJ_CUT_LONG_DOWN_RIGHT", + "BATTLE_ANIM_OBJ_SOLAR_BEAM_CHARGE", + "BATTLE_ANIM_OBJ_ABSORB_CENTER", + "BATTLE_ANIM_OBJ_GUST", + "BATTLE_ANIM_OBJ_VINE_WHIP1", + "BATTLE_ANIM_OBJ_VINE_WHIP2", + "BATTLE_ANIM_OBJ_RAZOR_WIND1", + "BATTLE_ANIM_OBJ_RAZOR_WIND2", + "BATTLE_ANIM_OBJ_SONICBOOM_JP", + "BATTLE_ANIM_OBJ_WARP", + "BATTLE_ANIM_OBJ_ABSORB", + "BATTLE_ANIM_OBJ_EGG", + "BATTLE_ANIM_OBJ_FOCUS", + "BATTLE_ANIM_OBJ_BIND1", + "BATTLE_ANIM_OBJ_BIND2", + "BATTLE_ANIM_OBJ_LEECH_SEED", + "BATTLE_ANIM_OBJ_SOUND", + "BATTLE_ANIM_OBJ_WAVE", + "BATTLE_ANIM_OBJ_CONFUSE_RAY", + "BATTLE_ANIM_OBJ_LEER", + "BATTLE_ANIM_OBJ_LEER_TIP", + "BATTLE_ANIM_OBJ_SCREEN", + "BATTLE_ANIM_OBJ_HARDEN", + "BATTLE_ANIM_OBJ_CHICK", + "BATTLE_ANIM_OBJ_AMNESIA", + "BATTLE_ANIM_OBJ_ASLEEP", + "BATTLE_ANIM_OBJ_SKULL", + "BATTLE_ANIM_OBJ_DIG_SAND", + "BATTLE_ANIM_OBJ_DIG_PILE", + "BATTLE_ANIM_OBJ_SAND", + "BATTLE_ANIM_OBJ_PARALYZED", + "BATTLE_ANIM_OBJ_STRING_SHOT", + "BATTLE_ANIM_OBJ_HAZE", + "BATTLE_ANIM_OBJ_MIST", + "BATTLE_ANIM_OBJ_SMOG", + "BATTLE_ANIM_OBJ_POISON_GAS", + "BATTLE_ANIM_OBJ_HORN", + "BATTLE_ANIM_OBJ_NEEDLE", + "BATTLE_ANIM_OBJ_PETAL_DANCE", + "BATTLE_ANIM_OBJ_SLUDGE_BOMB", + "BATTLE_ANIM_OBJ_PAY_DAY", + "BATTLE_ANIM_OBJ_SONICBOOM_JP_UNUSED", + "BATTLE_ANIM_OBJ_MIMIC", + "BATTLE_ANIM_OBJ_ATTRACT", + "BATTLE_ANIM_OBJ_BONEMERANG", + "BATTLE_ANIM_OBJ_BONE_CLUB", + "BATTLE_ANIM_OBJ_BONE_RUSH", + "BATTLE_ANIM_OBJ_SWIFT", + "BATTLE_ANIM_OBJ_KINESIS", + "BATTLE_ANIM_OBJ_FLASH", + "BATTLE_ANIM_OBJ_SHINY", + "BATTLE_ANIM_OBJ_SKY_ATTACK", + "BATTLE_ANIM_OBJ_LICK", + "BATTLE_ANIM_OBJ_WITHDRAW", + "BATTLE_ANIM_OBJ_DRAIN", + "BATTLE_ANIM_OBJ_GROWTH", + "BATTLE_ANIM_OBJ_CONVERSION2", + "BATTLE_ANIM_OBJ_SMOKE", + "BATTLE_ANIM_OBJ_SMOKESCREEN", + "BATTLE_ANIM_OBJ_SWORDS_DANCE", + "BATTLE_ANIM_OBJ_SPEED_LINE", + "BATTLE_ANIM_OBJ_SHARPEN", + "BATTLE_ANIM_OBJ_DEFENSE_CURL", + "BATTLE_ANIM_OBJ_METRONOME_HAND", + "BATTLE_ANIM_OBJ_METRONOME_SPARKLE", + "BATTLE_ANIM_OBJ_DISABLE", + "BATTLE_ANIM_OBJ_AGILITY", + "BATTLE_ANIM_OBJ_HEART", + "BATTLE_ANIM_OBJ_FLAME_WHEEL", + "BATTLE_ANIM_OBJ_SACRED_FIRE", + "BATTLE_ANIM_OBJ_COTTON_SPORE", + "BATTLE_ANIM_OBJ_MILK_DRINK", + "BATTLE_ANIM_OBJ_ANGER", + "BATTLE_ANIM_OBJ_HEAL_BELL", + "BATTLE_ANIM_OBJ_HEAL_BELL_NOTE", + "BATTLE_ANIM_OBJ_BATON_PASS", + "BATTLE_ANIM_OBJ_LOCK_ON", + "BATTLE_ANIM_OBJ_MIND_READER", + "BATTLE_ANIM_OBJ_SAFEGUARD", + "BATTLE_ANIM_OBJ_PROTECT", + "BATTLE_ANIM_OBJ_THIEF", + "BATTLE_ANIM_OBJ_OCTAZOOKA", + "BATTLE_ANIM_OBJ_PRESENT", + "BATTLE_ANIM_OBJ_SPIKES", + "BATTLE_ANIM_OBJ_POWDER_SNOW", + "BATTLE_ANIM_OBJ_DRAGONBREATH", + "BATTLE_ANIM_OBJ_CONVERSION", + "BATTLE_ANIM_OBJ_SPIDER_WEB", + "BATTLE_ANIM_OBJ_CAKE_UNUSED", + "BATTLE_ANIM_OBJ_NIGHTMARE", + "BATTLE_ANIM_OBJ_IN_NIGHTMARE", + "BATTLE_ANIM_OBJ_LOVELY_KISS", + "BATTLE_ANIM_OBJ_SWEET_KISS", + "BATTLE_ANIM_OBJ_SKETCH", + "BATTLE_ANIM_OBJ_ENCORE_HAND", + "BATTLE_ANIM_OBJ_ENCORE_STAR", + "BATTLE_ANIM_OBJ_DESTINY_BOND", + "BATTLE_ANIM_OBJ_MORNING_SUN", + "BATTLE_ANIM_OBJ_GLIMMER", + "BATTLE_ANIM_OBJ_MOONLIGHT", + "BATTLE_ANIM_OBJ_HIDDEN_POWER", + "BATTLE_ANIM_OBJ_CROSS_CHOP1", + "BATTLE_ANIM_OBJ_CROSS_CHOP2", + "BATTLE_ANIM_OBJ_SANDSTORM", + "BATTLE_ANIM_OBJ_ZAP_CANNON", + "BATTLE_ANIM_OBJ_SPITE", + "BATTLE_ANIM_OBJ_CURSE", + "BATTLE_ANIM_OBJ_PERISH_SONG", + "BATTLE_ANIM_OBJ_FORESIGHT", + "BATTLE_ANIM_OBJ_RAPID_SPIN", + "BATTLE_ANIM_OBJ_SWAGGER", + "BATTLE_ANIM_OBJ_BELLY_DRUM_HAND", + "BATTLE_ANIM_OBJ_BELLY_DRUM_NOTE", + "BATTLE_ANIM_OBJ_MEAN_LOOK", + "BATTLE_ANIM_OBJ_BETA_PURSUIT", + "BATTLE_ANIM_OBJ_SHOOTING_SPARKLE", + "BATTLE_ANIM_OBJ_RAIN", + "BATTLE_ANIM_OBJ_B0", + "BATTLE_ANIM_OBJ_PSYCH_UP", + "BATTLE_ANIM_OBJ_ANCIENTPOWER", + "BATTLE_ANIM_OBJ_AEROBLAST", + "BATTLE_ANIM_OBJ_SHADOW_BALL", + "BATTLE_ANIM_OBJ_ROCK_SMASH", + "BATTLE_ANIM_OBJ_FLOWER", + "BATTLE_ANIM_OBJ_COTTON", + "BATTLE_ANIM_OBJ_ENEMYFEET_1ROW", + "BATTLE_ANIM_OBJ_PLAYERHEAD_1ROW", + "BATTLE_ANIM_OBJ_ENEMYFEET_2ROW", + "BATTLE_ANIM_OBJ_PLAYERHEAD_2ROW" + ], + "battleBgEffectOrder": [ + "BATTLE_BG_EFFECT_END", + "BATTLE_BG_EFFECT_FLASH_INVERTED", + "BATTLE_BG_EFFECT_FLASH_WHITE", + "BATTLE_BG_EFFECT_WHITE_HUES", + "BATTLE_BG_EFFECT_BLACK_HUES", + "BATTLE_BG_EFFECT_ALTERNATE_HUES", + "BATTLE_BG_EFFECT_CYCLE_OBPALS_GRAY_AND_YELLOW", + "BATTLE_BG_EFFECT_CYCLE_MID_OBPALS_GRAY_AND_YELLOW", + "BATTLE_BG_EFFECT_CYCLE_BGPALS_INVERTED", + "BATTLE_BG_EFFECT_HIDE_MON", + "BATTLE_BG_EFFECT_SHOW_MON", + "BATTLE_BG_EFFECT_ENTER_MON", + "BATTLE_BG_EFFECT_RETURN_MON", + "BATTLE_BG_EFFECT_SURF", + "BATTLE_BG_EFFECT_WHIRLPOOL", + "BATTLE_BG_EFFECT_TELEPORT", + "BATTLE_BG_EFFECT_NIGHT_SHADE", + "BATTLE_BG_EFFECT_BATTLEROBJ_1ROW", + "BATTLE_BG_EFFECT_BATTLEROBJ_2ROW", + "BATTLE_BG_EFFECT_DOUBLE_TEAM", + "BATTLE_BG_EFFECT_ACID_ARMOR", + "BATTLE_BG_EFFECT_RAPID_FLASH", + "BATTLE_BG_EFFECT_FADE_MON_TO_LIGHT", + "BATTLE_BG_EFFECT_FADE_MON_TO_BLACK", + "BATTLE_BG_EFFECT_FADE_MON_TO_LIGHT_REPEATING", + "BATTLE_BG_EFFECT_FADE_MON_TO_BLACK_REPEATING", + "BATTLE_BG_EFFECT_CYCLE_MON_LIGHT_DARK_REPEATING", + "BATTLE_BG_EFFECT_FLASH_MON_REPEATING", + "BATTLE_BG_EFFECT_FADE_MONS_TO_BLACK_REPEATING", + "BATTLE_BG_EFFECT_FADE_MON_TO_WHITE_WAIT_FADE_BACK", + "BATTLE_BG_EFFECT_FADE_MON_FROM_WHITE", + "BATTLE_BG_EFFECT_SHAKE_SCREEN_X", + "BATTLE_BG_EFFECT_SHAKE_SCREEN_Y", + "BATTLE_BG_EFFECT_WITHDRAW", + "BATTLE_BG_EFFECT_BOUNCE_DOWN", + "BATTLE_BG_EFFECT_DIG", + "BATTLE_BG_EFFECT_TACKLE", + "BATTLE_BG_EFFECT_WOBBLE_MON", + "BATTLE_BG_EFFECT_REMOVE_MON", + "BATTLE_BG_EFFECT_WAVE_DEFORM_MON", + "BATTLE_BG_EFFECT_PSYCHIC", + "BATTLE_BG_EFFECT_BETA_SEND_OUT_MON1", + "BATTLE_BG_EFFECT_BETA_SEND_OUT_MON2", + "BATTLE_BG_EFFECT_FLAIL", + "BATTLE_BG_EFFECT_BETA_PURSUIT", + "BATTLE_BG_EFFECT_ROLLOUT", + "BATTLE_BG_EFFECT_VITAL_THROW", + "BATTLE_BG_EFFECT_START_WATER", + "BATTLE_BG_EFFECT_WATER", + "BATTLE_BG_EFFECT_END_WATER", + "BATTLE_BG_EFFECT_VIBRATE_MON", + "BATTLE_BG_EFFECT_WOBBLE_PLAYER", + "BATTLE_BG_EFFECT_WOBBLE_SCREEN" + ], + "cmdQueueOrder": [ + "CMDQUEUE_NULL", + "CMDQUEUE_TYPE1", + "CMDQUEUE_STONETABLE", + "CMDQUEUE_TYPE3", + "CMDQUEUE_TYPE4" + ], + "decoDescOrder": [ + "DECODESC_POSTER", + "DECODESC_LEFT_DOLL", + "DECODESC_RIGHT_DOLL", + "DECODESC_BIG_DOLL", + "DECODESC_CONSOLE" + ], + "eggGroupOrder": [ + "EGG_MONSTER", + "EGG_WATER_1", + "EGG_BUG", + "EGG_FLYING", + "EGG_GROUND", + "EGG_FAIRY", + "EGG_PLANT", + "EGG_HUMANSHAPE", + "EGG_WATER_3", + "EGG_MINERAL", + "EGG_INDETERMINATE", + "EGG_WATER_2", + "EGG_DITTO", + "EGG_DRAGON", + "EGG_NONE" + ], + "environmentOrder": [ + "TOWN", + "ROUTE", + "INDOOR", + "CAVE", + "ENVIRONMENT_5", + "GATE", + "DUNGEON" + ], + "evolveMethodOrder": [ + "EVOLVE_LEVEL", + "EVOLVE_ITEM", + "EVOLVE_TRADE", + "EVOLVE_HAPPINESS", + "EVOLVE_STAT" + ], + "fishGroupOrder": [ + "FISHGROUP_NONE", + "FISHGROUP_SHORE", + "FISHGROUP_OCEAN", + "FISHGROUP_LAKE", + "FISHGROUP_POND", + "FISHGROUP_DRATINI", + "FISHGROUP_QWILFISH_SWARM", + "FISHGROUP_REMORAID_SWARM", + "FISHGROUP_GYARADOS", + "FISHGROUP_DRATINI_2", + "FISHGROUP_WHIRL_ISLANDS", + "FISHGROUP_QWILFISH", + "FISHGROUP_REMORAID", + "FISHGROUP_QWILFISH_NO_SWARM" + ], + "floorOrder": [ + "FLOOR_B4F", + "FLOOR_B3F", + "FLOOR_B2F", + "FLOOR_B1F", + "FLOOR_1F", + "FLOOR_2F", + "FLOOR_3F", + "FLOOR_4F", + "FLOOR_5F", + "FLOOR_6F", + "FLOOR_7F", + "FLOOR_8F", + "FLOOR_9F", + "FLOOR_10F", + "FLOOR_11F", + "FLOOR_ROOF" + ], + "growthRateOrder": [ + "GROWTH_MEDIUM_FAST", + "GROWTH_SLIGHTLY_FAST", + "GROWTH_SLIGHTLY_SLOW", + "GROWTH_MEDIUM_SLOW", + "GROWTH_FAST", + "GROWTH_SLOW" + ], + "heldEffectOrder": [ + "HELD_NONE", + "HELD_BERRY", + "HELD_2", + "HELD_LEFTOVERS", + "HELD_UNUSED_4", + "HELD_5", + "HELD_RESTORE_PP", + "HELD_UNUSED_7", + "HELD_CLEANSE_TAG", + "HELD_UNUSED_9", + "HELD_HEAL_POISON", + "HELD_HEAL_FREEZE", + "HELD_HEAL_BURN", + "HELD_HEAL_SLEEP", + "HELD_HEAL_PARALYZE", + "HELD_HEAL_STATUS", + "HELD_HEAL_CONFUSION", + "HELD_UNUSED_17", + "HELD_UNUSED_18", + "HELD_UNUSED_19", + "HELD_PREVENT_POISON", + "HELD_PREVENT_BURN", + "HELD_PREVENT_FREEZE", + "HELD_PREVENT_SLEEP", + "HELD_PREVENT_PARALYZE", + "HELD_PREVENT_CONFUSE", + "HELD_UNUSED_26", + "HELD_UNUSED_27", + "HELD_UNUSED_28", + "HELD_UNUSED_29", + "HELD_30", + "HELD_ATTACK_UP", + "HELD_DEFENSE_UP", + "HELD_SPEED_UP", + "HELD_SP_ATTACK_UP", + "HELD_SP_DEFENSE_UP", + "HELD_ACCURACY_UP", + "HELD_EVASION_UP", + "HELD_38", + "HELD_UNUSED_39", + "HELD_UNUSED_40", + "HELD_UNUSED_41", + "HELD_METAL_POWDER", + "HELD_UNUSED_43", + "HELD_UNUSED_44", + "HELD_UNUSED_45", + "HELD_UNUSED_46", + "HELD_UNUSED_47", + "HELD_UNUSED_48", + "HELD_UNUSED_49", + "HELD_NORMAL_BOOST", + "HELD_FIGHTING_BOOST", + "HELD_FLYING_BOOST", + "HELD_POISON_BOOST", + "HELD_GROUND_BOOST", + "HELD_ROCK_BOOST", + "HELD_BUG_BOOST", + "HELD_GHOST_BOOST", + "HELD_FIRE_BOOST", + "HELD_WATER_BOOST", + "HELD_GRASS_BOOST", + "HELD_ELECTRIC_BOOST", + "HELD_PSYCHIC_BOOST", + "HELD_ICE_BOOST", + "HELD_DRAGON_BOOST", + "HELD_DARK_BOOST", + "HELD_STEEL_BOOST", + "HELD_UNUSED_67", + "HELD_UNUSED_68", + "HELD_UNUSED_69", + "HELD_CATCH_CHANCE", + "HELD_71", + "HELD_ESCAPE", + "HELD_CRITICAL_UP", + "HELD_QUICK_CLAW", + "HELD_FLINCH", + "HELD_AMULET_COIN", + "HELD_BRIGHTPOWDER", + "HELD_UNUSED_78", + "HELD_FOCUS_BAND" + ], + "iconOrder": [ + "ICON_NULL", + "ICON_POLIWAG", + "ICON_JIGGLYPUFF", + "ICON_DIGLETT", + "ICON_PIKACHU", + "ICON_STARYU", + "ICON_FISH", + "ICON_BIRD", + "ICON_MONSTER", + "ICON_CLEFAIRY", + "ICON_ODDISH", + "ICON_BUG", + "ICON_GHOST", + "ICON_LAPRAS", + "ICON_HUMANSHAPE", + "ICON_FOX", + "ICON_EQUINE", + "ICON_SHELL", + "ICON_BLOB", + "ICON_SERPENT", + "ICON_VOLTORB", + "ICON_SQUIRTLE", + "ICON_BULBASAUR", + "ICON_CHARMANDER", + "ICON_CATERPILLAR", + "ICON_UNOWN", + "ICON_GEODUDE", + "ICON_FIGHTER", + "ICON_EGG", + "ICON_JELLYFISH", + "ICON_MOTH", + "ICON_BAT", + "ICON_SNORLAX", + "ICON_HO_OH", + "ICON_LUGIA", + "ICON_GYARADOS", + "ICON_SLOWPOKE", + "ICON_SUDOWOODO", + "ICON_BIGMON" + ], + "itemMenuOrder": [ + "ITEMMENU_NOUSE", + "ITEMMENU_CURRENT", + "ITEMMENU_PARTY", + "ITEMMENU_CLOSE" + ], + "itemNameCount": 190, + "itemOrder": [ + "MASTER_BALL", + "ULTRA_BALL", + "BRIGHTPOWDER", + "GREAT_BALL", + "POKE_BALL", + "TOWN_MAP", + "BICYCLE", + "MOON_STONE", + "ANTIDOTE", + "BURN_HEAL", + "ICE_HEAL", + "AWAKENING", + "PARLYZ_HEAL", + "FULL_RESTORE", + "MAX_POTION", + "HYPER_POTION", + "SUPER_POTION", + "POTION", + "ESCAPE_ROPE", + "REPEL", + "MAX_ELIXER", + "FIRE_STONE", + "THUNDERSTONE", + "WATER_STONE", + "ITEM_19", + "HP_UP", + "PROTEIN", + "IRON", + "CARBOS", + "LUCKY_PUNCH", + "CALCIUM", + "RARE_CANDY", + "X_ACCURACY", + "LEAF_STONE", + "METAL_POWDER", + "NUGGET", + "POKE_DOLL", + "FULL_HEAL", + "REVIVE", + "MAX_REVIVE", + "GUARD_SPEC", + "SUPER_REPEL", + "MAX_REPEL", + "DIRE_HIT", + "ITEM_2D", + "FRESH_WATER", + "SODA_POP", + "LEMONADE", + "X_ATTACK", + "ITEM_32", + "X_DEFEND", + "X_SPEED", + "X_SPECIAL", + "COIN_CASE", + "ITEMFINDER", + "POKE_FLUTE", + "EXP_SHARE", + "OLD_ROD", + "GOOD_ROD", + "SILVER_LEAF", + "SUPER_ROD", + "PP_UP", + "ETHER", + "MAX_ETHER", + "ELIXER", + "RED_SCALE", + "SECRETPOTION", + "S_S_TICKET", + "MYSTERY_EGG", + "ITEM_46", + "SILVER_WING", + "MOOMOO_MILK", + "QUICK_CLAW", + "PSNCUREBERRY", + "GOLD_LEAF", + "SOFT_SAND", + "SHARP_BEAK", + "PRZCUREBERRY", + "BURNT_BERRY", + "ICE_BERRY", + "POISON_BARB", + "KINGS_ROCK", + "BITTER_BERRY", + "MINT_BERRY", + "RED_APRICORN", + "TINYMUSHROOM", + "BIG_MUSHROOM", + "SILVERPOWDER", + "BLU_APRICORN", + "ITEM_5A", + "AMULET_COIN", + "YLW_APRICORN", + "GRN_APRICORN", + "CLEANSE_TAG", + "MYSTIC_WATER", + "TWISTEDSPOON", + "WHT_APRICORN", + "BLACKBELT_I", + "BLK_APRICORN", + "ITEM_64", + "PNK_APRICORN", + "BLACKGLASSES", + "SLOWPOKETAIL", + "PINK_BOW", + "STICK", + "SMOKE_BALL", + "NEVERMELTICE", + "MAGNET", + "MIRACLEBERRY", + "PEARL", + "BIG_PEARL", + "EVERSTONE", + "SPELL_TAG", + "RAGECANDYBAR", + "ITEM_73", + "ITEM_74", + "MIRACLE_SEED", + "THICK_CLUB", + "FOCUS_BAND", + "ITEM_78", + "ENERGYPOWDER", + "ENERGY_ROOT", + "HEAL_POWDER", + "REVIVAL_HERB", + "HARD_STONE", + "LUCKY_EGG", + "CARD_KEY", + "MACHINE_PART", + "ITEM_81", + "LOST_ITEM", + "STARDUST", + "STAR_PIECE", + "BASEMENT_KEY", + "PASS", + "ITEM_87", + "ITEM_88", + "ITEM_89", + "CHARCOAL", + "BERRY_JUICE", + "SCOPE_LENS", + "ITEM_8D", + "ITEM_8E", + "METAL_COAT", + "DRAGON_FANG", + "ITEM_91", + "LEFTOVERS", + "ITEM_93", + "ITEM_94", + "ITEM_95", + "MYSTERYBERRY", + "DRAGON_SCALE", + "BERSERK_GENE", + "ITEM_99", + "ITEM_9A", + "ITEM_9B", + "SACRED_ASH", + "HEAVY_BALL", + "FLOWER_MAIL", + "LEVEL_BALL", + "LURE_BALL", + "FAST_BALL", + "ITEM_A2", + "LIGHT_BALL", + "FRIEND_BALL", + "MOON_BALL", + "LOVE_BALL", + "NORMAL_BOX", + "GORGEOUS_BOX", + "SUN_STONE", + "POLKADOT_BOW", + "ITEM_AB", + "UP_GRADE", + "BERRY", + "GOLD_BERRY", + "SQUIRTBOTTLE", + "ITEM_B0", + "PARK_BALL", + "RAINBOW_WING", + "ITEM_B3", + "BRICK_PIECE", + "SURF_MAIL", + "LITEBLUEMAIL", + "PORTRAITMAIL", + "LOVELY_MAIL", + "EON_MAIL", + "MORPH_MAIL", + "BLUESKY_MAIL", + "MUSIC_MAIL", + "MIRAGE_MAIL", + "ITEM_BE", + "TM_DYNAMICPUNCH", + "TM_HEADBUTT", + "TM_CURSE", + "TM_ROLLOUT", + "ITEM_C3", + "TM_ROAR", + "TM_TOXIC", + "TM_ZAP_CANNON", + "TM_ROCK_SMASH", + "TM_PSYCH_UP", + "TM_HIDDEN_POWER", + "TM_SUNNY_DAY", + "TM_SWEET_SCENT", + "TM_SNORE", + "TM_BLIZZARD", + "TM_HYPER_BEAM", + "TM_ICY_WIND", + "TM_PROTECT", + "TM_RAIN_DANCE", + "TM_GIGA_DRAIN", + "TM_ENDURE", + "TM_FRUSTRATION", + "TM_SOLARBEAM", + "TM_IRON_TAIL", + "TM_DRAGONBREATH", + "TM_THUNDER", + "TM_EARTHQUAKE", + "TM_RETURN", + "TM_DIG", + "ITEM_DC", + "TM_PSYCHIC_M", + "TM_SHADOW_BALL", + "TM_MUD_SLAP", + "TM_DOUBLE_TEAM", + "TM_ICE_PUNCH", + "TM_SWAGGER", + "TM_SLEEP_TALK", + "TM_SLUDGE_BOMB", + "TM_SANDSTORM", + "TM_FIRE_BLAST", + "TM_SWIFT", + "TM_DEFENSE_CURL", + "TM_THUNDERPUNCH", + "TM_DREAM_EATER", + "TM_DETECT", + "TM_REST", + "TM_ATTRACT", + "TM_THIEF", + "TM_STEEL_WING", + "TM_FIRE_PUNCH", + "TM_FURY_CUTTER", + "TM_NIGHTMARE", + "HM_CUT", + "HM_FLY", + "HM_SURF", + "HM_STRENGTH", + "HM_FLASH", + "HM_WHIRLPOOL", + "HM_WATERFALL", + "ITEM_FA" + ], + "landmarkOrder": [ + "LANDMARK_SPECIAL", + "LANDMARK_NEW_BARK_TOWN", + "LANDMARK_ROUTE_29", + "LANDMARK_CHERRYGROVE_CITY", + "LANDMARK_ROUTE_30", + "LANDMARK_ROUTE_31", + "LANDMARK_VIOLET_CITY", + "LANDMARK_SPROUT_TOWER", + "LANDMARK_ROUTE_32", + "LANDMARK_RUINS_OF_ALPH", + "LANDMARK_UNION_CAVE", + "LANDMARK_ROUTE_33", + "LANDMARK_AZALEA_TOWN", + "LANDMARK_SLOWPOKE_WELL", + "LANDMARK_ILEX_FOREST", + "LANDMARK_ROUTE_34", + "LANDMARK_GOLDENROD_CITY", + "LANDMARK_RADIO_TOWER", + "LANDMARK_ROUTE_35", + "LANDMARK_NATIONAL_PARK", + "LANDMARK_ROUTE_36", + "LANDMARK_ROUTE_37", + "LANDMARK_ECRUTEAK_CITY", + "LANDMARK_TIN_TOWER", + "LANDMARK_BURNED_TOWER", + "LANDMARK_ROUTE_38", + "LANDMARK_ROUTE_39", + "LANDMARK_OLIVINE_CITY", + "LANDMARK_LIGHTHOUSE", + "LANDMARK_ROUTE_40", + "LANDMARK_WHIRL_ISLANDS", + "LANDMARK_ROUTE_41", + "LANDMARK_CIANWOOD_CITY", + "LANDMARK_ROUTE_42", + "LANDMARK_MT_MORTAR", + "LANDMARK_MAHOGANY_TOWN", + "LANDMARK_ROUTE_43", + "LANDMARK_LAKE_OF_RAGE", + "LANDMARK_ROUTE_44", + "LANDMARK_ICE_PATH", + "LANDMARK_BLACKTHORN_CITY", + "LANDMARK_DRAGONS_DEN", + "LANDMARK_ROUTE_45", + "LANDMARK_DARK_CAVE", + "LANDMARK_ROUTE_46", + "LANDMARK_SILVER_CAVE", + "LANDMARK_PALLET_TOWN", + "LANDMARK_ROUTE_1", + "LANDMARK_VIRIDIAN_CITY", + "LANDMARK_ROUTE_2", + "LANDMARK_PEWTER_CITY", + "LANDMARK_ROUTE_3", + "LANDMARK_MT_MOON", + "LANDMARK_ROUTE_4", + "LANDMARK_CERULEAN_CITY", + "LANDMARK_ROUTE_24", + "LANDMARK_ROUTE_25", + "LANDMARK_ROUTE_5", + "LANDMARK_UNDERGROUND_PATH", + "LANDMARK_ROUTE_6", + "LANDMARK_VERMILION_CITY", + "LANDMARK_DIGLETTS_CAVE", + "LANDMARK_ROUTE_7", + "LANDMARK_ROUTE_8", + "LANDMARK_ROUTE_9", + "LANDMARK_ROCK_TUNNEL", + "LANDMARK_ROUTE_10", + "LANDMARK_POWER_PLANT", + "LANDMARK_LAVENDER_TOWN", + "LANDMARK_LAV_RADIO_TOWER", + "LANDMARK_CELADON_CITY", + "LANDMARK_SAFFRON_CITY", + "LANDMARK_ROUTE_11", + "LANDMARK_ROUTE_12", + "LANDMARK_ROUTE_13", + "LANDMARK_ROUTE_14", + "LANDMARK_ROUTE_15", + "LANDMARK_ROUTE_16", + "LANDMARK_ROUTE_17", + "LANDMARK_ROUTE_18", + "LANDMARK_FUCHSIA_CITY", + "LANDMARK_ROUTE_19", + "LANDMARK_ROUTE_20", + "LANDMARK_SEAFOAM_ISLANDS", + "LANDMARK_CINNABAR_ISLAND", + "LANDMARK_ROUTE_21", + "LANDMARK_ROUTE_22", + "LANDMARK_VICTORY_ROAD", + "LANDMARK_ROUTE_23", + "LANDMARK_INDIGO_PLATEAU", + "LANDMARK_ROUTE_26", + "LANDMARK_ROUTE_27", + "LANDMARK_TOHJO_FALLS", + "LANDMARK_ROUTE_28", + "LANDMARK_FAST_SHIP" + ], + "mapCallbackOrder": [ + "MAPCALLBACK_NONE", + "MAPCALLBACK_TILES", + "MAPCALLBACK_OBJECTS", + "MAPCALLBACK_CMDQUEUE", + "MAPCALLBACK_SPRITES", + "MAPCALLBACK_NEWMAP" + ], + "mapGroups": [ + { + "group": 1, + "height": 4, + "map": 1, + "name": "OLIVINE_POKECENTER_1F", + "width": 5 + }, + { + "group": 1, + "height": 8, + "map": 2, + "name": "OLIVINE_GYM", + "width": 5 + }, + { + "group": 1, + "height": 4, + "map": 3, + "name": "OLIVINE_TIMS_HOUSE", + "width": 4 + }, + { + "group": 1, + "height": 4, + "map": 4, + "name": "OLIVINE_HOUSE_BETA", + "width": 4 + }, + { + "group": 1, + "height": 4, + "map": 5, + "name": "OLIVINE_PUNISHMENT_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 1, + "height": 4, + "map": 6, + "name": "OLIVINE_GOOD_ROD_HOUSE", + "width": 4 + }, + { + "group": 1, + "height": 4, + "map": 7, + "name": "OLIVINE_CAFE", + "width": 4 + }, + { + "group": 1, + "height": 4, + "map": 8, + "name": "OLIVINE_MART", + "width": 6 + }, + { + "group": 1, + "height": 4, + "map": 9, + "name": "ROUTE_38_ECRUTEAK_GATE", + "width": 5 + }, + { + "group": 1, + "height": 4, + "map": 10, + "name": "ROUTE_39_BARN", + "width": 4 + }, + { + "group": 1, + "height": 4, + "map": 11, + "name": "ROUTE_39_FARMHOUSE", + "width": 4 + }, + { + "group": 1, + "height": 9, + "map": 12, + "name": "ROUTE_38", + "width": 20 + }, + { + "group": 1, + "height": 18, + "map": 13, + "name": "ROUTE_39", + "width": 10 + }, + { + "group": 1, + "height": 18, + "map": 14, + "name": "OLIVINE_CITY", + "width": 20 + }, + { + "group": 2, + "height": 4, + "map": 1, + "name": "MAHOGANY_RED_GYARADOS_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 2, + "height": 9, + "map": 2, + "name": "MAHOGANY_GYM", + "width": 5 + }, + { + "group": 2, + "height": 4, + "map": 3, + "name": "MAHOGANY_POKECENTER_1F", + "width": 5 + }, + { + "group": 2, + "height": 4, + "map": 4, + "name": "ROUTE_42_ECRUTEAK_GATE", + "width": 5 + }, + { + "group": 2, + "height": 9, + "map": 5, + "name": "ROUTE_42", + "width": 30 + }, + { + "group": 2, + "height": 9, + "map": 6, + "name": "ROUTE_44", + "width": 30 + }, + { + "group": 2, + "height": 9, + "map": 7, + "name": "MAHOGANY_TOWN", + "width": 10 + }, + { + "group": 3, + "height": 8, + "map": 1, + "name": "SPROUT_TOWER_1F", + "width": 10 + }, + { + "group": 3, + "height": 8, + "map": 2, + "name": "SPROUT_TOWER_2F", + "width": 10 + }, + { + "group": 3, + "height": 8, + "map": 3, + "name": "SPROUT_TOWER_3F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 4, + "name": "TIN_TOWER_1F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 5, + "name": "TIN_TOWER_2F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 6, + "name": "TIN_TOWER_3F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 7, + "name": "TIN_TOWER_4F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 8, + "name": "TIN_TOWER_5F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 9, + "name": "TIN_TOWER_6F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 10, + "name": "TIN_TOWER_7F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 11, + "name": "TIN_TOWER_8F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 12, + "name": "TIN_TOWER_9F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 13, + "name": "BURNED_TOWER_1F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 14, + "name": "BURNED_TOWER_B1F", + "width": 10 + }, + { + "group": 3, + "height": 27, + "map": 15, + "name": "NATIONAL_PARK", + "width": 20 + }, + { + "group": 3, + "height": 27, + "map": 16, + "name": "NATIONAL_PARK_BUG_CONTEST", + "width": 20 + }, + { + "group": 3, + "height": 4, + "map": 17, + "name": "RADIO_TOWER_1F", + "width": 9 + }, + { + "group": 3, + "height": 4, + "map": 18, + "name": "RADIO_TOWER_2F", + "width": 9 + }, + { + "group": 3, + "height": 4, + "map": 19, + "name": "RADIO_TOWER_3F", + "width": 9 + }, + { + "group": 3, + "height": 4, + "map": 20, + "name": "RADIO_TOWER_4F", + "width": 9 + }, + { + "group": 3, + "height": 4, + "map": 21, + "name": "RADIO_TOWER_5F", + "width": 9 + }, + { + "group": 3, + "height": 18, + "map": 22, + "name": "RUINS_OF_ALPH_OUTSIDE", + "width": 10 + }, + { + "group": 3, + "height": 5, + "map": 23, + "name": "RUINS_OF_ALPH_HO_OH_CHAMBER", + "width": 4 + }, + { + "group": 3, + "height": 5, + "map": 24, + "name": "RUINS_OF_ALPH_KABUTO_CHAMBER", + "width": 4 + }, + { + "group": 3, + "height": 5, + "map": 25, + "name": "RUINS_OF_ALPH_OMANYTE_CHAMBER", + "width": 4 + }, + { + "group": 3, + "height": 5, + "map": 26, + "name": "RUINS_OF_ALPH_AERODACTYL_CHAMBER", + "width": 4 + }, + { + "group": 3, + "height": 14, + "map": 27, + "name": "RUINS_OF_ALPH_INNER_CHAMBER", + "width": 10 + }, + { + "group": 3, + "height": 4, + "map": 28, + "name": "RUINS_OF_ALPH_RESEARCH_CENTER", + "width": 4 + }, + { + "group": 3, + "height": 18, + "map": 29, + "name": "UNION_CAVE_1F", + "width": 10 + }, + { + "group": 3, + "height": 18, + "map": 30, + "name": "UNION_CAVE_B1F", + "width": 10 + }, + { + "group": 3, + "height": 18, + "map": 31, + "name": "UNION_CAVE_B2F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 32, + "name": "SLOWPOKE_WELL_B1F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 33, + "name": "SLOWPOKE_WELL_B2F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 34, + "name": "OLIVINE_LIGHTHOUSE_1F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 35, + "name": "OLIVINE_LIGHTHOUSE_2F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 36, + "name": "OLIVINE_LIGHTHOUSE_3F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 37, + "name": "OLIVINE_LIGHTHOUSE_4F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 38, + "name": "OLIVINE_LIGHTHOUSE_5F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 39, + "name": "OLIVINE_LIGHTHOUSE_6F", + "width": 10 + }, + { + "group": 3, + "height": 4, + "map": 40, + "name": "MAHOGANY_MART_1F", + "width": 4 + }, + { + "group": 3, + "height": 9, + "map": 41, + "name": "TEAM_ROCKET_BASE_B1F", + "width": 15 + }, + { + "group": 3, + "height": 9, + "map": 42, + "name": "TEAM_ROCKET_BASE_B2F", + "width": 15 + }, + { + "group": 3, + "height": 9, + "map": 43, + "name": "TEAM_ROCKET_BASE_B3F", + "width": 15 + }, + { + "group": 3, + "height": 27, + "map": 44, + "name": "ILEX_FOREST", + "width": 15 + }, + { + "group": 3, + "height": 18, + "map": 45, + "name": "GOLDENROD_UNDERGROUND", + "width": 15 + }, + { + "group": 3, + "height": 18, + "map": 46, + "name": "GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES", + "width": 15 + }, + { + "group": 3, + "height": 9, + "map": 47, + "name": "GOLDENROD_DEPT_STORE_B1F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 48, + "name": "GOLDENROD_UNDERGROUND_WAREHOUSE", + "width": 10 + }, + { + "group": 3, + "height": 18, + "map": 49, + "name": "MOUNT_MORTAR_1F_OUTSIDE", + "width": 20 + }, + { + "group": 3, + "height": 27, + "map": 50, + "name": "MOUNT_MORTAR_1F_INSIDE", + "width": 20 + }, + { + "group": 3, + "height": 18, + "map": 51, + "name": "MOUNT_MORTAR_2F_INSIDE", + "width": 20 + }, + { + "group": 3, + "height": 18, + "map": 52, + "name": "MOUNT_MORTAR_B1F", + "width": 20 + }, + { + "group": 3, + "height": 18, + "map": 53, + "name": "ICE_PATH_1F", + "width": 20 + }, + { + "group": 3, + "height": 18, + "map": 54, + "name": "ICE_PATH_B1F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 55, + "name": "ICE_PATH_B2F_MAHOGANY_SIDE", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 56, + "name": "ICE_PATH_B2F_BLACKTHORN_SIDE", + "width": 5 + }, + { + "group": 3, + "height": 9, + "map": 57, + "name": "ICE_PATH_B3F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 58, + "name": "WHIRL_ISLAND_NW", + "width": 5 + }, + { + "group": 3, + "height": 9, + "map": 59, + "name": "WHIRL_ISLAND_NE", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 60, + "name": "WHIRL_ISLAND_SW", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 61, + "name": "WHIRL_ISLAND_CAVE", + "width": 5 + }, + { + "group": 3, + "height": 9, + "map": 62, + "name": "WHIRL_ISLAND_SE", + "width": 5 + }, + { + "group": 3, + "height": 18, + "map": 63, + "name": "WHIRL_ISLAND_B1F", + "width": 20 + }, + { + "group": 3, + "height": 18, + "map": 64, + "name": "WHIRL_ISLAND_B2F", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 65, + "name": "WHIRL_ISLAND_LUGIA_CHAMBER", + "width": 10 + }, + { + "group": 3, + "height": 18, + "map": 66, + "name": "SILVER_CAVE_ROOM_1", + "width": 10 + }, + { + "group": 3, + "height": 18, + "map": 67, + "name": "SILVER_CAVE_ROOM_2", + "width": 15 + }, + { + "group": 3, + "height": 18, + "map": 68, + "name": "SILVER_CAVE_ROOM_3", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 69, + "name": "SILVER_CAVE_ITEM_ROOMS", + "width": 10 + }, + { + "group": 3, + "height": 18, + "map": 70, + "name": "DARK_CAVE_VIOLET_ENTRANCE", + "width": 20 + }, + { + "group": 3, + "height": 18, + "map": 71, + "name": "DARK_CAVE_BLACKTHORN_ENTRANCE", + "width": 15 + }, + { + "group": 3, + "height": 9, + "map": 72, + "name": "DRAGONS_DEN_1F", + "width": 5 + }, + { + "group": 3, + "height": 18, + "map": 73, + "name": "DRAGONS_DEN_B1F", + "width": 20 + }, + { + "group": 3, + "height": 9, + "map": 74, + "name": "TOHJO_FALLS", + "width": 15 + }, + { + "group": 3, + "height": 18, + "map": 75, + "name": "DIGLETTS_CAVE", + "width": 10 + }, + { + "group": 3, + "height": 9, + "map": 76, + "name": "MOUNT_MOON", + "width": 15 + }, + { + "group": 3, + "height": 14, + "map": 77, + "name": "UNDERGROUND_PATH", + "width": 3 + }, + { + "group": 3, + "height": 18, + "map": 78, + "name": "ROCK_TUNNEL_1F", + "width": 15 + }, + { + "group": 3, + "height": 18, + "map": 79, + "name": "ROCK_TUNNEL_B1F", + "width": 15 + }, + { + "group": 3, + "height": 4, + "map": 80, + "name": "SAFARI_ZONE_FUCHSIA_GATE_BETA", + "width": 5 + }, + { + "group": 3, + "height": 18, + "map": 81, + "name": "SAFARI_ZONE_BETA", + "width": 10 + }, + { + "group": 3, + "height": 36, + "map": 82, + "name": "VICTORY_ROAD", + "width": 10 + }, + { + "group": 4, + "height": 9, + "map": 1, + "name": "ECRUTEAK_TIN_TOWER_ENTRANCE", + "width": 10 + }, + { + "group": 4, + "height": 4, + "map": 2, + "name": "ECRUTEAK_TIN_TOWER_BACK_ENTRANCE", + "width": 4 + }, + { + "group": 4, + "height": 4, + "map": 3, + "name": "ECRUTEAK_POKECENTER_1F", + "width": 5 + }, + { + "group": 4, + "height": 4, + "map": 4, + "name": "ECRUTEAK_LUGIA_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 4, + "height": 7, + "map": 5, + "name": "DANCE_THEATER", + "width": 6 + }, + { + "group": 4, + "height": 4, + "map": 6, + "name": "ECRUTEAK_MART", + "width": 6 + }, + { + "group": 4, + "height": 9, + "map": 7, + "name": "ECRUTEAK_GYM", + "width": 5 + }, + { + "group": 4, + "height": 4, + "map": 8, + "name": "ECRUTEAK_ITEMFINDER_HOUSE", + "width": 4 + }, + { + "group": 4, + "height": 18, + "map": 9, + "name": "ECRUTEAK_CITY", + "width": 20 + }, + { + "group": 5, + "height": 9, + "map": 1, + "name": "BLACKTHORN_GYM_1F", + "width": 5 + }, + { + "group": 5, + "height": 9, + "map": 2, + "name": "BLACKTHORN_GYM_2F", + "width": 5 + }, + { + "group": 5, + "height": 4, + "map": 3, + "name": "BLACKTHORN_DRAGON_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 5, + "height": 4, + "map": 4, + "name": "BLACKTHORN_EMYS_HOUSE", + "width": 4 + }, + { + "group": 5, + "height": 4, + "map": 5, + "name": "BLACKTHORN_MART", + "width": 6 + }, + { + "group": 5, + "height": 4, + "map": 6, + "name": "BLACKTHORN_POKECENTER_1F", + "width": 5 + }, + { + "group": 5, + "height": 4, + "map": 7, + "name": "MOVE_DELETERS_HOUSE", + "width": 4 + }, + { + "group": 5, + "height": 45, + "map": 8, + "name": "ROUTE_45", + "width": 10 + }, + { + "group": 5, + "height": 18, + "map": 9, + "name": "ROUTE_46", + "width": 10 + }, + { + "group": 5, + "height": 18, + "map": 10, + "name": "BLACKTHORN_CITY", + "width": 20 + }, + { + "group": 6, + "height": 4, + "map": 1, + "name": "CINNABAR_POKECENTER_1F", + "width": 5 + }, + { + "group": 6, + "height": 4, + "map": 2, + "name": "CINNABAR_POKECENTER_2F_BETA", + "width": 8 + }, + { + "group": 6, + "height": 4, + "map": 3, + "name": "ROUTE_19_FUCHSIA_GATE", + "width": 5 + }, + { + "group": 6, + "height": 4, + "map": 4, + "name": "SEAFOAM_GYM", + "width": 5 + }, + { + "group": 6, + "height": 18, + "map": 5, + "name": "ROUTE_19", + "width": 10 + }, + { + "group": 6, + "height": 9, + "map": 6, + "name": "ROUTE_20", + "width": 30 + }, + { + "group": 6, + "height": 18, + "map": 7, + "name": "ROUTE_21", + "width": 10 + }, + { + "group": 6, + "height": 9, + "map": 8, + "name": "CINNABAR_ISLAND", + "width": 10 + }, + { + "group": 7, + "height": 4, + "map": 1, + "name": "CERULEAN_GYM_BADGE_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 7, + "height": 4, + "map": 2, + "name": "CERULEAN_POLICE_STATION", + "width": 4 + }, + { + "group": 7, + "height": 4, + "map": 3, + "name": "CERULEAN_TRADE_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 7, + "height": 4, + "map": 4, + "name": "CERULEAN_POKECENTER_1F", + "width": 5 + }, + { + "group": 7, + "height": 4, + "map": 5, + "name": "CERULEAN_POKECENTER_2F_BETA", + "width": 8 + }, + { + "group": 7, + "height": 8, + "map": 6, + "name": "CERULEAN_GYM", + "width": 5 + }, + { + "group": 7, + "height": 4, + "map": 7, + "name": "CERULEAN_MART", + "width": 6 + }, + { + "group": 7, + "height": 4, + "map": 8, + "name": "ROUTE_10_POKECENTER_1F", + "width": 5 + }, + { + "group": 7, + "height": 4, + "map": 9, + "name": "ROUTE_10_POKECENTER_2F_BETA", + "width": 8 + }, + { + "group": 7, + "height": 9, + "map": 10, + "name": "POWER_PLANT", + "width": 10 + }, + { + "group": 7, + "height": 4, + "map": 11, + "name": "BILLS_HOUSE", + "width": 4 + }, + { + "group": 7, + "height": 9, + "map": 12, + "name": "ROUTE_4", + "width": 20 + }, + { + "group": 7, + "height": 9, + "map": 13, + "name": "ROUTE_9", + "width": 30 + }, + { + "group": 7, + "height": 9, + "map": 14, + "name": "ROUTE_10_NORTH", + "width": 10 + }, + { + "group": 7, + "height": 9, + "map": 15, + "name": "ROUTE_24", + "width": 10 + }, + { + "group": 7, + "height": 9, + "map": 16, + "name": "ROUTE_25", + "width": 30 + }, + { + "group": 7, + "height": 18, + "map": 17, + "name": "CERULEAN_CITY", + "width": 20 + }, + { + "group": 8, + "height": 4, + "map": 1, + "name": "AZALEA_POKECENTER_1F", + "width": 5 + }, + { + "group": 8, + "height": 4, + "map": 2, + "name": "CHARCOAL_KILN", + "width": 4 + }, + { + "group": 8, + "height": 4, + "map": 3, + "name": "AZALEA_MART", + "width": 6 + }, + { + "group": 8, + "height": 4, + "map": 4, + "name": "KURTS_HOUSE", + "width": 8 + }, + { + "group": 8, + "height": 8, + "map": 5, + "name": "AZALEA_GYM", + "width": 5 + }, + { + "group": 8, + "height": 9, + "map": 6, + "name": "ROUTE_33", + "width": 10 + }, + { + "group": 8, + "height": 9, + "map": 7, + "name": "AZALEA_TOWN", + "width": 20 + }, + { + "group": 9, + "height": 4, + "map": 1, + "name": "LAKE_OF_RAGE_HIDDEN_POWER_HOUSE", + "width": 4 + }, + { + "group": 9, + "height": 4, + "map": 2, + "name": "LAKE_OF_RAGE_MAGIKARP_HOUSE", + "width": 4 + }, + { + "group": 9, + "height": 4, + "map": 3, + "name": "ROUTE_43_MAHOGANY_GATE", + "width": 5 + }, + { + "group": 9, + "height": 4, + "map": 4, + "name": "ROUTE_43_GATE", + "width": 5 + }, + { + "group": 9, + "height": 27, + "map": 5, + "name": "ROUTE_43", + "width": 10 + }, + { + "group": 9, + "height": 18, + "map": 6, + "name": "LAKE_OF_RAGE", + "width": 20 + }, + { + "group": 10, + "height": 45, + "map": 1, + "name": "ROUTE_32", + "width": 10 + }, + { + "group": 10, + "height": 18, + "map": 2, + "name": "ROUTE_35", + "width": 10 + }, + { + "group": 10, + "height": 9, + "map": 3, + "name": "ROUTE_36", + "width": 30 + }, + { + "group": 10, + "height": 9, + "map": 4, + "name": "ROUTE_37", + "width": 10 + }, + { + "group": 10, + "height": 18, + "map": 5, + "name": "VIOLET_CITY", + "width": 20 + }, + { + "group": 10, + "height": 4, + "map": 6, + "name": "VIOLET_MART", + "width": 6 + }, + { + "group": 10, + "height": 8, + "map": 7, + "name": "VIOLET_GYM", + "width": 5 + }, + { + "group": 10, + "height": 8, + "map": 8, + "name": "EARLS_POKEMON_ACADEMY", + "width": 4 + }, + { + "group": 10, + "height": 4, + "map": 9, + "name": "VIOLET_NICKNAME_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 10, + "height": 4, + "map": 10, + "name": "VIOLET_POKECENTER_1F", + "width": 5 + }, + { + "group": 10, + "height": 4, + "map": 11, + "name": "VIOLET_KYLES_HOUSE", + "width": 4 + }, + { + "group": 10, + "height": 4, + "map": 12, + "name": "ROUTE_32_RUINS_OF_ALPH_GATE", + "width": 5 + }, + { + "group": 10, + "height": 4, + "map": 13, + "name": "ROUTE_32_POKECENTER_1F", + "width": 5 + }, + { + "group": 10, + "height": 4, + "map": 14, + "name": "ROUTE_35_GOLDENROD_GATE", + "width": 5 + }, + { + "group": 10, + "height": 4, + "map": 15, + "name": "ROUTE_35_NATIONAL_PARK_GATE", + "width": 4 + }, + { + "group": 10, + "height": 4, + "map": 16, + "name": "ROUTE_36_RUINS_OF_ALPH_GATE", + "width": 5 + }, + { + "group": 10, + "height": 4, + "map": 17, + "name": "ROUTE_36_NATIONAL_PARK_GATE", + "width": 5 + }, + { + "group": 11, + "height": 27, + "map": 1, + "name": "ROUTE_34", + "width": 10 + }, + { + "group": 11, + "height": 18, + "map": 2, + "name": "GOLDENROD_CITY", + "width": 20 + }, + { + "group": 11, + "height": 9, + "map": 3, + "name": "GOLDENROD_GYM", + "width": 10 + }, + { + "group": 11, + "height": 4, + "map": 4, + "name": "GOLDENROD_BIKE_SHOP", + "width": 4 + }, + { + "group": 11, + "height": 4, + "map": 5, + "name": "GOLDENROD_HAPPINESS_RATER", + "width": 4 + }, + { + "group": 11, + "height": 4, + "map": 6, + "name": "BILLS_FAMILYS_HOUSE", + "width": 4 + }, + { + "group": 11, + "height": 9, + "map": 7, + "name": "GOLDENROD_MAGNET_TRAIN_STATION", + "width": 10 + }, + { + "group": 11, + "height": 4, + "map": 8, + "name": "GOLDENROD_FLOWER_SHOP", + "width": 4 + }, + { + "group": 11, + "height": 4, + "map": 9, + "name": "GOLDENROD_POKECENTER_1F", + "width": 5 + }, + { + "group": 11, + "height": 4, + "map": 10, + "name": "GOLDENROD_PP_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 11, + "height": 4, + "map": 11, + "name": "GOLDENROD_NAME_RATER", + "width": 4 + }, + { + "group": 11, + "height": 4, + "map": 12, + "name": "GOLDENROD_DEPT_STORE_1F", + "width": 8 + }, + { + "group": 11, + "height": 4, + "map": 13, + "name": "GOLDENROD_DEPT_STORE_2F", + "width": 8 + }, + { + "group": 11, + "height": 4, + "map": 14, + "name": "GOLDENROD_DEPT_STORE_3F", + "width": 8 + }, + { + "group": 11, + "height": 4, + "map": 15, + "name": "GOLDENROD_DEPT_STORE_4F", + "width": 8 + }, + { + "group": 11, + "height": 4, + "map": 16, + "name": "GOLDENROD_DEPT_STORE_5F", + "width": 8 + }, + { + "group": 11, + "height": 4, + "map": 17, + "name": "GOLDENROD_DEPT_STORE_6F", + "width": 8 + }, + { + "group": 11, + "height": 2, + "map": 18, + "name": "GOLDENROD_DEPT_STORE_ELEVATOR", + "width": 2 + }, + { + "group": 11, + "height": 7, + "map": 19, + "name": "GOLDENROD_GAME_CORNER", + "width": 10 + }, + { + "group": 11, + "height": 4, + "map": 20, + "name": "ILEX_FOREST_AZALEA_GATE", + "width": 5 + }, + { + "group": 11, + "height": 4, + "map": 21, + "name": "ROUTE_34_ILEX_FOREST_GATE", + "width": 5 + }, + { + "group": 11, + "height": 4, + "map": 22, + "name": "DAY_CARE", + "width": 5 + }, + { + "group": 12, + "height": 9, + "map": 1, + "name": "ROUTE_6", + "width": 10 + }, + { + "group": 12, + "height": 9, + "map": 2, + "name": "ROUTE_11", + "width": 20 + }, + { + "group": 12, + "height": 18, + "map": 3, + "name": "VERMILION_CITY", + "width": 20 + }, + { + "group": 12, + "height": 4, + "map": 4, + "name": "VERMILION_FISHING_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 12, + "height": 4, + "map": 5, + "name": "VERMILION_POKECENTER_1F", + "width": 5 + }, + { + "group": 12, + "height": 4, + "map": 6, + "name": "VERMILION_POKECENTER_2F_BETA", + "width": 8 + }, + { + "group": 12, + "height": 4, + "map": 7, + "name": "POKEMON_FAN_CLUB", + "width": 5 + }, + { + "group": 12, + "height": 4, + "map": 8, + "name": "VERMILION_MAGNET_TRAIN_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 12, + "height": 4, + "map": 9, + "name": "VERMILION_MART", + "width": 6 + }, + { + "group": 12, + "height": 4, + "map": 10, + "name": "VERMILION_DIGLETTS_CAVE_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 12, + "height": 9, + "map": 11, + "name": "VERMILION_GYM", + "width": 5 + }, + { + "group": 12, + "height": 4, + "map": 12, + "name": "ROUTE_6_SAFFRON_GATE", + "width": 5 + }, + { + "group": 12, + "height": 4, + "map": 13, + "name": "ROUTE_6_UNDERGROUND_PATH_ENTRANCE", + "width": 4 + }, + { + "group": 13, + "height": 18, + "map": 1, + "name": "ROUTE_1", + "width": 10 + }, + { + "group": 13, + "height": 9, + "map": 2, + "name": "PALLET_TOWN", + "width": 10 + }, + { + "group": 13, + "height": 4, + "map": 3, + "name": "REDS_HOUSE_1F", + "width": 4 + }, + { + "group": 13, + "height": 4, + "map": 4, + "name": "REDS_HOUSE_2F", + "width": 4 + }, + { + "group": 13, + "height": 4, + "map": 5, + "name": "BLUES_HOUSE", + "width": 4 + }, + { + "group": 13, + "height": 6, + "map": 6, + "name": "OAKS_LAB", + "width": 5 + }, + { + "group": 14, + "height": 9, + "map": 1, + "name": "ROUTE_3", + "width": 30 + }, + { + "group": 14, + "height": 18, + "map": 2, + "name": "PEWTER_CITY", + "width": 20 + }, + { + "group": 14, + "height": 4, + "map": 3, + "name": "PEWTER_NIDORAN_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 14, + "height": 7, + "map": 4, + "name": "PEWTER_GYM", + "width": 5 + }, + { + "group": 14, + "height": 4, + "map": 5, + "name": "PEWTER_MART", + "width": 6 + }, + { + "group": 14, + "height": 4, + "map": 6, + "name": "PEWTER_POKECENTER_1F", + "width": 5 + }, + { + "group": 14, + "height": 4, + "map": 7, + "name": "PEWTER_POKECENTER_2F_BETA", + "width": 8 + }, + { + "group": 14, + "height": 4, + "map": 8, + "name": "PEWTER_SNOOZE_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 15, + "height": 18, + "map": 1, + "name": "OLIVINE_PORT", + "width": 10 + }, + { + "group": 15, + "height": 18, + "map": 2, + "name": "VERMILION_PORT", + "width": 10 + }, + { + "group": 15, + "height": 9, + "map": 3, + "name": "FAST_SHIP_1F", + "width": 16 + }, + { + "group": 15, + "height": 16, + "map": 4, + "name": "FAST_SHIP_CABINS_NNW_NNE_NE", + "width": 4 + }, + { + "group": 15, + "height": 16, + "map": 5, + "name": "FAST_SHIP_CABINS_SW_SSW_NW", + "width": 4 + }, + { + "group": 15, + "height": 17, + "map": 6, + "name": "FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN", + "width": 5 + }, + { + "group": 15, + "height": 8, + "map": 7, + "name": "FAST_SHIP_B1F", + "width": 16 + }, + { + "group": 15, + "height": 9, + "map": 8, + "name": "OLIVINE_PORT_PASSAGE", + "width": 10 + }, + { + "group": 15, + "height": 9, + "map": 9, + "name": "VERMILION_PORT_PASSAGE", + "width": 10 + }, + { + "group": 15, + "height": 9, + "map": 10, + "name": "MOUNT_MOON_SQUARE", + "width": 15 + }, + { + "group": 15, + "height": 4, + "map": 11, + "name": "MOUNT_MOON_GIFT_SHOP", + "width": 4 + }, + { + "group": 15, + "height": 9, + "map": 12, + "name": "TIN_TOWER_ROOF", + "width": 10 + }, + { + "group": 16, + "height": 9, + "map": 1, + "name": "ROUTE_23", + "width": 10 + }, + { + "group": 16, + "height": 7, + "map": 2, + "name": "INDIGO_PLATEAU_POKECENTER_1F", + "width": 9 + }, + { + "group": 16, + "height": 9, + "map": 3, + "name": "WILLS_ROOM", + "width": 5 + }, + { + "group": 16, + "height": 9, + "map": 4, + "name": "KOGAS_ROOM", + "width": 5 + }, + { + "group": 16, + "height": 9, + "map": 5, + "name": "BRUNOS_ROOM", + "width": 5 + }, + { + "group": 16, + "height": 9, + "map": 6, + "name": "KARENS_ROOM", + "width": 5 + }, + { + "group": 16, + "height": 12, + "map": 7, + "name": "LANCES_ROOM", + "width": 5 + }, + { + "group": 16, + "height": 7, + "map": 8, + "name": "HALL_OF_FAME", + "width": 5 + }, + { + "group": 17, + "height": 9, + "map": 1, + "name": "ROUTE_13", + "width": 30 + }, + { + "group": 17, + "height": 18, + "map": 2, + "name": "ROUTE_14", + "width": 10 + }, + { + "group": 17, + "height": 9, + "map": 3, + "name": "ROUTE_15", + "width": 20 + }, + { + "group": 17, + "height": 9, + "map": 4, + "name": "ROUTE_18", + "width": 10 + }, + { + "group": 17, + "height": 18, + "map": 5, + "name": "FUCHSIA_CITY", + "width": 20 + }, + { + "group": 17, + "height": 4, + "map": 6, + "name": "FUCHSIA_MART", + "width": 6 + }, + { + "group": 17, + "height": 4, + "map": 7, + "name": "SAFARI_ZONE_MAIN_OFFICE", + "width": 4 + }, + { + "group": 17, + "height": 9, + "map": 8, + "name": "FUCHSIA_GYM", + "width": 5 + }, + { + "group": 17, + "height": 4, + "map": 9, + "name": "BILLS_OLDER_SISTERS_HOUSE", + "width": 4 + }, + { + "group": 17, + "height": 4, + "map": 10, + "name": "FUCHSIA_POKECENTER_1F", + "width": 5 + }, + { + "group": 17, + "height": 4, + "map": 11, + "name": "FUCHSIA_POKECENTER_2F_BETA", + "width": 8 + }, + { + "group": 17, + "height": 4, + "map": 12, + "name": "SAFARI_ZONE_WARDENS_HOME", + "width": 5 + }, + { + "group": 17, + "height": 4, + "map": 13, + "name": "ROUTE_15_FUCHSIA_GATE", + "width": 5 + }, + { + "group": 18, + "height": 9, + "map": 1, + "name": "ROUTE_8", + "width": 20 + }, + { + "group": 18, + "height": 27, + "map": 2, + "name": "ROUTE_12", + "width": 10 + }, + { + "group": 18, + "height": 9, + "map": 3, + "name": "ROUTE_10_SOUTH", + "width": 10 + }, + { + "group": 18, + "height": 9, + "map": 4, + "name": "LAVENDER_TOWN", + "width": 10 + }, + { + "group": 18, + "height": 4, + "map": 5, + "name": "LAVENDER_POKECENTER_1F", + "width": 5 + }, + { + "group": 18, + "height": 4, + "map": 6, + "name": "LAVENDER_POKECENTER_2F_BETA", + "width": 8 + }, + { + "group": 18, + "height": 4, + "map": 7, + "name": "MR_FUJIS_HOUSE", + "width": 5 + }, + { + "group": 18, + "height": 4, + "map": 8, + "name": "LAVENDER_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 18, + "height": 4, + "map": 9, + "name": "LAVENDER_NAME_RATER", + "width": 4 + }, + { + "group": 18, + "height": 4, + "map": 10, + "name": "LAVENDER_MART", + "width": 6 + }, + { + "group": 18, + "height": 4, + "map": 11, + "name": "SOUL_HOUSE", + "width": 5 + }, + { + "group": 18, + "height": 4, + "map": 12, + "name": "LAV_RADIO_TOWER_1F", + "width": 10 + }, + { + "group": 18, + "height": 4, + "map": 13, + "name": "ROUTE_8_SAFFRON_GATE", + "width": 5 + }, + { + "group": 18, + "height": 4, + "map": 14, + "name": "ROUTE_12_SUPER_ROD_HOUSE", + "width": 4 + }, + { + "group": 19, + "height": 9, + "map": 1, + "name": "ROUTE_28", + "width": 20 + }, + { + "group": 19, + "height": 18, + "map": 2, + "name": "SILVER_CAVE_OUTSIDE", + "width": 20 + }, + { + "group": 19, + "height": 4, + "map": 3, + "name": "SILVER_CAVE_POKECENTER_1F", + "width": 5 + }, + { + "group": 19, + "height": 4, + "map": 4, + "name": "ROUTE_28_STEEL_WING_HOUSE", + "width": 4 + }, + { + "group": 20, + "height": 4, + "map": 1, + "name": "POKECENTER_2F", + "width": 8 + }, + { + "group": 20, + "height": 4, + "map": 2, + "name": "TRADE_CENTER", + "width": 5 + }, + { + "group": 20, + "height": 4, + "map": 3, + "name": "COLOSSEUM", + "width": 5 + }, + { + "group": 20, + "height": 4, + "map": 4, + "name": "TIME_CAPSULE", + "width": 5 + }, + { + "group": 21, + "height": 9, + "map": 1, + "name": "ROUTE_7", + "width": 10 + }, + { + "group": 21, + "height": 9, + "map": 2, + "name": "ROUTE_16", + "width": 10 + }, + { + "group": 21, + "height": 45, + "map": 3, + "name": "ROUTE_17", + "width": 10 + }, + { + "group": 21, + "height": 18, + "map": 4, + "name": "CELADON_CITY", + "width": 20 + }, + { + "group": 21, + "height": 4, + "map": 5, + "name": "CELADON_DEPT_STORE_1F", + "width": 8 + }, + { + "group": 21, + "height": 4, + "map": 6, + "name": "CELADON_DEPT_STORE_2F", + "width": 8 + }, + { + "group": 21, + "height": 4, + "map": 7, + "name": "CELADON_DEPT_STORE_3F", + "width": 8 + }, + { + "group": 21, + "height": 4, + "map": 8, + "name": "CELADON_DEPT_STORE_4F", + "width": 8 + }, + { + "group": 21, + "height": 4, + "map": 9, + "name": "CELADON_DEPT_STORE_5F", + "width": 8 + }, + { + "group": 21, + "height": 4, + "map": 10, + "name": "CELADON_DEPT_STORE_6F", + "width": 8 + }, + { + "group": 21, + "height": 2, + "map": 11, + "name": "CELADON_DEPT_STORE_ELEVATOR", + "width": 2 + }, + { + "group": 21, + "height": 5, + "map": 12, + "name": "CELADON_MANSION_1F", + "width": 4 + }, + { + "group": 21, + "height": 5, + "map": 13, + "name": "CELADON_MANSION_2F", + "width": 4 + }, + { + "group": 21, + "height": 5, + "map": 14, + "name": "CELADON_MANSION_3F", + "width": 4 + }, + { + "group": 21, + "height": 5, + "map": 15, + "name": "CELADON_MANSION_ROOF", + "width": 4 + }, + { + "group": 21, + "height": 4, + "map": 16, + "name": "CELADON_MANSION_ROOF_HOUSE", + "width": 4 + }, + { + "group": 21, + "height": 4, + "map": 17, + "name": "CELADON_POKECENTER_1F", + "width": 5 + }, + { + "group": 21, + "height": 4, + "map": 18, + "name": "CELADON_POKECENTER_2F_BETA", + "width": 8 + }, + { + "group": 21, + "height": 7, + "map": 19, + "name": "CELADON_GAME_CORNER", + "width": 10 + }, + { + "group": 21, + "height": 3, + "map": 20, + "name": "CELADON_GAME_CORNER_PRIZE_ROOM", + "width": 3 + }, + { + "group": 21, + "height": 9, + "map": 21, + "name": "CELADON_GYM", + "width": 5 + }, + { + "group": 21, + "height": 4, + "map": 22, + "name": "CELADON_CAFE", + "width": 6 + }, + { + "group": 21, + "height": 4, + "map": 23, + "name": "ROUTE_16_FUCHSIA_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 21, + "height": 4, + "map": 24, + "name": "ROUTE_16_GATE", + "width": 5 + }, + { + "group": 21, + "height": 4, + "map": 25, + "name": "ROUTE_7_SAFFRON_GATE", + "width": 5 + }, + { + "group": 21, + "height": 4, + "map": 26, + "name": "ROUTE_17_ROUTE_18_GATE", + "width": 5 + }, + { + "group": 22, + "height": 18, + "map": 1, + "name": "ROUTE_40", + "width": 10 + }, + { + "group": 22, + "height": 27, + "map": 2, + "name": "ROUTE_41", + "width": 25 + }, + { + "group": 22, + "height": 27, + "map": 3, + "name": "CIANWOOD_CITY", + "width": 15 + }, + { + "group": 22, + "height": 4, + "map": 4, + "name": "MANIAS_HOUSE", + "width": 4 + }, + { + "group": 22, + "height": 9, + "map": 5, + "name": "CIANWOOD_GYM", + "width": 5 + }, + { + "group": 22, + "height": 4, + "map": 6, + "name": "CIANWOOD_POKECENTER_1F", + "width": 5 + }, + { + "group": 22, + "height": 4, + "map": 7, + "name": "CIANWOOD_PHARMACY", + "width": 4 + }, + { + "group": 22, + "height": 4, + "map": 8, + "name": "CIANWOOD_PHOTO_STUDIO", + "width": 4 + }, + { + "group": 22, + "height": 4, + "map": 9, + "name": "CIANWOOD_LUGIA_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 23, + "height": 27, + "map": 1, + "name": "ROUTE_2", + "width": 10 + }, + { + "group": 23, + "height": 9, + "map": 2, + "name": "ROUTE_22", + "width": 20 + }, + { + "group": 23, + "height": 18, + "map": 3, + "name": "VIRIDIAN_CITY", + "width": 20 + }, + { + "group": 23, + "height": 9, + "map": 4, + "name": "VIRIDIAN_GYM", + "width": 5 + }, + { + "group": 23, + "height": 4, + "map": 5, + "name": "VIRIDIAN_NICKNAME_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 23, + "height": 7, + "map": 6, + "name": "TRAINER_HOUSE_1F", + "width": 5 + }, + { + "group": 23, + "height": 8, + "map": 7, + "name": "TRAINER_HOUSE_B1F", + "width": 5 + }, + { + "group": 23, + "height": 4, + "map": 8, + "name": "VIRIDIAN_MART", + "width": 6 + }, + { + "group": 23, + "height": 4, + "map": 9, + "name": "VIRIDIAN_POKECENTER_1F", + "width": 5 + }, + { + "group": 23, + "height": 4, + "map": 10, + "name": "VIRIDIAN_POKECENTER_2F_BETA", + "width": 8 + }, + { + "group": 23, + "height": 4, + "map": 11, + "name": "ROUTE_2_NUGGET_HOUSE", + "width": 4 + }, + { + "group": 23, + "height": 4, + "map": 12, + "name": "ROUTE_2_GATE", + "width": 5 + }, + { + "group": 23, + "height": 9, + "map": 13, + "name": "VICTORY_ROAD_GATE", + "width": 10 + }, + { + "group": 24, + "height": 54, + "map": 1, + "name": "ROUTE_26", + "width": 10 + }, + { + "group": 24, + "height": 9, + "map": 2, + "name": "ROUTE_27", + "width": 40 + }, + { + "group": 24, + "height": 9, + "map": 3, + "name": "ROUTE_29", + "width": 30 + }, + { + "group": 24, + "height": 9, + "map": 4, + "name": "NEW_BARK_TOWN", + "width": 10 + }, + { + "group": 24, + "height": 6, + "map": 5, + "name": "ELMS_LAB", + "width": 5 + }, + { + "group": 24, + "height": 4, + "map": 6, + "name": "PLAYERS_HOUSE_1F", + "width": 5 + }, + { + "group": 24, + "height": 3, + "map": 7, + "name": "PLAYERS_HOUSE_2F", + "width": 4 + }, + { + "group": 24, + "height": 4, + "map": 8, + "name": "PLAYERS_NEIGHBORS_HOUSE", + "width": 4 + }, + { + "group": 24, + "height": 4, + "map": 9, + "name": "ELMS_HOUSE", + "width": 4 + }, + { + "group": 24, + "height": 4, + "map": 10, + "name": "ROUTE_26_HEAL_HOUSE", + "width": 4 + }, + { + "group": 24, + "height": 4, + "map": 11, + "name": "DAY_OF_WEEK_SIBLINGS_HOUSE", + "width": 4 + }, + { + "group": 24, + "height": 4, + "map": 12, + "name": "ROUTE_27_SANDSTORM_HOUSE", + "width": 4 + }, + { + "group": 24, + "height": 4, + "map": 13, + "name": "ROUTE_29_ROUTE_46_GATE", + "width": 5 + }, + { + "group": 25, + "height": 9, + "map": 1, + "name": "ROUTE_5", + "width": 10 + }, + { + "group": 25, + "height": 18, + "map": 2, + "name": "SAFFRON_CITY", + "width": 20 + }, + { + "group": 25, + "height": 6, + "map": 3, + "name": "FIGHTING_DOJO", + "width": 5 + }, + { + "group": 25, + "height": 9, + "map": 4, + "name": "SAFFRON_GYM", + "width": 10 + }, + { + "group": 25, + "height": 4, + "map": 5, + "name": "SAFFRON_MART", + "width": 6 + }, + { + "group": 25, + "height": 4, + "map": 6, + "name": "SAFFRON_POKECENTER_1F", + "width": 5 + }, + { + "group": 25, + "height": 4, + "map": 7, + "name": "SAFFRON_POKECENTER_2F_BETA", + "width": 8 + }, + { + "group": 25, + "height": 4, + "map": 8, + "name": "MR_PSYCHICS_HOUSE", + "width": 4 + }, + { + "group": 25, + "height": 9, + "map": 9, + "name": "SAFFRON_MAGNET_TRAIN_STATION", + "width": 10 + }, + { + "group": 25, + "height": 4, + "map": 10, + "name": "SILPH_CO_1F", + "width": 8 + }, + { + "group": 25, + "height": 4, + "map": 11, + "name": "COPYCATS_HOUSE_1F", + "width": 4 + }, + { + "group": 25, + "height": 3, + "map": 12, + "name": "COPYCATS_HOUSE_2F", + "width": 5 + }, + { + "group": 25, + "height": 4, + "map": 13, + "name": "ROUTE_5_UNDERGROUND_PATH_ENTRANCE", + "width": 4 + }, + { + "group": 25, + "height": 4, + "map": 14, + "name": "ROUTE_5_SAFFRON_GATE", + "width": 5 + }, + { + "group": 25, + "height": 4, + "map": 15, + "name": "ROUTE_5_CLEANSE_TAG_HOUSE", + "width": 4 + }, + { + "group": 26, + "height": 27, + "map": 1, + "name": "ROUTE_30", + "width": 10 + }, + { + "group": 26, + "height": 9, + "map": 2, + "name": "ROUTE_31", + "width": 20 + }, + { + "group": 26, + "height": 9, + "map": 3, + "name": "CHERRYGROVE_CITY", + "width": 20 + }, + { + "group": 26, + "height": 4, + "map": 4, + "name": "CHERRYGROVE_MART", + "width": 6 + }, + { + "group": 26, + "height": 4, + "map": 5, + "name": "CHERRYGROVE_POKECENTER_1F", + "width": 5 + }, + { + "group": 26, + "height": 4, + "map": 6, + "name": "CHERRYGROVE_GYM_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 26, + "height": 4, + "map": 7, + "name": "GUIDE_GENTS_HOUSE", + "width": 4 + }, + { + "group": 26, + "height": 4, + "map": 8, + "name": "CHERRYGROVE_EVOLUTION_SPEECH_HOUSE", + "width": 4 + }, + { + "group": 26, + "height": 4, + "map": 9, + "name": "ROUTE_30_BERRY_HOUSE", + "width": 4 + }, + { + "group": 26, + "height": 4, + "map": 10, + "name": "MR_POKEMONS_HOUSE", + "width": 4 + }, + { + "group": 26, + "height": 4, + "map": 11, + "name": "ROUTE_31_VIOLET_GATE", + "width": 5 + } + ], + "mapOrder": [ + "OLIVINE_POKECENTER_1F", + "OLIVINE_GYM", + "OLIVINE_TIMS_HOUSE", + "OLIVINE_HOUSE_BETA", + "OLIVINE_PUNISHMENT_SPEECH_HOUSE", + "OLIVINE_GOOD_ROD_HOUSE", + "OLIVINE_CAFE", + "OLIVINE_MART", + "ROUTE_38_ECRUTEAK_GATE", + "ROUTE_39_BARN", + "ROUTE_39_FARMHOUSE", + "ROUTE_38", + "ROUTE_39", + "OLIVINE_CITY", + "MAHOGANY_RED_GYARADOS_SPEECH_HOUSE", + "MAHOGANY_GYM", + "MAHOGANY_POKECENTER_1F", + "ROUTE_42_ECRUTEAK_GATE", + "ROUTE_42", + "ROUTE_44", + "MAHOGANY_TOWN", + "SPROUT_TOWER_1F", + "SPROUT_TOWER_2F", + "SPROUT_TOWER_3F", + "TIN_TOWER_1F", + "TIN_TOWER_2F", + "TIN_TOWER_3F", + "TIN_TOWER_4F", + "TIN_TOWER_5F", + "TIN_TOWER_6F", + "TIN_TOWER_7F", + "TIN_TOWER_8F", + "TIN_TOWER_9F", + "BURNED_TOWER_1F", + "BURNED_TOWER_B1F", + "NATIONAL_PARK", + "NATIONAL_PARK_BUG_CONTEST", + "RADIO_TOWER_1F", + "RADIO_TOWER_2F", + "RADIO_TOWER_3F", + "RADIO_TOWER_4F", + "RADIO_TOWER_5F", + "RUINS_OF_ALPH_OUTSIDE", + "RUINS_OF_ALPH_HO_OH_CHAMBER", + "RUINS_OF_ALPH_KABUTO_CHAMBER", + "RUINS_OF_ALPH_OMANYTE_CHAMBER", + "RUINS_OF_ALPH_AERODACTYL_CHAMBER", + "RUINS_OF_ALPH_INNER_CHAMBER", + "RUINS_OF_ALPH_RESEARCH_CENTER", + "UNION_CAVE_1F", + "UNION_CAVE_B1F", + "UNION_CAVE_B2F", + "SLOWPOKE_WELL_B1F", + "SLOWPOKE_WELL_B2F", + "OLIVINE_LIGHTHOUSE_1F", + "OLIVINE_LIGHTHOUSE_2F", + "OLIVINE_LIGHTHOUSE_3F", + "OLIVINE_LIGHTHOUSE_4F", + "OLIVINE_LIGHTHOUSE_5F", + "OLIVINE_LIGHTHOUSE_6F", + "MAHOGANY_MART_1F", + "TEAM_ROCKET_BASE_B1F", + "TEAM_ROCKET_BASE_B2F", + "TEAM_ROCKET_BASE_B3F", + "ILEX_FOREST", + "GOLDENROD_UNDERGROUND", + "GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES", + "GOLDENROD_DEPT_STORE_B1F", + "GOLDENROD_UNDERGROUND_WAREHOUSE", + "MOUNT_MORTAR_1F_OUTSIDE", + "MOUNT_MORTAR_1F_INSIDE", + "MOUNT_MORTAR_2F_INSIDE", + "MOUNT_MORTAR_B1F", + "ICE_PATH_1F", + "ICE_PATH_B1F", + "ICE_PATH_B2F_MAHOGANY_SIDE", + "ICE_PATH_B2F_BLACKTHORN_SIDE", + "ICE_PATH_B3F", + "WHIRL_ISLAND_NW", + "WHIRL_ISLAND_NE", + "WHIRL_ISLAND_SW", + "WHIRL_ISLAND_CAVE", + "WHIRL_ISLAND_SE", + "WHIRL_ISLAND_B1F", + "WHIRL_ISLAND_B2F", + "WHIRL_ISLAND_LUGIA_CHAMBER", + "SILVER_CAVE_ROOM_1", + "SILVER_CAVE_ROOM_2", + "SILVER_CAVE_ROOM_3", + "SILVER_CAVE_ITEM_ROOMS", + "DARK_CAVE_VIOLET_ENTRANCE", + "DARK_CAVE_BLACKTHORN_ENTRANCE", + "DRAGONS_DEN_1F", + "DRAGONS_DEN_B1F", + "TOHJO_FALLS", + "DIGLETTS_CAVE", + "MOUNT_MOON", + "UNDERGROUND_PATH", + "ROCK_TUNNEL_1F", + "ROCK_TUNNEL_B1F", + "SAFARI_ZONE_FUCHSIA_GATE_BETA", + "SAFARI_ZONE_BETA", + "VICTORY_ROAD", + "ECRUTEAK_TIN_TOWER_ENTRANCE", + "ECRUTEAK_TIN_TOWER_BACK_ENTRANCE", + "ECRUTEAK_POKECENTER_1F", + "ECRUTEAK_LUGIA_SPEECH_HOUSE", + "DANCE_THEATER", + "ECRUTEAK_MART", + "ECRUTEAK_GYM", + "ECRUTEAK_ITEMFINDER_HOUSE", + "ECRUTEAK_CITY", + "BLACKTHORN_GYM_1F", + "BLACKTHORN_GYM_2F", + "BLACKTHORN_DRAGON_SPEECH_HOUSE", + "BLACKTHORN_EMYS_HOUSE", + "BLACKTHORN_MART", + "BLACKTHORN_POKECENTER_1F", + "MOVE_DELETERS_HOUSE", + "ROUTE_45", + "ROUTE_46", + "BLACKTHORN_CITY", + "CINNABAR_POKECENTER_1F", + "CINNABAR_POKECENTER_2F_BETA", + "ROUTE_19_FUCHSIA_GATE", + "SEAFOAM_GYM", + "ROUTE_19", + "ROUTE_20", + "ROUTE_21", + "CINNABAR_ISLAND", + "CERULEAN_GYM_BADGE_SPEECH_HOUSE", + "CERULEAN_POLICE_STATION", + "CERULEAN_TRADE_SPEECH_HOUSE", + "CERULEAN_POKECENTER_1F", + "CERULEAN_POKECENTER_2F_BETA", + "CERULEAN_GYM", + "CERULEAN_MART", + "ROUTE_10_POKECENTER_1F", + "ROUTE_10_POKECENTER_2F_BETA", + "POWER_PLANT", + "BILLS_HOUSE", + "ROUTE_4", + "ROUTE_9", + "ROUTE_10_NORTH", + "ROUTE_24", + "ROUTE_25", + "CERULEAN_CITY", + "AZALEA_POKECENTER_1F", + "CHARCOAL_KILN", + "AZALEA_MART", + "KURTS_HOUSE", + "AZALEA_GYM", + "ROUTE_33", + "AZALEA_TOWN", + "LAKE_OF_RAGE_HIDDEN_POWER_HOUSE", + "LAKE_OF_RAGE_MAGIKARP_HOUSE", + "ROUTE_43_MAHOGANY_GATE", + "ROUTE_43_GATE", + "ROUTE_43", + "LAKE_OF_RAGE", + "ROUTE_32", + "ROUTE_35", + "ROUTE_36", + "ROUTE_37", + "VIOLET_CITY", + "VIOLET_MART", + "VIOLET_GYM", + "EARLS_POKEMON_ACADEMY", + "VIOLET_NICKNAME_SPEECH_HOUSE", + "VIOLET_POKECENTER_1F", + "VIOLET_KYLES_HOUSE", + "ROUTE_32_RUINS_OF_ALPH_GATE", + "ROUTE_32_POKECENTER_1F", + "ROUTE_35_GOLDENROD_GATE", + "ROUTE_35_NATIONAL_PARK_GATE", + "ROUTE_36_RUINS_OF_ALPH_GATE", + "ROUTE_36_NATIONAL_PARK_GATE", + "ROUTE_34", + "GOLDENROD_CITY", + "GOLDENROD_GYM", + "GOLDENROD_BIKE_SHOP", + "GOLDENROD_HAPPINESS_RATER", + "BILLS_FAMILYS_HOUSE", + "GOLDENROD_MAGNET_TRAIN_STATION", + "GOLDENROD_FLOWER_SHOP", + "GOLDENROD_POKECENTER_1F", + "GOLDENROD_PP_SPEECH_HOUSE", + "GOLDENROD_NAME_RATER", + "GOLDENROD_DEPT_STORE_1F", + "GOLDENROD_DEPT_STORE_2F", + "GOLDENROD_DEPT_STORE_3F", + "GOLDENROD_DEPT_STORE_4F", + "GOLDENROD_DEPT_STORE_5F", + "GOLDENROD_DEPT_STORE_6F", + "GOLDENROD_DEPT_STORE_ELEVATOR", + "GOLDENROD_GAME_CORNER", + "ILEX_FOREST_AZALEA_GATE", + "ROUTE_34_ILEX_FOREST_GATE", + "DAY_CARE", + "ROUTE_6", + "ROUTE_11", + "VERMILION_CITY", + "VERMILION_FISHING_SPEECH_HOUSE", + "VERMILION_POKECENTER_1F", + "VERMILION_POKECENTER_2F_BETA", + "POKEMON_FAN_CLUB", + "VERMILION_MAGNET_TRAIN_SPEECH_HOUSE", + "VERMILION_MART", + "VERMILION_DIGLETTS_CAVE_SPEECH_HOUSE", + "VERMILION_GYM", + "ROUTE_6_SAFFRON_GATE", + "ROUTE_6_UNDERGROUND_PATH_ENTRANCE", + "ROUTE_1", + "PALLET_TOWN", + "REDS_HOUSE_1F", + "REDS_HOUSE_2F", + "BLUES_HOUSE", + "OAKS_LAB", + "ROUTE_3", + "PEWTER_CITY", + "PEWTER_NIDORAN_SPEECH_HOUSE", + "PEWTER_GYM", + "PEWTER_MART", + "PEWTER_POKECENTER_1F", + "PEWTER_POKECENTER_2F_BETA", + "PEWTER_SNOOZE_SPEECH_HOUSE", + "OLIVINE_PORT", + "VERMILION_PORT", + "FAST_SHIP_1F", + "FAST_SHIP_CABINS_NNW_NNE_NE", + "FAST_SHIP_CABINS_SW_SSW_NW", + "FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN", + "FAST_SHIP_B1F", + "OLIVINE_PORT_PASSAGE", + "VERMILION_PORT_PASSAGE", + "MOUNT_MOON_SQUARE", + "MOUNT_MOON_GIFT_SHOP", + "TIN_TOWER_ROOF", + "ROUTE_23", + "INDIGO_PLATEAU_POKECENTER_1F", + "WILLS_ROOM", + "KOGAS_ROOM", + "BRUNOS_ROOM", + "KARENS_ROOM", + "LANCES_ROOM", + "HALL_OF_FAME", + "ROUTE_13", + "ROUTE_14", + "ROUTE_15", + "ROUTE_18", + "FUCHSIA_CITY", + "FUCHSIA_MART", + "SAFARI_ZONE_MAIN_OFFICE", + "FUCHSIA_GYM", + "BILLS_OLDER_SISTERS_HOUSE", + "FUCHSIA_POKECENTER_1F", + "FUCHSIA_POKECENTER_2F_BETA", + "SAFARI_ZONE_WARDENS_HOME", + "ROUTE_15_FUCHSIA_GATE", + "ROUTE_8", + "ROUTE_12", + "ROUTE_10_SOUTH", + "LAVENDER_TOWN", + "LAVENDER_POKECENTER_1F", + "LAVENDER_POKECENTER_2F_BETA", + "MR_FUJIS_HOUSE", + "LAVENDER_SPEECH_HOUSE", + "LAVENDER_NAME_RATER", + "LAVENDER_MART", + "SOUL_HOUSE", + "LAV_RADIO_TOWER_1F", + "ROUTE_8_SAFFRON_GATE", + "ROUTE_12_SUPER_ROD_HOUSE", + "ROUTE_28", + "SILVER_CAVE_OUTSIDE", + "SILVER_CAVE_POKECENTER_1F", + "ROUTE_28_STEEL_WING_HOUSE", + "POKECENTER_2F", + "TRADE_CENTER", + "COLOSSEUM", + "TIME_CAPSULE", + "ROUTE_7", + "ROUTE_16", + "ROUTE_17", + "CELADON_CITY", + "CELADON_DEPT_STORE_1F", + "CELADON_DEPT_STORE_2F", + "CELADON_DEPT_STORE_3F", + "CELADON_DEPT_STORE_4F", + "CELADON_DEPT_STORE_5F", + "CELADON_DEPT_STORE_6F", + "CELADON_DEPT_STORE_ELEVATOR", + "CELADON_MANSION_1F", + "CELADON_MANSION_2F", + "CELADON_MANSION_3F", + "CELADON_MANSION_ROOF", + "CELADON_MANSION_ROOF_HOUSE", + "CELADON_POKECENTER_1F", + "CELADON_POKECENTER_2F_BETA", + "CELADON_GAME_CORNER", + "CELADON_GAME_CORNER_PRIZE_ROOM", + "CELADON_GYM", + "CELADON_CAFE", + "ROUTE_16_FUCHSIA_SPEECH_HOUSE", + "ROUTE_16_GATE", + "ROUTE_7_SAFFRON_GATE", + "ROUTE_17_ROUTE_18_GATE", + "ROUTE_40", + "ROUTE_41", + "CIANWOOD_CITY", + "MANIAS_HOUSE", + "CIANWOOD_GYM", + "CIANWOOD_POKECENTER_1F", + "CIANWOOD_PHARMACY", + "CIANWOOD_PHOTO_STUDIO", + "CIANWOOD_LUGIA_SPEECH_HOUSE", + "ROUTE_2", + "ROUTE_22", + "VIRIDIAN_CITY", + "VIRIDIAN_GYM", + "VIRIDIAN_NICKNAME_SPEECH_HOUSE", + "TRAINER_HOUSE_1F", + "TRAINER_HOUSE_B1F", + "VIRIDIAN_MART", + "VIRIDIAN_POKECENTER_1F", + "VIRIDIAN_POKECENTER_2F_BETA", + "ROUTE_2_NUGGET_HOUSE", + "ROUTE_2_GATE", + "VICTORY_ROAD_GATE", + "ROUTE_26", + "ROUTE_27", + "ROUTE_29", + "NEW_BARK_TOWN", + "ELMS_LAB", + "PLAYERS_HOUSE_1F", + "PLAYERS_HOUSE_2F", + "PLAYERS_NEIGHBORS_HOUSE", + "ELMS_HOUSE", + "ROUTE_26_HEAL_HOUSE", + "DAY_OF_WEEK_SIBLINGS_HOUSE", + "ROUTE_27_SANDSTORM_HOUSE", + "ROUTE_29_ROUTE_46_GATE", + "ROUTE_5", + "SAFFRON_CITY", + "FIGHTING_DOJO", + "SAFFRON_GYM", + "SAFFRON_MART", + "SAFFRON_POKECENTER_1F", + "SAFFRON_POKECENTER_2F_BETA", + "MR_PSYCHICS_HOUSE", + "SAFFRON_MAGNET_TRAIN_STATION", + "SILPH_CO_1F", + "COPYCATS_HOUSE_1F", + "COPYCATS_HOUSE_2F", + "ROUTE_5_UNDERGROUND_PATH_ENTRANCE", + "ROUTE_5_SAFFRON_GATE", + "ROUTE_5_CLEANSE_TAG_HOUSE", + "ROUTE_30", + "ROUTE_31", + "CHERRYGROVE_CITY", + "CHERRYGROVE_MART", + "CHERRYGROVE_POKECENTER_1F", + "CHERRYGROVE_GYM_SPEECH_HOUSE", + "GUIDE_GENTS_HOUSE", + "CHERRYGROVE_EVOLUTION_SPEECH_HOUSE", + "ROUTE_30_BERRY_HOUSE", + "MR_POKEMONS_HOUSE", + "ROUTE_31_VIOLET_GATE" + ], + "moveEffectOrder": [ + "EFFECT_NORMAL_HIT", + "EFFECT_SLEEP", + "EFFECT_POISON_HIT", + "EFFECT_LEECH_HIT", + "EFFECT_BURN_HIT", + "EFFECT_FREEZE_HIT", + "EFFECT_PARALYZE_HIT", + "EFFECT_SELFDESTRUCT", + "EFFECT_DREAM_EATER", + "EFFECT_MIRROR_MOVE", + "EFFECT_ATTACK_UP", + "EFFECT_DEFENSE_UP", + "EFFECT_SPEED_UP", + "EFFECT_SP_ATK_UP", + "EFFECT_SP_DEF_UP", + "EFFECT_ACCURACY_UP", + "EFFECT_EVASION_UP", + "EFFECT_ALWAYS_HIT", + "EFFECT_ATTACK_DOWN", + "EFFECT_DEFENSE_DOWN", + "EFFECT_SPEED_DOWN", + "EFFECT_SP_ATK_DOWN", + "EFFECT_SP_DEF_DOWN", + "EFFECT_ACCURACY_DOWN", + "EFFECT_EVASION_DOWN", + "EFFECT_RESET_STATS", + "EFFECT_BIDE", + "EFFECT_RAMPAGE", + "EFFECT_FORCE_SWITCH", + "EFFECT_MULTI_HIT", + "EFFECT_CONVERSION", + "EFFECT_FLINCH_HIT", + "EFFECT_HEAL", + "EFFECT_TOXIC", + "EFFECT_PAY_DAY", + "EFFECT_LIGHT_SCREEN", + "EFFECT_TRI_ATTACK", + "EFFECT_UNUSED_25", + "EFFECT_OHKO", + "EFFECT_RAZOR_WIND", + "EFFECT_SUPER_FANG", + "EFFECT_STATIC_DAMAGE", + "EFFECT_TRAP_TARGET", + "EFFECT_UNUSED_2B", + "EFFECT_DOUBLE_HIT", + "EFFECT_JUMP_KICK", + "EFFECT_MIST", + "EFFECT_FOCUS_ENERGY", + "EFFECT_RECOIL_HIT", + "EFFECT_CONFUSE", + "EFFECT_ATTACK_UP_2", + "EFFECT_DEFENSE_UP_2", + "EFFECT_SPEED_UP_2", + "EFFECT_SP_ATK_UP_2", + "EFFECT_SP_DEF_UP_2", + "EFFECT_ACCURACY_UP_2", + "EFFECT_EVASION_UP_2", + "EFFECT_TRANSFORM", + "EFFECT_ATTACK_DOWN_2", + "EFFECT_DEFENSE_DOWN_2", + "EFFECT_SPEED_DOWN_2", + "EFFECT_SP_ATK_DOWN_2", + "EFFECT_SP_DEF_DOWN_2", + "EFFECT_ACCURACY_DOWN_2", + "EFFECT_EVASION_DOWN_2", + "EFFECT_REFLECT", + "EFFECT_POISON", + "EFFECT_PARALYZE", + "EFFECT_ATTACK_DOWN_HIT", + "EFFECT_DEFENSE_DOWN_HIT", + "EFFECT_SPEED_DOWN_HIT", + "EFFECT_SP_ATK_DOWN_HIT", + "EFFECT_SP_DEF_DOWN_HIT", + "EFFECT_ACCURACY_DOWN_HIT", + "EFFECT_EVASION_DOWN_HIT", + "EFFECT_SKY_ATTACK", + "EFFECT_CONFUSE_HIT", + "EFFECT_POISON_MULTI_HIT", + "EFFECT_UNUSED_4E", + "EFFECT_SUBSTITUTE", + "EFFECT_HYPER_BEAM", + "EFFECT_RAGE", + "EFFECT_MIMIC", + "EFFECT_METRONOME", + "EFFECT_LEECH_SEED", + "EFFECT_SPLASH", + "EFFECT_DISABLE", + "EFFECT_LEVEL_DAMAGE", + "EFFECT_PSYWAVE", + "EFFECT_COUNTER", + "EFFECT_ENCORE", + "EFFECT_PAIN_SPLIT", + "EFFECT_SNORE", + "EFFECT_CONVERSION2", + "EFFECT_LOCK_ON", + "EFFECT_SKETCH", + "EFFECT_DEFROST_OPPONENT", + "EFFECT_SLEEP_TALK", + "EFFECT_DESTINY_BOND", + "EFFECT_REVERSAL", + "EFFECT_SPITE", + "EFFECT_FALSE_SWIPE", + "EFFECT_HEAL_BELL", + "EFFECT_PRIORITY_HIT", + "EFFECT_TRIPLE_KICK", + "EFFECT_THIEF", + "EFFECT_MEAN_LOOK", + "EFFECT_NIGHTMARE", + "EFFECT_FLAME_WHEEL", + "EFFECT_CURSE", + "EFFECT_UNUSED_6E", + "EFFECT_PROTECT", + "EFFECT_SPIKES", + "EFFECT_FORESIGHT", + "EFFECT_PERISH_SONG", + "EFFECT_SANDSTORM", + "EFFECT_ENDURE", + "EFFECT_ROLLOUT", + "EFFECT_SWAGGER", + "EFFECT_FURY_CUTTER", + "EFFECT_ATTRACT", + "EFFECT_RETURN", + "EFFECT_PRESENT", + "EFFECT_FRUSTRATION", + "EFFECT_SAFEGUARD", + "EFFECT_SACRED_FIRE", + "EFFECT_MAGNITUDE", + "EFFECT_BATON_PASS", + "EFFECT_PURSUIT", + "EFFECT_RAPID_SPIN", + "EFFECT_UNUSED_82", + "EFFECT_UNUSED_83", + "EFFECT_MORNING_SUN", + "EFFECT_SYNTHESIS", + "EFFECT_MOONLIGHT", + "EFFECT_HIDDEN_POWER", + "EFFECT_RAIN_DANCE", + "EFFECT_SUNNY_DAY", + "EFFECT_DEFENSE_UP_HIT", + "EFFECT_ATTACK_UP_HIT", + "EFFECT_ALL_UP_HIT", + "EFFECT_FAKE_OUT", + "EFFECT_BELLY_DRUM", + "EFFECT_PSYCH_UP", + "EFFECT_MIRROR_COAT", + "EFFECT_SKULL_BASH", + "EFFECT_TWISTER", + "EFFECT_EARTHQUAKE", + "EFFECT_FUTURE_SIGHT", + "EFFECT_GUST", + "EFFECT_STOMP", + "EFFECT_SOLARBEAM", + "EFFECT_THUNDER", + "EFFECT_TELEPORT", + "EFFECT_BEAT_UP", + "EFFECT_FLY", + "EFFECT_DEFENSE_CURL" + ], + "moveOrder": [ + "POUND", + "KARATE_CHOP", + "DOUBLESLAP", + "COMET_PUNCH", + "MEGA_PUNCH", + "PAY_DAY", + "FIRE_PUNCH", + "ICE_PUNCH", + "THUNDERPUNCH", + "SCRATCH", + "VICEGRIP", + "GUILLOTINE", + "RAZOR_WIND", + "SWORDS_DANCE", + "CUT", + "GUST", + "WING_ATTACK", + "WHIRLWIND", + "FLY", + "BIND", + "SLAM", + "VINE_WHIP", + "STOMP", + "DOUBLE_KICK", + "MEGA_KICK", + "JUMP_KICK", + "ROLLING_KICK", + "SAND_ATTACK", + "HEADBUTT", + "HORN_ATTACK", + "FURY_ATTACK", + "HORN_DRILL", + "TACKLE", + "BODY_SLAM", + "WRAP", + "TAKE_DOWN", + "THRASH", + "DOUBLE_EDGE", + "TAIL_WHIP", + "POISON_STING", + "TWINEEDLE", + "PIN_MISSILE", + "LEER", + "BITE", + "GROWL", + "ROAR", + "SING", + "SUPERSONIC", + "SONICBOOM", + "DISABLE", + "ACID", + "EMBER", + "FLAMETHROWER", + "MIST", + "WATER_GUN", + "HYDRO_PUMP", + "SURF", + "ICE_BEAM", + "BLIZZARD", + "PSYBEAM", + "BUBBLEBEAM", + "AURORA_BEAM", + "HYPER_BEAM", + "PECK", + "DRILL_PECK", + "SUBMISSION", + "LOW_KICK", + "COUNTER", + "SEISMIC_TOSS", + "STRENGTH", + "ABSORB", + "MEGA_DRAIN", + "LEECH_SEED", + "GROWTH", + "RAZOR_LEAF", + "SOLARBEAM", + "POISONPOWDER", + "STUN_SPORE", + "SLEEP_POWDER", + "PETAL_DANCE", + "STRING_SHOT", + "DRAGON_RAGE", + "FIRE_SPIN", + "THUNDERSHOCK", + "THUNDERBOLT", + "THUNDER_WAVE", + "THUNDER", + "ROCK_THROW", + "EARTHQUAKE", + "FISSURE", + "DIG", + "TOXIC", + "CONFUSION", + "PSYCHIC_M", + "HYPNOSIS", + "MEDITATE", + "AGILITY", + "QUICK_ATTACK", + "RAGE", + "TELEPORT", + "NIGHT_SHADE", + "MIMIC", + "SCREECH", + "DOUBLE_TEAM", + "RECOVER", + "HARDEN", + "MINIMIZE", + "SMOKESCREEN", + "CONFUSE_RAY", + "WITHDRAW", + "DEFENSE_CURL", + "BARRIER", + "LIGHT_SCREEN", + "HAZE", + "REFLECT", + "FOCUS_ENERGY", + "BIDE", + "METRONOME", + "MIRROR_MOVE", + "SELFDESTRUCT", + "EGG_BOMB", + "LICK", + "SMOG", + "SLUDGE", + "BONE_CLUB", + "FIRE_BLAST", + "WATERFALL", + "CLAMP", + "SWIFT", + "SKULL_BASH", + "SPIKE_CANNON", + "CONSTRICT", + "AMNESIA", + "KINESIS", + "SOFTBOILED", + "HI_JUMP_KICK", + "GLARE", + "DREAM_EATER", + "POISON_GAS", + "BARRAGE", + "LEECH_LIFE", + "LOVELY_KISS", + "SKY_ATTACK", + "TRANSFORM", + "BUBBLE", + "DIZZY_PUNCH", + "SPORE", + "FLASH", + "PSYWAVE", + "SPLASH", + "ACID_ARMOR", + "CRABHAMMER", + "EXPLOSION", + "FURY_SWIPES", + "BONEMERANG", + "REST", + "ROCK_SLIDE", + "HYPER_FANG", + "SHARPEN", + "CONVERSION", + "TRI_ATTACK", + "SUPER_FANG", + "SLASH", + "SUBSTITUTE", + "STRUGGLE", + "SKETCH", + "TRIPLE_KICK", + "THIEF", + "SPIDER_WEB", + "MIND_READER", + "NIGHTMARE", + "FLAME_WHEEL", + "SNORE", + "CURSE", + "FLAIL", + "CONVERSION2", + "AEROBLAST", + "COTTON_SPORE", + "REVERSAL", + "SPITE", + "POWDER_SNOW", + "PROTECT", + "MACH_PUNCH", + "SCARY_FACE", + "FAINT_ATTACK", + "SWEET_KISS", + "BELLY_DRUM", + "SLUDGE_BOMB", + "MUD_SLAP", + "OCTAZOOKA", + "SPIKES", + "ZAP_CANNON", + "FORESIGHT", + "DESTINY_BOND", + "PERISH_SONG", + "ICY_WIND", + "DETECT", + "BONE_RUSH", + "LOCK_ON", + "OUTRAGE", + "SANDSTORM", + "GIGA_DRAIN", + "ENDURE", + "CHARM", + "ROLLOUT", + "FALSE_SWIPE", + "SWAGGER", + "MILK_DRINK", + "SPARK", + "FURY_CUTTER", + "STEEL_WING", + "MEAN_LOOK", + "ATTRACT", + "SLEEP_TALK", + "HEAL_BELL", + "RETURN", + "PRESENT", + "FRUSTRATION", + "SAFEGUARD", + "PAIN_SPLIT", + "SACRED_FIRE", + "MAGNITUDE", + "DYNAMICPUNCH", + "MEGAHORN", + "DRAGONBREATH", + "BATON_PASS", + "ENCORE", + "PURSUIT", + "RAPID_SPIN", + "SWEET_SCENT", + "IRON_TAIL", + "METAL_CLAW", + "VITAL_THROW", + "MORNING_SUN", + "SYNTHESIS", + "MOONLIGHT", + "HIDDEN_POWER", + "CROSS_CHOP", + "TWISTER", + "RAIN_DANCE", + "SUNNY_DAY", + "CRUNCH", + "MIRROR_COAT", + "PSYCH_UP", + "EXTREMESPEED", + "ANCIENTPOWER", + "SHADOW_BALL", + "FUTURE_SIGHT", + "ROCK_SMASH", + "WHIRLPOOL", + "BEAT_UP" + ], + "musicOrder": [ + "Music_Nothing", + "Music_TitleScreen", + "Music_Route1", + "Music_Route3", + "Music_Route12", + "Music_MagnetTrain", + "Music_KantoGymBattle", + "Music_KantoTrainerBattle", + "Music_KantoWildBattle", + "Music_PokemonCenter", + "Music_LookHiker", + "Music_LookLass", + "Music_LookOfficer", + "Music_HealPokemon", + "Music_LavenderTown", + "Music_Route2", + "Music_MtMoon", + "Music_ShowMeAround", + "Music_GameCorner", + "Music_Bicycle", + "Music_HallOfFame", + "Music_ViridianCity", + "Music_CeladonCity", + "Music_TrainerVictory", + "Music_WildPokemonVictory", + "Music_GymLeaderVictory", + "Music_MtMoonSquare", + "Music_Gym", + "Music_PalletTown", + "Music_ProfOaksPokemonTalk", + "Music_ProfOak", + "Music_LookRival", + "Music_AfterTheRivalFight", + "Music_Surf", + "Music_Evolution", + "Music_NationalPark", + "Music_Credits", + "Music_AzaleaTown", + "Music_CherrygroveCity", + "Music_LookKimonoGirl", + "Music_UnionCave", + "Music_JohtoWildBattle", + "Music_JohtoTrainerBattle", + "Music_Route30", + "Music_EcruteakCity", + "Music_VioletCity", + "Music_JohtoGymBattle", + "Music_ChampionBattle", + "Music_RivalBattle", + "Music_RocketBattle", + "Music_ElmsLab", + "Music_DarkCave", + "Music_Route29", + "Music_Route36", + "Music_SSAqua", + "Music_LookYoungster", + "Music_LookBeauty", + "Music_LookRocket", + "Music_LookPokemaniac", + "Music_LookSage", + "Music_NewBarkTown", + "Music_GoldenrodCity", + "Music_VermilionCity", + "Music_PokemonChannel", + "Music_PokeFluteChannel", + "Music_TinTower", + "Music_SproutTower", + "Music_BurnedTower", + "Music_Lighthouse", + "Music_LakeOfRage", + "Music_IndigoPlateau", + "Music_Route37", + "Music_RocketHideout", + "Music_DragonsDen", + "Music_JohtoWildBattleNight", + "Music_RuinsOfAlphRadio", + "Music_SuccessfulCapture", + "Music_Route26", + "Music_Mom", + "Music_VictoryRoad", + "Music_PokemonLullaby", + "Music_PokemonMarch", + "Music_GoldSilverOpening", + "Music_GoldSilverOpening2", + "Music_MainMenu", + "Music_RuinsOfAlphInterior", + "Music_RocketTheme", + "Music_DancingHall", + "Music_ContestResults", + "Music_BugCatchingContest", + "Music_LakeOfRageRocketRadio", + "Music_Printer", + "Music_PostCredits" + ], + "numOverworldSprites": 95, + "paletteOrder": [ + "PALETTE_AUTO", + "PALETTE_DAY", + "PALETTE_NITE", + "PALETTE_MORN", + "PALETTE_DARK" + ], + "phoneContactOrder": [ + "PHONE_00", + "PHONE_MOM", + "PHONE_OAK", + "PHONE_BILL", + "PHONE_ELM", + "PHONE_SCHOOLBOY_JACK", + "PHONE_POKEFAN_BEVERLY", + "PHONE_SAILOR_HUEY", + "PHONE_UNUSED", + "PHONE_UNUSED", + "PHONE_UNUSED", + "PHONE_COOLTRAINERM_GAVEN", + "PHONE_COOLTRAINERF_BETH", + "PHONE_BIRDKEEPER_JOSE", + "PHONE_COOLTRAINERF_REENA", + "PHONE_YOUNGSTER_JOEY", + "PHONE_BUG_CATCHER_WADE", + "PHONE_FISHER_RALPH", + "PHONE_PICNICKER_LIZ", + "PHONE_HIKER_ANTHONY", + "PHONE_CAMPER_TODD", + "PHONE_PICNICKER_GINA", + "PHONE_JUGGLER_IRWIN", + "PHONE_BUG_CATCHER_ARNIE", + "PHONE_SCHOOLBOY_ALAN", + "PHONE_UNUSED", + "PHONE_LASS_DANA", + "PHONE_SCHOOLBOY_CHAD", + "PHONE_POKEFANM_DEREK", + "PHONE_FISHER_CHRIS", + "PHONE_POKEMANIAC_BRENT", + "PHONE_PICNICKER_TIFFANY", + "PHONE_BIRDKEEPER_VANCE", + "PHONE_FISHER_WILTON", + "PHONE_BLACKBELT_KENJI", + "PHONE_HIKER_PARRY", + "PHONE_PICNICKER_ERIN" + ], + "pocketOrder": [ + "ITEM", + "KEY_ITEM", + "BALL", + "TM_HM" + ], + "sfxOrder": [ + "Sfx_DexFanfare5079", + "Sfx_Item", + "Sfx_CaughtMon", + "Sfx_PokeballsPlacedOnTable", + "Sfx_Potion", + "Sfx_FullHeal", + "Sfx_Menu", + "Sfx_ReadText", + "Sfx_ReadText2", + "Sfx_DexFanfare2049", + "Sfx_DexFanfare80109", + "Sfx_Poison", + "Sfx_GotSafariBalls", + "Sfx_BootPc", + "Sfx_ShutDownPc", + "Sfx_ChoosePcOption", + "Sfx_EscapeRope", + "Sfx_PushButton", + "Sfx_SecondPartOfItemfinder", + "Sfx_WarpTo", + "Sfx_WarpFrom", + "Sfx_ChangeDexMode", + "Sfx_JumpOverLedge", + "Sfx_GrassRustle", + "Sfx_Fly", + "Sfx_Wrong", + "Sfx_Squeak", + "Sfx_Strength", + "Sfx_Boat", + "Sfx_WallOpen", + "Sfx_PlacePuzzlePieceDown", + "Sfx_EnterDoor", + "Sfx_SwitchPokemon", + "Sfx_Tally", + "Sfx_Transaction", + "Sfx_ExitBuilding", + "Sfx_Bump", + "Sfx_Save", + "Sfx_Pokeflute", + "Sfx_ElevatorEnd", + "Sfx_ThrowBall", + "Sfx_BallPoof", + "Sfx_Faint", + "Sfx_Run", + "Sfx_SlotMachineStart", + "Sfx_Fanfare", + "Sfx_Peck", + "Sfx_Kinesis", + "Sfx_Lick", + "Sfx_Pound", + "Sfx_MovePuzzlePiece", + "Sfx_CometPunch", + "Sfx_MegaPunch", + "Sfx_Scratch", + "Sfx_Vicegrip", + "Sfx_RazorWind", + "Sfx_Cut", + "Sfx_WingAttack", + "Sfx_Whirlwind", + "Sfx_Bind", + "Sfx_VineWhip", + "Sfx_DoubleKick", + "Sfx_MegaKick", + "Sfx_Headbutt", + "Sfx_HornAttack", + "Sfx_Tackle", + "Sfx_PoisonSting", + "Sfx_Powder", + "Sfx_Doubleslap", + "Sfx_Bite", + "Sfx_JumpKick", + "Sfx_Stomp", + "Sfx_TailWhip", + "Sfx_KarateChop", + "Sfx_Submission", + "Sfx_WaterGun", + "Sfx_SwordsDance", + "Sfx_Thunder", + "Sfx_Supersonic", + "Sfx_Leer", + "Sfx_Ember", + "Sfx_Bubblebeam", + "Sfx_HydroPump", + "Sfx_Surf", + "Sfx_Psybeam", + "Sfx_Charge", + "Sfx_Thundershock", + "Sfx_Psychic", + "Sfx_Screech", + "Sfx_BoneClub", + "Sfx_Sharpen", + "Sfx_EggBomb", + "Sfx_Sing", + "Sfx_HyperBeam", + "Sfx_Shine", + "Sfx_Unknown5F", + "Sfx_Unknown60", + "Sfx_Unknown61", + "Sfx_SwitchPockets", + "Sfx_Unknown63", + "Sfx_Burn", + "Sfx_TitleScreenEntrance", + "Sfx_Unknown66", + "Sfx_GetCoinFromSlots", + "Sfx_PayDay", + "Sfx_Metronome", + "Sfx_Call", + "Sfx_HangUp", + "Sfx_NoSignal", + "Sfx_Sandstorm", + "Sfx_Elevator", + "Sfx_Protect", + "Sfx_Sketch", + "Sfx_RainDance", + "Sfx_Aeroblast", + "Sfx_Spark", + "Sfx_Curse", + "Sfx_Rage", + "Sfx_Thief", + "Sfx_Thief2", + "Sfx_SpiderWeb", + "Sfx_MindReader", + "Sfx_Nightmare", + "Sfx_Snore", + "Sfx_SweetKiss", + "Sfx_SweetKiss2", + "Sfx_BellyDrum", + "Sfx_Toxic", + "Sfx_SludgeBomb", + "Sfx_Foresight", + "Sfx_Spite", + "Sfx_Outrage", + "Sfx_PerishSong", + "Sfx_GigaDrain", + "Sfx_Attract", + "Sfx_Kinesis2", + "Sfx_ZapCannon", + "Sfx_MeanLook", + "Sfx_HealBell", + "Sfx_Return", + "Sfx_ExpBar", + "Sfx_MilkDrink", + "Sfx_Present", + "Sfx_MorningSun", + "Sfx_LevelUp", + "Sfx_KeyItem", + "Sfx_Fanfare2", + "Sfx_RegisterPhoneNumber", + "Sfx_3rdPlace", + "Sfx_GetEgg", + "Sfx_GetEgg", + "Sfx_MoveDeleted", + "Sfx_2ndPlace", + "Sfx_1stPlace", + "Sfx_ChooseACard", + "Sfx_GetTm", + "Sfx_GetBadge", + "Sfx_QuitSlots", + "Sfx_EggCrack", + "Sfx_DexFanfareLessThan20", + "Sfx_DexFanfare140169", + "Sfx_DexFanfare170199", + "Sfx_DexFanfare200229", + "Sfx_DexFanfare230Plus", + "Sfx_Evolved", + "Sfx_MasterBall", + "Sfx_EggHatch", + "Sfx_GsIntroCharizardFireball", + "Sfx_GsIntroPokemonAppears", + "Sfx_Flash", + "Sfx_GameFreakLogoGs", + "Sfx_NotVeryEffective", + "Sfx_Damage", + "Sfx_SuperEffective", + "Sfx_BallBounce", + "Sfx_Moonlight", + "Sfx_Encore", + "Sfx_BeatUp", + "Sfx_BatonPass", + "Sfx_BallWobble", + "Sfx_SweetScent", + "Sfx_SweetScent2", + "Sfx_HitEndOfExpBar", + "Sfx_GiveTrademon", + "Sfx_GetTrademon", + "Sfx_TrainArrived", + "Sfx_StopSlot", + "Sfx_2Boops" + ], + "source": "pret/pokegold constants/*.asm", + "spawnOrder": [ + "SPAWN_HOME", + "SPAWN_DEBUG", + "SPAWN_PALLET", + "SPAWN_VIRIDIAN", + "SPAWN_PEWTER", + "SPAWN_CERULEAN", + "SPAWN_ROCK_TUNNEL", + "SPAWN_VERMILION", + "SPAWN_LAVENDER", + "SPAWN_SAFFRON", + "SPAWN_CELADON", + "SPAWN_FUCHSIA", + "SPAWN_CINNABAR", + "SPAWN_INDIGO", + "SPAWN_NEW_BARK", + "SPAWN_CHERRYGROVE", + "SPAWN_VIOLET", + "SPAWN_UNION_CAVE", + "SPAWN_AZALEA", + "SPAWN_CIANWOOD", + "SPAWN_GOLDENROD", + "SPAWN_OLIVINE", + "SPAWN_ECRUTEAK", + "SPAWN_MAHOGANY", + "SPAWN_LAKE_OF_RAGE", + "SPAWN_BLACKTHORN", + "SPAWN_MT_SILVER", + "SPAWN_FAST_SHIP" + ], + "specialCallOrder": [ + "SPECIALCALL_NONE", + "SPECIALCALL_POKERUS", + "SPECIALCALL_ROBBED", + "SPECIALCALL_ASSISTANT", + "SPECIALCALL_WEIRDBROADCAST", + "SPECIALCALL_SSTICKET", + "SPECIALCALL_BIKESHOP", + "SPECIALCALL_WORRIED", + "SPECIALCALL_MASTERBALL" + ], + "specialOrder": [ + "WarpToSpawnPoint", + "SetBitsForLinkTradeRequest", + "WaitForLinkedFriend", + "CheckLinkTimeout_Receptionist", + "TryQuickSave", + "CheckBothSelectedSameRoom", + "FailedLinkToPast", + "CloseLink", + "WaitForOtherPlayerToExit", + "SetBitsForBattleRequest", + "SetBitsForTimeCapsuleRequest", + "CheckTimeCapsuleCompatibility", + "EnterTimeCapsule", + "TradeCenter", + "Colosseum", + "TimeCapsule", + "CableClubCheckWhichChris", + "CheckMysteryGift", + "GetMysteryGiftItem", + "UnlockMysteryGift", + "BugContestJudging", + "CheckPartyFullAfterContest", + "ContestDropOffMons", + "ContestReturnMons", + "GiveParkBalls", + "CheckMagikarpLength", + "MagikarpHouseSign", + "HealParty", + "PokemonCenterPC", + "PlayersHousePC", + "DayCareMan", + "DayCareLady", + "DayCareManOutside", + "MoveDeletion", + "BankOfMom", + "MagnetTrain", + "NameRival", + "SetDayOfWeek", + "OverworldTownMap", + "UnownPrinter", + "MapRadio", + "UnownPuzzle", + "SlotMachine", + "CardFlip", + "UnusedMemoryGame", + "ClearBGPalettesBufferScreen", + "FadeOutToWhite", + "FadeOutToBlack", + "FadeInFromWhite", + "FadeInFromBlack", + "ReloadSpritesNoPalettes", + "ClearBGPalettes", + "UpdateTimePals", + "ClearTilemap", + "UpdateSprites", + "UpdatePlayerSprite", + "GameCornerPrizeMonCheckDex", + "UnusedSetSeenMon", + "WaitSFX", + "PlayMapMusic", + "RestartMapMusic", + "HealMachineAnim", + "SurfStartStep", + "FindPartyMonAboveLevel", + "FindPartyMonAtLeastThatHappy", + "FindPartyMonThatSpecies", + "FindPartyMonThatSpeciesYourTrainerID", + "UnusedCheckUnusedTwoDayTimer", + "DayCareMon1", + "DayCareMon2", + "SelectRandomBugContestContestants", + "ActivateFishingSwarm", + "ToggleMaptileDecorations", + "ToggleDecorationsVisibility", + "GiveShuckle", + "ReturnShuckie", + "BillsGrandfather", + "CheckPokerus", + "DisplayCoinCaseBalance", + "DisplayMoneyAndCoinBalance", + "PlaceMoneyTopRight", + "CheckForLuckyNumberWinners", + "CheckLuckyNumberShowFlag", + "ResetLuckyNumberShowFlag", + "PrintTodaysLuckyNumber", + "SelectApricornForKurt", + "NameRater", + "DisplayLinkRecord", + "GetFirstPokemonHappiness", + "CheckFirstMonIsEgg", + "RandomUnseenWildMon", + "RandomPhoneWildMon", + "RandomPhoneMon", + "LoadUsedSpritesGFX", + "PlaySlowCry", + "SnorlaxAwake", + "OlderHaircutBrother", + "YoungerHaircutBrother", + "DaisysGrooming", + "PlayCurMonCry", + "ProfOaksPCBoot", + "GameboyCheck", + "TrainerHouse", + "PhotoStudio", + "InitRoamMons", + "FadeOutMusic", + "Diploma", + "PrintDiploma", + "InitialSetDSTFlag", + "InitialClearDSTFlag", + "MrChrono", + "UnusedDummySpecial" + ], + "speciesOrder": [ + "BULBASAUR", + "IVYSAUR", + "VENUSAUR", + "CHARMANDER", + "CHARMELEON", + "CHARIZARD", + "SQUIRTLE", + "WARTORTLE", + "BLASTOISE", + "CATERPIE", + "METAPOD", + "BUTTERFREE", + "WEEDLE", + "KAKUNA", + "BEEDRILL", + "PIDGEY", + "PIDGEOTTO", + "PIDGEOT", + "RATTATA", + "RATICATE", + "SPEAROW", + "FEAROW", + "EKANS", + "ARBOK", + "PIKACHU", + "RAICHU", + "SANDSHREW", + "SANDSLASH", + "NIDORAN_F", + "NIDORINA", + "NIDOQUEEN", + "NIDORAN_M", + "NIDORINO", + "NIDOKING", + "CLEFAIRY", + "CLEFABLE", + "VULPIX", + "NINETALES", + "JIGGLYPUFF", + "WIGGLYTUFF", + "ZUBAT", + "GOLBAT", + "ODDISH", + "GLOOM", + "VILEPLUME", + "PARAS", + "PARASECT", + "VENONAT", + "VENOMOTH", + "DIGLETT", + "DUGTRIO", + "MEOWTH", + "PERSIAN", + "PSYDUCK", + "GOLDUCK", + "MANKEY", + "PRIMEAPE", + "GROWLITHE", + "ARCANINE", + "POLIWAG", + "POLIWHIRL", + "POLIWRATH", + "ABRA", + "KADABRA", + "ALAKAZAM", + "MACHOP", + "MACHOKE", + "MACHAMP", + "BELLSPROUT", + "WEEPINBELL", + "VICTREEBEL", + "TENTACOOL", + "TENTACRUEL", + "GEODUDE", + "GRAVELER", + "GOLEM", + "PONYTA", + "RAPIDASH", + "SLOWPOKE", + "SLOWBRO", + "MAGNEMITE", + "MAGNETON", + "FARFETCH_D", + "DODUO", + "DODRIO", + "SEEL", + "DEWGONG", + "GRIMER", + "MUK", + "SHELLDER", + "CLOYSTER", + "GASTLY", + "HAUNTER", + "GENGAR", + "ONIX", + "DROWZEE", + "HYPNO", + "KRABBY", + "KINGLER", + "VOLTORB", + "ELECTRODE", + "EXEGGCUTE", + "EXEGGUTOR", + "CUBONE", + "MAROWAK", + "HITMONLEE", + "HITMONCHAN", + "LICKITUNG", + "KOFFING", + "WEEZING", + "RHYHORN", + "RHYDON", + "CHANSEY", + "TANGELA", + "KANGASKHAN", + "HORSEA", + "SEADRA", + "GOLDEEN", + "SEAKING", + "STARYU", + "STARMIE", + "MR__MIME", + "SCYTHER", + "JYNX", + "ELECTABUZZ", + "MAGMAR", + "PINSIR", + "TAUROS", + "MAGIKARP", + "GYARADOS", + "LAPRAS", + "DITTO", + "EEVEE", + "VAPOREON", + "JOLTEON", + "FLAREON", + "PORYGON", + "OMANYTE", + "OMASTAR", + "KABUTO", + "KABUTOPS", + "AERODACTYL", + "SNORLAX", + "ARTICUNO", + "ZAPDOS", + "MOLTRES", + "DRATINI", + "DRAGONAIR", + "DRAGONITE", + "MEWTWO", + "MEW", + "CHIKORITA", + "BAYLEEF", + "MEGANIUM", + "CYNDAQUIL", + "QUILAVA", + "TYPHLOSION", + "TOTODILE", + "CROCONAW", + "FERALIGATR", + "SENTRET", + "FURRET", + "HOOTHOOT", + "NOCTOWL", + "LEDYBA", + "LEDIAN", + "SPINARAK", + "ARIADOS", + "CROBAT", + "CHINCHOU", + "LANTURN", + "PICHU", + "CLEFFA", + "IGGLYBUFF", + "TOGEPI", + "TOGETIC", + "NATU", + "XATU", + "MAREEP", + "FLAAFFY", + "AMPHAROS", + "BELLOSSOM", + "MARILL", + "AZUMARILL", + "SUDOWOODO", + "POLITOED", + "HOPPIP", + "SKIPLOOM", + "JUMPLUFF", + "AIPOM", + "SUNKERN", + "SUNFLORA", + "YANMA", + "WOOPER", + "QUAGSIRE", + "ESPEON", + "UMBREON", + "MURKROW", + "SLOWKING", + "MISDREAVUS", + "UNOWN", + "WOBBUFFET", + "GIRAFARIG", + "PINECO", + "FORRETRESS", + "DUNSPARCE", + "GLIGAR", + "STEELIX", + "SNUBBULL", + "GRANBULL", + "QWILFISH", + "SCIZOR", + "SHUCKLE", + "HERACROSS", + "SNEASEL", + "TEDDIURSA", + "URSARING", + "SLUGMA", + "MAGCARGO", + "SWINUB", + "PILOSWINE", + "CORSOLA", + "REMORAID", + "OCTILLERY", + "DELIBIRD", + "MANTINE", + "SKARMORY", + "HOUNDOUR", + "HOUNDOOM", + "KINGDRA", + "PHANPY", + "DONPHAN", + "PORYGON2", + "STANTLER", + "SMEARGLE", + "TYROGUE", + "HITMONTOP", + "SMOOCHUM", + "ELEKID", + "MAGBY", + "MILTANK", + "BLISSEY", + "RAIKOU", + "ENTEI", + "SUICUNE", + "LARVITAR", + "PUPITAR", + "TYRANITAR", + "LUGIA", + "HO_OH", + "CELEBI" + ], + "spriteOrder": [ + "SPRITE_CHRIS", + "SPRITE_CHRIS_BIKE", + "SPRITE_GAMEBOY_KID", + "SPRITE_RIVAL", + "SPRITE_OAK", + "SPRITE_RED", + "SPRITE_BLUE", + "SPRITE_BILL", + "SPRITE_ELDER", + "SPRITE_JANINE", + "SPRITE_KURT", + "SPRITE_MOM", + "SPRITE_BLAINE", + "SPRITE_REDS_MOM", + "SPRITE_DAISY", + "SPRITE_ELM", + "SPRITE_WILL", + "SPRITE_FALKNER", + "SPRITE_WHITNEY", + "SPRITE_BUGSY", + "SPRITE_MORTY", + "SPRITE_CHUCK", + "SPRITE_JASMINE", + "SPRITE_PRYCE", + "SPRITE_CLAIR", + "SPRITE_BROCK", + "SPRITE_KAREN", + "SPRITE_BRUNO", + "SPRITE_MISTY", + "SPRITE_LANCE", + "SPRITE_SURGE", + "SPRITE_ERIKA", + "SPRITE_KOGA", + "SPRITE_SABRINA", + "SPRITE_COOLTRAINER_M", + "SPRITE_COOLTRAINER_F", + "SPRITE_BUG_CATCHER", + "SPRITE_TWIN", + "SPRITE_YOUNGSTER", + "SPRITE_LASS", + "SPRITE_TEACHER", + "SPRITE_BEAUTY", + "SPRITE_SUPER_NERD", + "SPRITE_ROCKER", + "SPRITE_POKEFAN_M", + "SPRITE_POKEFAN_F", + "SPRITE_GRAMPS", + "SPRITE_GRANNY", + "SPRITE_SWIMMER_GUY", + "SPRITE_SWIMMER_GIRL", + "SPRITE_BIG_SNORLAX", + "SPRITE_SURFING_PIKACHU", + "SPRITE_ROCKET", + "SPRITE_ROCKET_GIRL", + "SPRITE_NURSE", + "SPRITE_LINK_RECEPTIONIST", + "SPRITE_CLERK", + "SPRITE_FISHER", + "SPRITE_FISHING_GURU", + "SPRITE_SCIENTIST", + "SPRITE_KIMONO_GIRL", + "SPRITE_SAGE", + "SPRITE_UNUSED_GUY", + "SPRITE_GENTLEMAN", + "SPRITE_BLACK_BELT", + "SPRITE_RECEPTIONIST", + "SPRITE_OFFICER", + "SPRITE_CAL", + "SPRITE_SLOWPOKE", + "SPRITE_CAPTAIN", + "SPRITE_BIG_LAPRAS", + "SPRITE_GYM_GUIDE", + "SPRITE_SAILOR", + "SPRITE_BIKER", + "SPRITE_PHARMACIST", + "SPRITE_MONSTER", + "SPRITE_FAIRY", + "SPRITE_BIRD", + "SPRITE_DRAGON", + "SPRITE_BIG_ONIX", + "SPRITE_N64", + "SPRITE_SUDOWOODO", + "SPRITE_SURF", + "SPRITE_POKE_BALL", + "SPRITE_POKEDEX", + "SPRITE_PAPER", + "SPRITE_VIRTUAL_BOY", + "SPRITE_OLD_LINK_RECEPTIONIST", + "SPRITE_ROCK", + "SPRITE_BOULDER", + "SPRITE_SNES", + "SPRITE_FAMICOM", + "SPRITE_FRUIT_TREE", + "SPRITE_GOLD_TROPHY", + "SPRITE_SILVER_TROPHY", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "UNUSED", + "SPRITE_UNOWN", + "SPRITE_GEODUDE", + "SPRITE_GROWLITHE", + "SPRITE_WEEDLE", + "SPRITE_SHELLDER", + "SPRITE_ODDISH", + "SPRITE_GENGAR", + "SPRITE_ZUBAT", + "SPRITE_MAGIKARP", + "SPRITE_SQUIRTLE", + "SPRITE_TOGEPI", + "SPRITE_BUTTERFREE", + "SPRITE_DIGLETT", + "SPRITE_POLIWAG", + "SPRITE_PIKACHU", + "SPRITE_CLEFAIRY", + "SPRITE_CHARMANDER", + "SPRITE_JYNX", + "SPRITE_STARMIE", + "SPRITE_BULBASAUR", + "SPRITE_JIGGLYPUFF", + "SPRITE_GRIMER", + "SPRITE_EKANS", + "SPRITE_PARAS", + "SPRITE_TENTACOOL", + "SPRITE_TAUROS", + "SPRITE_MACHOP", + "SPRITE_VOLTORB", + "SPRITE_LAPRAS", + "SPRITE_RHYDON", + "SPRITE_MOLTRES", + "SPRITE_SNORLAX", + "SPRITE_GYARADOS", + "SPRITE_LUGIA", + "SPRITE_HO_OH" + ], + "spritePokemon": 128, + "stdScriptOrder": [ + "PokecenterNurseScript", + "DifficultBookshelfScript", + "PictureBookshelfScript", + "MagazineBookshelfScript", + "TeamRocketOathScript", + "IncenseBurnerScript", + "MerchandiseShelfScript", + "TownMapScript", + "WindowScript", + "TVScript", + "HomepageScript", + "Radio1Script", + "Radio2Script", + "TrashCanScript", + "StrengthBoulderScript", + "SmashRockScript", + "PokecenterSignScript", + "MartSignScript", + "GoldenrodRocketsScript", + "RadioTowerRocketsScript", + "ElevatorButtonScript", + "DayToTextScript", + "BugContestResultsWarpScript", + "BugContestResultsScript", + "InitializeEventsScript", + "AskNumber1MScript", + "AskNumber2MScript", + "RegisteredNumberMScript", + "NumberAcceptedMScript", + "NumberDeclinedMScript", + "PhoneFullMScript", + "RematchMScript", + "AskNumber1FScript", + "AskNumber2FScript", + "RegisteredNumberFScript", + "NumberAcceptedFScript", + "NumberDeclinedFScript", + "PhoneFullFScript", + "RematchFScript", + "GymStatue1Script", + "GymStatue2Script", + "ReceiveItemScript", + "ReceiveTogepiEggScript", + "PCScript", + "GameCornerCoinVendorScript", + "HappinessCheckScript" + ], + "tilesetOrder": [ + "TILESET_JOHTO", + "TILESET_JOHTO_MODERN", + "TILESET_KANTO", + "TILESET_HOUSE", + "TILESET_PLAYERS_HOUSE", + "TILESET_POKECENTER", + "TILESET_GATE", + "TILESET_PORT", + "TILESET_LAB", + "TILESET_FACILITY", + "TILESET_MART", + "TILESET_MANSION", + "TILESET_GAME_CORNER", + "TILESET_ELITE_FOUR_ROOM", + "TILESET_TRADITIONAL_HOUSE", + "TILESET_TRAIN_STATION", + "TILESET_CHAMPIONS_ROOM", + "TILESET_LIGHTHOUSE", + "TILESET_PLAYERS_ROOM", + "TILESET_TOWER", + "TILESET_CAVE", + "TILESET_PARK", + "TILESET_RUINS_OF_ALPH", + "TILESET_RADIO_TOWER", + "TILESET_UNDERGROUND", + "TILESET_ICE_PATH", + "TILESET_DARK_CAVE", + "TILESET_FOREST" + ], + "tradeDialogOrder": [ + "TRADE_DIALOGSET_COLLECTOR", + "TRADE_DIALOGSET_HAPPY", + "TRADE_DIALOGSET_NEWBIE" + ], + "tradeGenderOrder": [ + "TRADE_GENDER_EITHER", + "TRADE_GENDER_MALE", + "TRADE_GENDER_FEMALE" + ], + "trainerClassMembers": { + "BEAUTY": [ + "VICTORIA", + "SAMANTHA", + "JULIE", + "JACLYN", + "BRENDA", + "CASSIE", + "CAROLINE", + "CARLENE", + "JESSICA", + "RACHAEL", + "ANGELICA", + "KENDRA", + "VERONICA", + "JULIA", + "THERESA", + "VALERIE" + ], + "BIKER": [ + "KAZU1", + "KAZU2", + "DWAYNE", + "HARRIS", + "ZEKE", + "CHARLES", + "RILEY", + "JOEL", + "GLENN" + ], + "BIRD_KEEPER": [ + "ROD", + "ABE", + "BRYAN", + "THEO", + "TOBY", + "DENIS", + "VANCE1", + "HANK", + "ROY", + "BORIS", + "BOB", + "JOSE1", + "PETER", + "JOSE2", + "PERRY", + "BRET", + "JOSE3", + "VANCE2", + "VANCE3" + ], + "BLACKBELT_T": [ + "KENJI1", + "YOSHI", + "KENJI2", + "LAO", + "NOB", + "KIYO", + "LUNG", + "KENJI3", + "WAI" + ], + "BLAINE": [ + "BLAINE1" + ], + "BLUE": [ + "BLUE1" + ], + "BOARDER": [ + "RONALD", + "BRAD", + "DOUGLAS" + ], + "BROCK": [ + "BROCK1" + ], + "BRUNO": [ + "BRUNO1" + ], + "BUGSY": [ + "BUGSY1" + ], + "BUG_CATCHER": [ + "DON", + "ROB", + "ED", + "WADE1", + "BENNY", + "AL", + "JOSH", + "ARNIE1", + "KEN", + "WADE2", + "WADE3", + "DOUG", + "ARNIE2", + "ARNIE3" + ], + "BURGLAR": [ + "DUNCAN", + "EDDIE", + "COREY" + ], + "CAL": [ + "CAL1", + "CAL2", + "CAL3" + ], + "CAMPER": [ + "ROLAND", + "TODD1", + "IVAN", + "ELLIOT", + "BARRY", + "LLOYD", + "DEAN", + "SID", + "HARVEY", + "DALE", + "TED", + "TODD2", + "TODD3", + "THOMAS", + "LEROY", + "DAVID", + "JOHN", + "JERRY", + "SPENCER" + ], + "CHAMPION": [ + "LANCE" + ], + "CHUCK": [ + "CHUCK1" + ], + "CLAIR": [ + "CLAIR1" + ], + "COOLTRAINERF": [ + "GWEN", + "LOIS", + "FRAN", + "LOLA", + "KATE", + "IRENE", + "KELLY", + "JOYCE", + "BETH1", + "REENA1", + "MEGAN", + "BETH2", + "CAROL", + "QUINN", + "EMMA", + "CYBIL", + "JENN", + "BETH3", + "REENA2", + "REENA3" + ], + "COOLTRAINERM": [ + "NICK", + "AARON", + "PAUL", + "CODY", + "MIKE", + "GAVEN1", + "GAVEN2", + "RYAN", + "JAKE", + "GAVEN3", + "BLAKE", + "BRIAN", + "ERICK", + "ANDY", + "TYLER", + "SEAN", + "KEVIN", + "STEVE", + "ALLEN" + ], + "ERIKA": [ + "ERIKA1" + ], + "EXECUTIVEF": [ + "EXECUTIVEF_1", + "EXECUTIVEF_2" + ], + "EXECUTIVEM": [ + "EXECUTIVEM_1", + "EXECUTIVEM_2", + "EXECUTIVEM_3", + "EXECUTIVEM_4" + ], + "FALKNER": [ + "FALKNER1" + ], + "FIREBREATHER": [ + "OTIS", + "DICK", + "NED", + "BURT", + "BILL", + "WALT", + "RAY", + "LYLE" + ], + "FISHER": [ + "JUSTIN", + "RALPH1", + "ARNOLD", + "KYLE", + "HENRY", + "MARVIN", + "CHRIS1", + "ANDRE", + "RAYMOND", + "WILTON1", + "EDGAR", + "JONAH", + "MARTIN", + "STEPHEN", + "BARNEY", + "RALPH2", + "RALPH3", + "CHRIS2", + "CHRIS3", + "WILTON2", + "SCOTT", + "WILTON3" + ], + "GENTLEMAN": [ + "PRESTON", + "EDWARD", + "GREGORY", + "VIRGIL", + "ALFRED" + ], + "GRUNTF": [ + "GRUNTF_1", + "GRUNTF_2", + "GRUNTF_3", + "GRUNTF_4", + "GRUNTF_5" + ], + "GRUNTM": [ + "GRUNTM_1", + "GRUNTM_2", + "GRUNTM_3", + "GRUNTM_4", + "GRUNTM_5", + "GRUNTM_6", + "GRUNTM_7", + "GRUNTM_8", + "GRUNTM_9", + "GRUNTM_10", + "GRUNTM_11", + "GRUNTM_12", + "GRUNTM_13", + "GRUNTM_14", + "GRUNTM_15", + "GRUNTM_16", + "GRUNTM_17", + "GRUNTM_18", + "GRUNTM_19", + "GRUNTM_20", + "GRUNTM_21", + "GRUNTM_22", + "GRUNTM_23", + "GRUNTM_24", + "GRUNTM_25", + "GRUNTM_26", + "GRUNTM_27", + "GRUNTM_28", + "GRUNTM_29", + "GRUNTM_30", + "GRUNTM_31" + ], + "GUITARIST": [ + "CLYDE", + "VINCENT" + ], + "HIKER": [ + "ANTHONY1", + "RUSSELL", + "PHILLIP", + "LEONARD", + "ANTHONY2", + "BENJAMIN", + "ERIK", + "MICHAEL", + "PARRY1", + "TIMOTHY", + "BAILEY", + "ANTHONY3", + "TIM", + "NOLAND", + "SIDNEY", + "KENNY", + "JIM", + "DANIEL", + "PARRY2", + "PARRY3" + ], + "JANINE": [ + "JANINE1" + ], + "JASMINE": [ + "JASMINE1" + ], + "JUGGLER": [ + "IRWIN1", + "FRITZ", + "HORTON", + "IRWIN2", + "IRWIN3" + ], + "KAREN": [ + "KAREN1" + ], + "KIMONO_GIRL": [ + "KYOKO", + "NAOKO", + "SAYO", + "ZUKI", + "KUNI", + "MIKI" + ], + "KOGA": [ + "KOGA1" + ], + "LASS": [ + "CARRIE", + "BRIDGET", + "ALICE", + "KRISE", + "CONNIE1", + "LINDA", + "LAURA", + "SHANNON", + "MICHELLE", + "DANA1", + "ELLEN", + "CONNIE2", + "CONNIE3", + "DANA2", + "DANA3" + ], + "LT_SURGE": [ + "LT_SURGE1" + ], + "MEDIUM": [ + "MARTHA", + "GRACE", + "BETHANY", + "MARGRET", + "ETHEL", + "REBECCA", + "DORIS" + ], + "MISTY": [ + "MISTY1" + ], + "MORTY": [ + "MORTY1" + ], + "OFFICER": [ + "KEITH", + "DIRK" + ], + "PICNICKER": [ + "LIZ1", + "GINA1", + "BROOKE", + "KIM", + "CINDY", + "HOPE", + "SHARON", + "DEBRA", + "GINA2", + "ERIN1", + "LIZ2", + "LIZ3", + "HEIDI", + "EDNA", + "GINA3", + "TIFFANY1", + "TIFFANY2", + "ERIN2", + "TANYA", + "TIFFANY3", + "ERIN3" + ], + "POKEFANF": [ + "BEVERLY1", + "RUTH", + "BEVERLY2", + "BEVERLY3", + "GEORGIA" + ], + "POKEFANM": [ + "WILLIAM", + "DEREK1", + "ROBERT", + "JOSHUA", + "CARTER", + "TREVOR", + "BRANDON", + "JEREMY", + "COLIN", + "DEREK2", + "DEREK3", + "ALEX" + ], + "POKEMANIAC": [ + "LARRY", + "ANDREW", + "CALVIN", + "SHANE", + "BEN", + "BRENT1", + "RON", + "ETHAN", + "BRENT2", + "BRENT3", + "ISSAC", + "DONALD", + "ZACH" + ], + "POKEMON_PROF": [], + "PRYCE": [ + "PRYCE1" + ], + "PSYCHIC_T": [ + "NATHAN", + "FRANKLIN", + "HERMAN", + "FIDEL", + "GREG", + "NORMAN", + "MARK", + "PHIL", + "RICHARD", + "GILBERT", + "JARED", + "RODNEY" + ], + "RED": [ + "RED1" + ], + "RIVAL1": [ + "RIVAL1_1_CHIKORITA", + "RIVAL1_1_CYNDAQUIL", + "RIVAL1_1_TOTODILE", + "RIVAL1_2_CHIKORITA", + "RIVAL1_2_CYNDAQUIL", + "RIVAL1_2_TOTODILE", + "RIVAL1_3_CHIKORITA", + "RIVAL1_3_CYNDAQUIL", + "RIVAL1_3_TOTODILE", + "RIVAL1_4_CHIKORITA", + "RIVAL1_4_CYNDAQUIL", + "RIVAL1_4_TOTODILE", + "RIVAL1_5_CHIKORITA", + "RIVAL1_5_CYNDAQUIL", + "RIVAL1_5_TOTODILE" + ], + "RIVAL2": [ + "RIVAL2_1_CHIKORITA", + "RIVAL2_1_CYNDAQUIL", + "RIVAL2_1_TOTODILE", + "RIVAL2_2_CHIKORITA", + "RIVAL2_2_CYNDAQUIL", + "RIVAL2_2_TOTODILE" + ], + "SABRINA": [ + "SABRINA1" + ], + "SAGE": [ + "CHOW", + "NICO", + "JIN", + "TROY", + "JEFFREY", + "PING", + "EDMOND", + "NEAL", + "LI" + ], + "SAILOR": [ + "EUGENE", + "HUEY1", + "TERRELL", + "KENT", + "ERNEST", + "JEFF", + "GARRETT", + "KENNETH", + "STANLY", + "HARRY", + "HUEY2", + "HUEY3" + ], + "SCHOOLBOY": [ + "JACK1", + "KIPP", + "ALAN1", + "JOHNNY", + "DANNY", + "TOMMY", + "DUDLEY", + "JOE", + "BILLY", + "CHAD1", + "NATE", + "RICKY", + "JACK2", + "JACK3", + "ALAN2", + "ALAN3", + "CHAD2", + "CHAD3" + ], + "SCIENTIST": [ + "ROSS", + "MITCH", + "JED", + "MARC", + "RICH" + ], + "SKIER": [ + "ROXANNE", + "CLARISSA" + ], + "SUPER_NERD": [ + "ERIC_UNUSED", + "ERIC", + "GREGG", + "JAY", + "DAVE", + "SAM", + "TOM", + "PAT", + "SHAWN", + "TERU", + "RUSS", + "NORTON" + ], + "SWIMMERF": [ + "ELAINE", + "PAULA", + "KAYLEE", + "SUSIE", + "DENISE", + "KARA", + "WENDY", + "LISA", + "JILL", + "MARY", + "KATIE", + "DAWN", + "TARA", + "NICOLE", + "LORI", + "JODY", + "NIKKI", + "DIANA", + "BRIANA" + ], + "SWIMMERM": [ + "HAROLD", + "SIMON", + "RANDALL", + "CHARLIE", + "GEORGE", + "BERKE", + "KIRK", + "MATHEW", + "HAL", + "PATON", + "DARYL", + "WALTER", + "TONY", + "JEROME", + "TUCKER", + "RICK", + "CAMERON", + "SETH", + "JAMES", + "LEWIS", + "PARKER" + ], + "TEACHER": [ + "COLETTE", + "HILLARY", + "SHIRLEY" + ], + "TRAINER_NONE": [], + "TWINS": [ + "AMYANDMAY1", + "ANNANDANNE1", + "ANNANDANNE2", + "AMYANDMAY2", + "JOANDZOE1", + "JOANDZOE2", + "MEGANDPEG1", + "MEGANDPEG2" + ], + "WHITNEY": [ + "WHITNEY1" + ], + "WILL": [ + "WILL1" + ], + "YOUNGSTER": [ + "JOEY1", + "MIKEY", + "ALBERT", + "GORDON", + "SAMUEL", + "IAN", + "JOEY2", + "JOEY3", + "WARREN", + "JIMMY", + "OWEN", + "JASON" + ] + }, + "trainerClassOrder": [ + "TRAINER_NONE", + "FALKNER", + "WHITNEY", + "BUGSY", + "MORTY", + "PRYCE", + "JASMINE", + "CHUCK", + "CLAIR", + "RIVAL1", + "POKEMON_PROF", + "WILL", + "CAL", + "BRUNO", + "KAREN", + "KOGA", + "CHAMPION", + "BROCK", + "MISTY", + "LT_SURGE", + "SCIENTIST", + "ERIKA", + "YOUNGSTER", + "SCHOOLBOY", + "BIRD_KEEPER", + "LASS", + "JANINE", + "COOLTRAINERM", + "COOLTRAINERF", + "BEAUTY", + "POKEMANIAC", + "GRUNTM", + "GENTLEMAN", + "SKIER", + "TEACHER", + "SABRINA", + "BUG_CATCHER", + "FISHER", + "SWIMMERM", + "SWIMMERF", + "SAILOR", + "SUPER_NERD", + "RIVAL2", + "GUITARIST", + "HIKER", + "BIKER", + "BLAINE", + "BURGLAR", + "FIREBREATHER", + "JUGGLER", + "BLACKBELT_T", + "EXECUTIVEM", + "PSYCHIC_T", + "PICNICKER", + "CAMPER", + "EXECUTIVEF", + "SAGE", + "MEDIUM", + "BOARDER", + "POKEFANM", + "KIMONO_GIRL", + "TWINS", + "POKEFANF", + "RED", + "BLUE", + "OFFICER", + "GRUNTF" + ], + "trainerTypeOrder": [ + "TRAINERTYPE_NORMAL", + "TRAINERTYPE_MOVES", + "TRAINERTYPE_ITEM", + "TRAINERTYPE_ITEM_MOVES" + ], + "treeMonSetOrder": [ + "TREEMON_SET_NONE", + "TREEMON_SET_FOREST", + "TREEMON_SET_CANYON", + "TREEMON_SET_ROCK", + "TREEMON_SET_UNUSED", + "TREEMON_SET_CITY" + ], + "types": { + "BIRD": 6, + "BUG": 7, + "CURSE_TYPE": 19, + "DARK": 27, + "DRAGON": 26, + "ELECTRIC": 23, + "FIGHTING": 1, + "FIRE": 20, + "FLYING": 2, + "GHOST": 8, + "GRASS": 22, + "GROUND": 4, + "ICE": 25, + "NORMAL": 0, + "POISON": 3, + "PSYCHIC_TYPE": 24, + "ROCK": 5, + "STEEL": 9, + "WATER": 21 + } + }, + "fontCharmap": [ + { + "code": 96, + "seq": "" + }, + { + "code": 97, + "seq": "" + }, + { + "code": 98, + "seq": "" + }, + { + "code": 99, + "seq": "" + }, + { + "code": 100, + "seq": "" + }, + { + "code": 101, + "seq": "" + }, + { + "code": 102, + "seq": "" + }, + { + "code": 103, + "seq": "" + }, + { + "code": 104, + "seq": "" + }, + { + "code": 107, + "seq": "" + }, + { + "code": 108, + "seq": "" + }, + { + "code": 106, + "seq": "" + }, + { + "code": 105, + "seq": "" + }, + { + "code": 109, + "seq": "" + }, + { + "code": 242, + "seq": "" + }, + { + "code": 112, + "seq": "" + }, + { + "code": 115, + "seq": "" + }, + { + "code": 113, + "seq": "" + }, + { + "code": 110, + "seq": "" + }, + { + "code": 226, + "seq": "" + }, + { + "code": 225, + "seq": "" + }, + { + "code": 112, + "seq": "" + }, + { + "code": 208, + "seq": "'d" + }, + { + "code": 209, + "seq": "'l" + }, + { + "code": 210, + "seq": "'m" + }, + { + "code": 211, + "seq": "'r" + }, + { + "code": 212, + "seq": "'s" + }, + { + "code": 213, + "seq": "'t" + }, + { + "code": 214, + "seq": "'v" + }, + { + "code": 127, + "seq": " " + }, + { + "code": 231, + "seq": "!" + }, + { + "code": 115, + "seq": "\"" + }, + { + "code": 233, + "seq": "&" + }, + { + "code": 224, + "seq": "'" + }, + { + "code": 154, + "seq": "(" + }, + { + "code": 155, + "seq": ")" + }, + { + "code": 244, + "seq": "," + }, + { + "code": 227, + "seq": "-" + }, + { + "code": 232, + "seq": "." + }, + { + "code": 243, + "seq": "/" + }, + { + "code": 246, + "seq": "0" + }, + { + "code": 247, + "seq": "1" + }, + { + "code": 248, + "seq": "2" + }, + { + "code": 249, + "seq": "3" + }, + { + "code": 250, + "seq": "4" + }, + { + "code": 251, + "seq": "5" + }, + { + "code": 252, + "seq": "6" + }, + { + "code": 253, + "seq": "7" + }, + { + "code": 254, + "seq": "8" + }, + { + "code": 255, + "seq": "9" + }, + { + "code": 156, + "seq": ":" + }, + { + "code": 157, + "seq": ";" + }, + { + "code": 230, + "seq": "?" + }, + { + "code": 128, + "seq": "A" + }, + { + "code": 129, + "seq": "B" + }, + { + "code": 130, + "seq": "C" + }, + { + "code": 131, + "seq": "D" + }, + { + "code": 132, + "seq": "E" + }, + { + "code": 133, + "seq": "F" + }, + { + "code": 134, + "seq": "G" + }, + { + "code": 135, + "seq": "H" + }, + { + "code": 136, + "seq": "I" + }, + { + "code": 137, + "seq": "J" + }, + { + "code": 138, + "seq": "K" + }, + { + "code": 139, + "seq": "L" + }, + { + "code": 140, + "seq": "M" + }, + { + "code": 141, + "seq": "N" + }, + { + "code": 142, + "seq": "O" + }, + { + "code": 143, + "seq": "P" + }, + { + "code": 144, + "seq": "Q" + }, + { + "code": 145, + "seq": "R" + }, + { + "code": 146, + "seq": "S" + }, + { + "code": 147, + "seq": "T" + }, + { + "code": 148, + "seq": "U" + }, + { + "code": 149, + "seq": "V" + }, + { + "code": 150, + "seq": "W" + }, + { + "code": 151, + "seq": "X" + }, + { + "code": 152, + "seq": "Y" + }, + { + "code": 153, + "seq": "Z" + }, + { + "code": 158, + "seq": "[" + }, + { + "code": 159, + "seq": "]" + }, + { + "code": 160, + "seq": "a" + }, + { + "code": 161, + "seq": "b" + }, + { + "code": 162, + "seq": "c" + }, + { + "code": 163, + "seq": "d" + }, + { + "code": 164, + "seq": "e" + }, + { + "code": 165, + "seq": "f" + }, + { + "code": 166, + "seq": "g" + }, + { + "code": 167, + "seq": "h" + }, + { + "code": 168, + "seq": "i" + }, + { + "code": 169, + "seq": "j" + }, + { + "code": 170, + "seq": "k" + }, + { + "code": 171, + "seq": "l" + }, + { + "code": 172, + "seq": "m" + }, + { + "code": 173, + "seq": "n" + }, + { + "code": 174, + "seq": "o" + }, + { + "code": 175, + "seq": "p" + }, + { + "code": 176, + "seq": "q" + }, + { + "code": 177, + "seq": "r" + }, + { + "code": 178, + "seq": "s" + }, + { + "code": 179, + "seq": "t" + }, + { + "code": 180, + "seq": "u" + }, + { + "code": 181, + "seq": "v" + }, + { + "code": 182, + "seq": "w" + }, + { + "code": 183, + "seq": "x" + }, + { + "code": 184, + "seq": "y" + }, + { + "code": 185, + "seq": "z" + }, + { + "code": 240, + "seq": "¥" + }, + { + "code": 116, + "seq": "·" + }, + { + "code": 192, + "seq": "Ä" + }, + { + "code": 193, + "seq": "Ö" + }, + { + "code": 241, + "seq": "×" + }, + { + "code": 194, + "seq": "Ü" + }, + { + "code": 195, + "seq": "ä" + }, + { + "code": 234, + "seq": "é" + }, + { + "code": 196, + "seq": "ö" + }, + { + "code": 197, + "seq": "ü" + }, + { + "code": 114, + "seq": "“" + }, + { + "code": 115, + "seq": "”" + }, + { + "code": 117, + "seq": "…" + }, + { + "code": 110, + "seq": "′" + }, + { + "code": 111, + "seq": "″" + }, + { + "code": 116, + "seq": "№" + }, + { + "code": 223, + "seq": "←" + }, + { + "code": 235, + "seq": "→" + }, + { + "code": 117, + "seq": "⋯" + }, + { + "code": 122, + "seq": "─" + }, + { + "code": 124, + "seq": "│" + }, + { + "code": 121, + "seq": "┌" + }, + { + "code": 123, + "seq": "┐" + }, + { + "code": 125, + "seq": "└" + }, + { + "code": 126, + "seq": "┘" + }, + { + "code": 96, + "seq": "■" + }, + { + "code": 97, + "seq": "▲" + }, + { + "code": 237, + "seq": "▶" + }, + { + "code": 236, + "seq": "▷" + }, + { + "code": 238, + "seq": "▼" + }, + { + "code": 113, + "seq": "◀" + }, + { + "code": 98, + "seq": "☎" + }, + { + "code": 245, + "seq": "♀" + }, + { + "code": 239, + "seq": "♂" + }, + { + "code": 127, + "seq": " " + }, + { + "code": 232, + "seq": "。" + }, + { + "code": 112, + "seq": "「" + }, + { + "code": 113, + "seq": "」" + }, + { + "code": 114, + "seq": "『" + }, + { + "code": 115, + "seq": "』" + }, + { + "code": 118, + "seq": "ぁ" + }, + { + "code": 177, + "seq": "あ" + }, + { + "code": 110, + "seq": "ぃ" + }, + { + "code": 178, + "seq": "い" + }, + { + "code": 111, + "seq": "ぅ" + }, + { + "code": 179, + "seq": "う" + }, + { + "code": 119, + "seq": "ぇ" + }, + { + "code": 180, + "seq": "え" + }, + { + "code": 120, + "seq": "ぉ" + }, + { + "code": 181, + "seq": "お" + }, + { + "code": 182, + "seq": "か" + }, + { + "code": 183, + "seq": "き" + }, + { + "code": 184, + "seq": "く" + }, + { + "code": 185, + "seq": "け" + }, + { + "code": 186, + "seq": "こ" + }, + { + "code": 187, + "seq": "さ" + }, + { + "code": 188, + "seq": "し" + }, + { + "code": 189, + "seq": "す" + }, + { + "code": 190, + "seq": "せ" + }, + { + "code": 191, + "seq": "そ" + }, + { + "code": 192, + "seq": "た" + }, + { + "code": 193, + "seq": "ち" + }, + { + "code": 223, + "seq": "っ" + }, + { + "code": 194, + "seq": "つ" + }, + { + "code": 195, + "seq": "て" + }, + { + "code": 196, + "seq": "と" + }, + { + "code": 197, + "seq": "な" + }, + { + "code": 198, + "seq": "に" + }, + { + "code": 199, + "seq": "ぬ" + }, + { + "code": 200, + "seq": "ね" + }, + { + "code": 201, + "seq": "の" + }, + { + "code": 202, + "seq": "は" + }, + { + "code": 203, + "seq": "ひ" + }, + { + "code": 204, + "seq": "ふ" + }, + { + "code": 205, + "seq": "へ" + }, + { + "code": 206, + "seq": "ほ" + }, + { + "code": 207, + "seq": "ま" + }, + { + "code": 208, + "seq": "み" + }, + { + "code": 209, + "seq": "む" + }, + { + "code": 210, + "seq": "め" + }, + { + "code": 211, + "seq": "も" + }, + { + "code": 224, + "seq": "ゃ" + }, + { + "code": 212, + "seq": "や" + }, + { + "code": 225, + "seq": "ゅ" + }, + { + "code": 213, + "seq": "ゆ" + }, + { + "code": 226, + "seq": "ょ" + }, + { + "code": 214, + "seq": "よ" + }, + { + "code": 215, + "seq": "ら" + }, + { + "code": 216, + "seq": "り" + }, + { + "code": 217, + "seq": "る" + }, + { + "code": 218, + "seq": "れ" + }, + { + "code": 219, + "seq": "ろ" + }, + { + "code": 220, + "seq": "わ" + }, + { + "code": 221, + "seq": "を" + }, + { + "code": 222, + "seq": "ん" + }, + { + "code": 233, + "seq": "ァ" + }, + { + "code": 128, + "seq": "ア" + }, + { + "code": 176, + "seq": "ィ" + }, + { + "code": 129, + "seq": "イ" + }, + { + "code": 234, + "seq": "ゥ" + }, + { + "code": 130, + "seq": "ウ" + }, + { + "code": 235, + "seq": "ェ" + }, + { + "code": 131, + "seq": "エ" + }, + { + "code": 244, + "seq": "ォ" + }, + { + "code": 132, + "seq": "オ" + }, + { + "code": 133, + "seq": "カ" + }, + { + "code": 134, + "seq": "キ" + }, + { + "code": 135, + "seq": "ク" + }, + { + "code": 136, + "seq": "ケ" + }, + { + "code": 137, + "seq": "コ" + }, + { + "code": 138, + "seq": "サ" + }, + { + "code": 139, + "seq": "シ" + }, + { + "code": 140, + "seq": "ス" + }, + { + "code": 141, + "seq": "セ" + }, + { + "code": 142, + "seq": "ソ" + }, + { + "code": 143, + "seq": "タ" + }, + { + "code": 144, + "seq": "チ" + }, + { + "code": 172, + "seq": "ッ" + }, + { + "code": 145, + "seq": "ツ" + }, + { + "code": 146, + "seq": "テ" + }, + { + "code": 147, + "seq": "ト" + }, + { + "code": 148, + "seq": "ナ" + }, + { + "code": 149, + "seq": "ニ" + }, + { + "code": 150, + "seq": "ヌ" + }, + { + "code": 151, + "seq": "ネ" + }, + { + "code": 152, + "seq": "ノ" + }, + { + "code": 153, + "seq": "ハ" + }, + { + "code": 154, + "seq": "ヒ" + }, + { + "code": 155, + "seq": "フ" + }, + { + "code": 156, + "seq": "ホ" + }, + { + "code": 157, + "seq": "マ" + }, + { + "code": 158, + "seq": "ミ" + }, + { + "code": 159, + "seq": "ム" + }, + { + "code": 160, + "seq": "メ" + }, + { + "code": 161, + "seq": "モ" + }, + { + "code": 173, + "seq": "ャ" + }, + { + "code": 162, + "seq": "ヤ" + }, + { + "code": 174, + "seq": "ュ" + }, + { + "code": 163, + "seq": "ユ" + }, + { + "code": 175, + "seq": "ョ" + }, + { + "code": 164, + "seq": "ヨ" + }, + { + "code": 165, + "seq": "ラ" + }, + { + "code": 166, + "seq": "ル" + }, + { + "code": 167, + "seq": "レ" + }, + { + "code": 168, + "seq": "ロ" + }, + { + "code": 169, + "seq": "ワ" + }, + { + "code": 170, + "seq": "ヲ" + }, + { + "code": 171, + "seq": "ン" + }, + { + "code": 116, + "seq": "・" + }, + { + "code": 227, + "seq": "ー" + }, + { + "code": 240, + "seq": "円" + }, + { + "code": 231, + "seq": "!" + }, + { + "code": 242, + "seq": "." + }, + { + "code": 243, + "seq": "/" + }, + { + "code": 246, + "seq": "0" + }, + { + "code": 247, + "seq": "1" + }, + { + "code": 248, + "seq": "2" + }, + { + "code": 249, + "seq": "3" + }, + { + "code": 250, + "seq": "4" + }, + { + "code": 251, + "seq": "5" + }, + { + "code": 252, + "seq": "6" + }, + { + "code": 253, + "seq": "7" + }, + { + "code": 254, + "seq": "8" + }, + { + "code": 255, + "seq": "9" + }, + { + "code": 230, + "seq": "?" + }, + { + "code": 229, + "seq": "゙" + }, + { + "code": 228, + "seq": "゚" + } + ], + "format": 3, + "generation": 2, + "maps": { + "AZALEA_GYM": { + "group": 8, + "height": 8, + "map": 5, + "name": "AZALEA_GYM", + "width": 5 + }, + "AZALEA_MART": { + "group": 8, + "height": 4, + "map": 3, + "name": "AZALEA_MART", + "width": 6 + }, + "AZALEA_POKECENTER_1F": { + "group": 8, + "height": 4, + "map": 1, + "name": "AZALEA_POKECENTER_1F", + "width": 5 + }, + "AZALEA_TOWN": { + "group": 8, + "height": 9, + "map": 7, + "name": "AZALEA_TOWN", + "width": 20 + }, + "BILLS_FAMILYS_HOUSE": { + "group": 11, + "height": 4, + "map": 6, + "name": "BILLS_FAMILYS_HOUSE", + "width": 4 + }, + "BILLS_HOUSE": { + "group": 7, + "height": 4, + "map": 11, + "name": "BILLS_HOUSE", + "width": 4 + }, + "BILLS_OLDER_SISTERS_HOUSE": { + "group": 17, + "height": 4, + "map": 9, + "name": "BILLS_OLDER_SISTERS_HOUSE", + "width": 4 + }, + "BLACKTHORN_CITY": { + "group": 5, + "height": 18, + "map": 10, + "name": "BLACKTHORN_CITY", + "width": 20 + }, + "BLACKTHORN_DRAGON_SPEECH_HOUSE": { + "group": 5, + "height": 4, + "map": 3, + "name": "BLACKTHORN_DRAGON_SPEECH_HOUSE", + "width": 4 + }, + "BLACKTHORN_EMYS_HOUSE": { + "group": 5, + "height": 4, + "map": 4, + "name": "BLACKTHORN_EMYS_HOUSE", + "width": 4 + }, + "BLACKTHORN_GYM_1F": { + "group": 5, + "height": 9, + "map": 1, + "name": "BLACKTHORN_GYM_1F", + "width": 5 + }, + "BLACKTHORN_GYM_2F": { + "group": 5, + "height": 9, + "map": 2, + "name": "BLACKTHORN_GYM_2F", + "width": 5 + }, + "BLACKTHORN_MART": { + "group": 5, + "height": 4, + "map": 5, + "name": "BLACKTHORN_MART", + "width": 6 + }, + "BLACKTHORN_POKECENTER_1F": { + "group": 5, + "height": 4, + "map": 6, + "name": "BLACKTHORN_POKECENTER_1F", + "width": 5 + }, + "BLUES_HOUSE": { + "group": 13, + "height": 4, + "map": 5, + "name": "BLUES_HOUSE", + "width": 4 + }, + "BRUNOS_ROOM": { + "group": 16, + "height": 9, + "map": 5, + "name": "BRUNOS_ROOM", + "width": 5 + }, + "BURNED_TOWER_1F": { + "group": 3, + "height": 9, + "map": 13, + "name": "BURNED_TOWER_1F", + "width": 10 + }, + "BURNED_TOWER_B1F": { + "group": 3, + "height": 9, + "map": 14, + "name": "BURNED_TOWER_B1F", + "width": 10 + }, + "CELADON_CAFE": { + "group": 21, + "height": 4, + "map": 22, + "name": "CELADON_CAFE", + "width": 6 + }, + "CELADON_CITY": { + "group": 21, + "height": 18, + "map": 4, + "name": "CELADON_CITY", + "width": 20 + }, + "CELADON_DEPT_STORE_1F": { + "group": 21, + "height": 4, + "map": 5, + "name": "CELADON_DEPT_STORE_1F", + "width": 8 + }, + "CELADON_DEPT_STORE_2F": { + "group": 21, + "height": 4, + "map": 6, + "name": "CELADON_DEPT_STORE_2F", + "width": 8 + }, + "CELADON_DEPT_STORE_3F": { + "group": 21, + "height": 4, + "map": 7, + "name": "CELADON_DEPT_STORE_3F", + "width": 8 + }, + "CELADON_DEPT_STORE_4F": { + "group": 21, + "height": 4, + "map": 8, + "name": "CELADON_DEPT_STORE_4F", + "width": 8 + }, + "CELADON_DEPT_STORE_5F": { + "group": 21, + "height": 4, + "map": 9, + "name": "CELADON_DEPT_STORE_5F", + "width": 8 + }, + "CELADON_DEPT_STORE_6F": { + "group": 21, + "height": 4, + "map": 10, + "name": "CELADON_DEPT_STORE_6F", + "width": 8 + }, + "CELADON_DEPT_STORE_ELEVATOR": { + "group": 21, + "height": 2, + "map": 11, + "name": "CELADON_DEPT_STORE_ELEVATOR", + "width": 2 + }, + "CELADON_GAME_CORNER": { + "group": 21, + "height": 7, + "map": 19, + "name": "CELADON_GAME_CORNER", + "width": 10 + }, + "CELADON_GAME_CORNER_PRIZE_ROOM": { + "group": 21, + "height": 3, + "map": 20, + "name": "CELADON_GAME_CORNER_PRIZE_ROOM", + "width": 3 + }, + "CELADON_GYM": { + "group": 21, + "height": 9, + "map": 21, + "name": "CELADON_GYM", + "width": 5 + }, + "CELADON_MANSION_1F": { + "group": 21, + "height": 5, + "map": 12, + "name": "CELADON_MANSION_1F", + "width": 4 + }, + "CELADON_MANSION_2F": { + "group": 21, + "height": 5, + "map": 13, + "name": "CELADON_MANSION_2F", + "width": 4 + }, + "CELADON_MANSION_3F": { + "group": 21, + "height": 5, + "map": 14, + "name": "CELADON_MANSION_3F", + "width": 4 + }, + "CELADON_MANSION_ROOF": { + "group": 21, + "height": 5, + "map": 15, + "name": "CELADON_MANSION_ROOF", + "width": 4 + }, + "CELADON_MANSION_ROOF_HOUSE": { + "group": 21, + "height": 4, + "map": 16, + "name": "CELADON_MANSION_ROOF_HOUSE", + "width": 4 + }, + "CELADON_POKECENTER_1F": { + "group": 21, + "height": 4, + "map": 17, + "name": "CELADON_POKECENTER_1F", + "width": 5 + }, + "CELADON_POKECENTER_2F_BETA": { + "group": 21, + "height": 4, + "map": 18, + "name": "CELADON_POKECENTER_2F_BETA", + "width": 8 + }, + "CERULEAN_CITY": { + "group": 7, + "height": 18, + "map": 17, + "name": "CERULEAN_CITY", + "width": 20 + }, + "CERULEAN_GYM": { + "group": 7, + "height": 8, + "map": 6, + "name": "CERULEAN_GYM", + "width": 5 + }, + "CERULEAN_GYM_BADGE_SPEECH_HOUSE": { + "group": 7, + "height": 4, + "map": 1, + "name": "CERULEAN_GYM_BADGE_SPEECH_HOUSE", + "width": 4 + }, + "CERULEAN_MART": { + "group": 7, + "height": 4, + "map": 7, + "name": "CERULEAN_MART", + "width": 6 + }, + "CERULEAN_POKECENTER_1F": { + "group": 7, + "height": 4, + "map": 4, + "name": "CERULEAN_POKECENTER_1F", + "width": 5 + }, + "CERULEAN_POKECENTER_2F_BETA": { + "group": 7, + "height": 4, + "map": 5, + "name": "CERULEAN_POKECENTER_2F_BETA", + "width": 8 + }, + "CERULEAN_POLICE_STATION": { + "group": 7, + "height": 4, + "map": 2, + "name": "CERULEAN_POLICE_STATION", + "width": 4 + }, + "CERULEAN_TRADE_SPEECH_HOUSE": { + "group": 7, + "height": 4, + "map": 3, + "name": "CERULEAN_TRADE_SPEECH_HOUSE", + "width": 4 + }, + "CHARCOAL_KILN": { + "group": 8, + "height": 4, + "map": 2, + "name": "CHARCOAL_KILN", + "width": 4 + }, + "CHERRYGROVE_CITY": { + "group": 26, + "height": 9, + "map": 3, + "name": "CHERRYGROVE_CITY", + "width": 20 + }, + "CHERRYGROVE_EVOLUTION_SPEECH_HOUSE": { + "group": 26, + "height": 4, + "map": 8, + "name": "CHERRYGROVE_EVOLUTION_SPEECH_HOUSE", + "width": 4 + }, + "CHERRYGROVE_GYM_SPEECH_HOUSE": { + "group": 26, + "height": 4, + "map": 6, + "name": "CHERRYGROVE_GYM_SPEECH_HOUSE", + "width": 4 + }, + "CHERRYGROVE_MART": { + "group": 26, + "height": 4, + "map": 4, + "name": "CHERRYGROVE_MART", + "width": 6 + }, + "CHERRYGROVE_POKECENTER_1F": { + "group": 26, + "height": 4, + "map": 5, + "name": "CHERRYGROVE_POKECENTER_1F", + "width": 5 + }, + "CIANWOOD_CITY": { + "group": 22, + "height": 27, + "map": 3, + "name": "CIANWOOD_CITY", + "width": 15 + }, + "CIANWOOD_GYM": { + "group": 22, + "height": 9, + "map": 5, + "name": "CIANWOOD_GYM", + "width": 5 + }, + "CIANWOOD_LUGIA_SPEECH_HOUSE": { + "group": 22, + "height": 4, + "map": 9, + "name": "CIANWOOD_LUGIA_SPEECH_HOUSE", + "width": 4 + }, + "CIANWOOD_PHARMACY": { + "group": 22, + "height": 4, + "map": 7, + "name": "CIANWOOD_PHARMACY", + "width": 4 + }, + "CIANWOOD_PHOTO_STUDIO": { + "group": 22, + "height": 4, + "map": 8, + "name": "CIANWOOD_PHOTO_STUDIO", + "width": 4 + }, + "CIANWOOD_POKECENTER_1F": { + "group": 22, + "height": 4, + "map": 6, + "name": "CIANWOOD_POKECENTER_1F", + "width": 5 + }, + "CINNABAR_ISLAND": { + "group": 6, + "height": 9, + "map": 8, + "name": "CINNABAR_ISLAND", + "width": 10 + }, + "CINNABAR_POKECENTER_1F": { + "group": 6, + "height": 4, + "map": 1, + "name": "CINNABAR_POKECENTER_1F", + "width": 5 + }, + "CINNABAR_POKECENTER_2F_BETA": { + "group": 6, + "height": 4, + "map": 2, + "name": "CINNABAR_POKECENTER_2F_BETA", + "width": 8 + }, + "COLOSSEUM": { + "group": 20, + "height": 4, + "map": 3, + "name": "COLOSSEUM", + "width": 5 + }, + "COPYCATS_HOUSE_1F": { + "group": 25, + "height": 4, + "map": 11, + "name": "COPYCATS_HOUSE_1F", + "width": 4 + }, + "COPYCATS_HOUSE_2F": { + "group": 25, + "height": 3, + "map": 12, + "name": "COPYCATS_HOUSE_2F", + "width": 5 + }, + "DANCE_THEATER": { + "group": 4, + "height": 7, + "map": 5, + "name": "DANCE_THEATER", + "width": 6 + }, + "DARK_CAVE_BLACKTHORN_ENTRANCE": { + "group": 3, + "height": 18, + "map": 71, + "name": "DARK_CAVE_BLACKTHORN_ENTRANCE", + "width": 15 + }, + "DARK_CAVE_VIOLET_ENTRANCE": { + "group": 3, + "height": 18, + "map": 70, + "name": "DARK_CAVE_VIOLET_ENTRANCE", + "width": 20 + }, + "DAY_CARE": { + "group": 11, + "height": 4, + "map": 22, + "name": "DAY_CARE", + "width": 5 + }, + "DAY_OF_WEEK_SIBLINGS_HOUSE": { + "group": 24, + "height": 4, + "map": 11, + "name": "DAY_OF_WEEK_SIBLINGS_HOUSE", + "width": 4 + }, + "DIGLETTS_CAVE": { + "group": 3, + "height": 18, + "map": 75, + "name": "DIGLETTS_CAVE", + "width": 10 + }, + "DRAGONS_DEN_1F": { + "group": 3, + "height": 9, + "map": 72, + "name": "DRAGONS_DEN_1F", + "width": 5 + }, + "DRAGONS_DEN_B1F": { + "group": 3, + "height": 18, + "map": 73, + "name": "DRAGONS_DEN_B1F", + "width": 20 + }, + "EARLS_POKEMON_ACADEMY": { + "group": 10, + "height": 8, + "map": 8, + "name": "EARLS_POKEMON_ACADEMY", + "width": 4 + }, + "ECRUTEAK_CITY": { + "group": 4, + "height": 18, + "map": 9, + "name": "ECRUTEAK_CITY", + "width": 20 + }, + "ECRUTEAK_GYM": { + "group": 4, + "height": 9, + "map": 7, + "name": "ECRUTEAK_GYM", + "width": 5 + }, + "ECRUTEAK_ITEMFINDER_HOUSE": { + "group": 4, + "height": 4, + "map": 8, + "name": "ECRUTEAK_ITEMFINDER_HOUSE", + "width": 4 + }, + "ECRUTEAK_LUGIA_SPEECH_HOUSE": { + "group": 4, + "height": 4, + "map": 4, + "name": "ECRUTEAK_LUGIA_SPEECH_HOUSE", + "width": 4 + }, + "ECRUTEAK_MART": { + "group": 4, + "height": 4, + "map": 6, + "name": "ECRUTEAK_MART", + "width": 6 + }, + "ECRUTEAK_POKECENTER_1F": { + "group": 4, + "height": 4, + "map": 3, + "name": "ECRUTEAK_POKECENTER_1F", + "width": 5 + }, + "ECRUTEAK_TIN_TOWER_BACK_ENTRANCE": { + "group": 4, + "height": 4, + "map": 2, + "name": "ECRUTEAK_TIN_TOWER_BACK_ENTRANCE", + "width": 4 + }, + "ECRUTEAK_TIN_TOWER_ENTRANCE": { + "group": 4, + "height": 9, + "map": 1, + "name": "ECRUTEAK_TIN_TOWER_ENTRANCE", + "width": 10 + }, + "ELMS_HOUSE": { + "group": 24, + "height": 4, + "map": 9, + "name": "ELMS_HOUSE", + "width": 4 + }, + "ELMS_LAB": { + "group": 24, + "height": 6, + "map": 5, + "name": "ELMS_LAB", + "width": 5 + }, + "FAST_SHIP_1F": { + "group": 15, + "height": 9, + "map": 3, + "name": "FAST_SHIP_1F", + "width": 16 + }, + "FAST_SHIP_B1F": { + "group": 15, + "height": 8, + "map": 7, + "name": "FAST_SHIP_B1F", + "width": 16 + }, + "FAST_SHIP_CABINS_NNW_NNE_NE": { + "group": 15, + "height": 16, + "map": 4, + "name": "FAST_SHIP_CABINS_NNW_NNE_NE", + "width": 4 + }, + "FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN": { + "group": 15, + "height": 17, + "map": 6, + "name": "FAST_SHIP_CABINS_SE_SSE_CAPTAINS_CABIN", + "width": 5 + }, + "FAST_SHIP_CABINS_SW_SSW_NW": { + "group": 15, + "height": 16, + "map": 5, + "name": "FAST_SHIP_CABINS_SW_SSW_NW", + "width": 4 + }, + "FIGHTING_DOJO": { + "group": 25, + "height": 6, + "map": 3, + "name": "FIGHTING_DOJO", + "width": 5 + }, + "FUCHSIA_CITY": { + "group": 17, + "height": 18, + "map": 5, + "name": "FUCHSIA_CITY", + "width": 20 + }, + "FUCHSIA_GYM": { + "group": 17, + "height": 9, + "map": 8, + "name": "FUCHSIA_GYM", + "width": 5 + }, + "FUCHSIA_MART": { + "group": 17, + "height": 4, + "map": 6, + "name": "FUCHSIA_MART", + "width": 6 + }, + "FUCHSIA_POKECENTER_1F": { + "group": 17, + "height": 4, + "map": 10, + "name": "FUCHSIA_POKECENTER_1F", + "width": 5 + }, + "FUCHSIA_POKECENTER_2F_BETA": { + "group": 17, + "height": 4, + "map": 11, + "name": "FUCHSIA_POKECENTER_2F_BETA", + "width": 8 + }, + "GOLDENROD_BIKE_SHOP": { + "group": 11, + "height": 4, + "map": 4, + "name": "GOLDENROD_BIKE_SHOP", + "width": 4 + }, + "GOLDENROD_CITY": { + "group": 11, + "height": 18, + "map": 2, + "name": "GOLDENROD_CITY", + "width": 20 + }, + "GOLDENROD_DEPT_STORE_1F": { + "group": 11, + "height": 4, + "map": 12, + "name": "GOLDENROD_DEPT_STORE_1F", + "width": 8 + }, + "GOLDENROD_DEPT_STORE_2F": { + "group": 11, + "height": 4, + "map": 13, + "name": "GOLDENROD_DEPT_STORE_2F", + "width": 8 + }, + "GOLDENROD_DEPT_STORE_3F": { + "group": 11, + "height": 4, + "map": 14, + "name": "GOLDENROD_DEPT_STORE_3F", + "width": 8 + }, + "GOLDENROD_DEPT_STORE_4F": { + "group": 11, + "height": 4, + "map": 15, + "name": "GOLDENROD_DEPT_STORE_4F", + "width": 8 + }, + "GOLDENROD_DEPT_STORE_5F": { + "group": 11, + "height": 4, + "map": 16, + "name": "GOLDENROD_DEPT_STORE_5F", + "width": 8 + }, + "GOLDENROD_DEPT_STORE_6F": { + "group": 11, + "height": 4, + "map": 17, + "name": "GOLDENROD_DEPT_STORE_6F", + "width": 8 + }, + "GOLDENROD_DEPT_STORE_B1F": { + "group": 3, + "height": 9, + "map": 47, + "name": "GOLDENROD_DEPT_STORE_B1F", + "width": 10 + }, + "GOLDENROD_DEPT_STORE_ELEVATOR": { + "group": 11, + "height": 2, + "map": 18, + "name": "GOLDENROD_DEPT_STORE_ELEVATOR", + "width": 2 + }, + "GOLDENROD_FLOWER_SHOP": { + "group": 11, + "height": 4, + "map": 8, + "name": "GOLDENROD_FLOWER_SHOP", + "width": 4 + }, + "GOLDENROD_GAME_CORNER": { + "group": 11, + "height": 7, + "map": 19, + "name": "GOLDENROD_GAME_CORNER", + "width": 10 + }, + "GOLDENROD_GYM": { + "group": 11, + "height": 9, + "map": 3, + "name": "GOLDENROD_GYM", + "width": 10 + }, + "GOLDENROD_HAPPINESS_RATER": { + "group": 11, + "height": 4, + "map": 5, + "name": "GOLDENROD_HAPPINESS_RATER", + "width": 4 + }, + "GOLDENROD_MAGNET_TRAIN_STATION": { + "group": 11, + "height": 9, + "map": 7, + "name": "GOLDENROD_MAGNET_TRAIN_STATION", + "width": 10 + }, + "GOLDENROD_NAME_RATER": { + "group": 11, + "height": 4, + "map": 11, + "name": "GOLDENROD_NAME_RATER", + "width": 4 + }, + "GOLDENROD_POKECENTER_1F": { + "group": 11, + "height": 4, + "map": 9, + "name": "GOLDENROD_POKECENTER_1F", + "width": 5 + }, + "GOLDENROD_PP_SPEECH_HOUSE": { + "group": 11, + "height": 4, + "map": 10, + "name": "GOLDENROD_PP_SPEECH_HOUSE", + "width": 4 + }, + "GOLDENROD_UNDERGROUND": { + "group": 3, + "height": 18, + "map": 45, + "name": "GOLDENROD_UNDERGROUND", + "width": 15 + }, + "GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES": { + "group": 3, + "height": 18, + "map": 46, + "name": "GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES", + "width": 15 + }, + "GOLDENROD_UNDERGROUND_WAREHOUSE": { + "group": 3, + "height": 9, + "map": 48, + "name": "GOLDENROD_UNDERGROUND_WAREHOUSE", + "width": 10 + }, + "GUIDE_GENTS_HOUSE": { + "group": 26, + "height": 4, + "map": 7, + "name": "GUIDE_GENTS_HOUSE", + "width": 4 + }, + "HALL_OF_FAME": { + "group": 16, + "height": 7, + "map": 8, + "name": "HALL_OF_FAME", + "width": 5 + }, + "ICE_PATH_1F": { + "group": 3, + "height": 18, + "map": 53, + "name": "ICE_PATH_1F", + "width": 20 + }, + "ICE_PATH_B1F": { + "group": 3, + "height": 18, + "map": 54, + "name": "ICE_PATH_B1F", + "width": 10 + }, + "ICE_PATH_B2F_BLACKTHORN_SIDE": { + "group": 3, + "height": 9, + "map": 56, + "name": "ICE_PATH_B2F_BLACKTHORN_SIDE", + "width": 5 + }, + "ICE_PATH_B2F_MAHOGANY_SIDE": { + "group": 3, + "height": 9, + "map": 55, + "name": "ICE_PATH_B2F_MAHOGANY_SIDE", + "width": 10 + }, + "ICE_PATH_B3F": { + "group": 3, + "height": 9, + "map": 57, + "name": "ICE_PATH_B3F", + "width": 10 + }, + "ILEX_FOREST": { + "group": 3, + "height": 27, + "map": 44, + "name": "ILEX_FOREST", + "width": 15 + }, + "ILEX_FOREST_AZALEA_GATE": { + "group": 11, + "height": 4, + "map": 20, + "name": "ILEX_FOREST_AZALEA_GATE", + "width": 5 + }, + "INDIGO_PLATEAU_POKECENTER_1F": { + "group": 16, + "height": 7, + "map": 2, + "name": "INDIGO_PLATEAU_POKECENTER_1F", + "width": 9 + }, + "KARENS_ROOM": { + "group": 16, + "height": 9, + "map": 6, + "name": "KARENS_ROOM", + "width": 5 + }, + "KOGAS_ROOM": { + "group": 16, + "height": 9, + "map": 4, + "name": "KOGAS_ROOM", + "width": 5 + }, + "KURTS_HOUSE": { + "group": 8, + "height": 4, + "map": 4, + "name": "KURTS_HOUSE", + "width": 8 + }, + "LAKE_OF_RAGE": { + "group": 9, + "height": 18, + "map": 6, + "name": "LAKE_OF_RAGE", + "width": 20 + }, + "LAKE_OF_RAGE_HIDDEN_POWER_HOUSE": { + "group": 9, + "height": 4, + "map": 1, + "name": "LAKE_OF_RAGE_HIDDEN_POWER_HOUSE", + "width": 4 + }, + "LAKE_OF_RAGE_MAGIKARP_HOUSE": { + "group": 9, + "height": 4, + "map": 2, + "name": "LAKE_OF_RAGE_MAGIKARP_HOUSE", + "width": 4 + }, + "LANCES_ROOM": { + "group": 16, + "height": 12, + "map": 7, + "name": "LANCES_ROOM", + "width": 5 + }, + "LAVENDER_MART": { + "group": 18, + "height": 4, + "map": 10, + "name": "LAVENDER_MART", + "width": 6 + }, + "LAVENDER_NAME_RATER": { + "group": 18, + "height": 4, + "map": 9, + "name": "LAVENDER_NAME_RATER", + "width": 4 + }, + "LAVENDER_POKECENTER_1F": { + "group": 18, + "height": 4, + "map": 5, + "name": "LAVENDER_POKECENTER_1F", + "width": 5 + }, + "LAVENDER_POKECENTER_2F_BETA": { + "group": 18, + "height": 4, + "map": 6, + "name": "LAVENDER_POKECENTER_2F_BETA", + "width": 8 + }, + "LAVENDER_SPEECH_HOUSE": { + "group": 18, + "height": 4, + "map": 8, + "name": "LAVENDER_SPEECH_HOUSE", + "width": 4 + }, + "LAVENDER_TOWN": { + "group": 18, + "height": 9, + "map": 4, + "name": "LAVENDER_TOWN", + "width": 10 + }, + "LAV_RADIO_TOWER_1F": { + "group": 18, + "height": 4, + "map": 12, + "name": "LAV_RADIO_TOWER_1F", + "width": 10 + }, + "MAHOGANY_GYM": { + "group": 2, + "height": 9, + "map": 2, + "name": "MAHOGANY_GYM", + "width": 5 + }, + "MAHOGANY_MART_1F": { + "group": 3, + "height": 4, + "map": 40, + "name": "MAHOGANY_MART_1F", + "width": 4 + }, + "MAHOGANY_POKECENTER_1F": { + "group": 2, + "height": 4, + "map": 3, + "name": "MAHOGANY_POKECENTER_1F", + "width": 5 + }, + "MAHOGANY_RED_GYARADOS_SPEECH_HOUSE": { + "group": 2, + "height": 4, + "map": 1, + "name": "MAHOGANY_RED_GYARADOS_SPEECH_HOUSE", + "width": 4 + }, + "MAHOGANY_TOWN": { + "group": 2, + "height": 9, + "map": 7, + "name": "MAHOGANY_TOWN", + "width": 10 + }, + "MANIAS_HOUSE": { + "group": 22, + "height": 4, + "map": 4, + "name": "MANIAS_HOUSE", + "width": 4 + }, + "MOUNT_MOON": { + "group": 3, + "height": 9, + "map": 76, + "name": "MOUNT_MOON", + "width": 15 + }, + "MOUNT_MOON_GIFT_SHOP": { + "group": 15, + "height": 4, + "map": 11, + "name": "MOUNT_MOON_GIFT_SHOP", + "width": 4 + }, + "MOUNT_MOON_SQUARE": { + "group": 15, + "height": 9, + "map": 10, + "name": "MOUNT_MOON_SQUARE", + "width": 15 + }, + "MOUNT_MORTAR_1F_INSIDE": { + "group": 3, + "height": 27, + "map": 50, + "name": "MOUNT_MORTAR_1F_INSIDE", + "width": 20 + }, + "MOUNT_MORTAR_1F_OUTSIDE": { + "group": 3, + "height": 18, + "map": 49, + "name": "MOUNT_MORTAR_1F_OUTSIDE", + "width": 20 + }, + "MOUNT_MORTAR_2F_INSIDE": { + "group": 3, + "height": 18, + "map": 51, + "name": "MOUNT_MORTAR_2F_INSIDE", + "width": 20 + }, + "MOUNT_MORTAR_B1F": { + "group": 3, + "height": 18, + "map": 52, + "name": "MOUNT_MORTAR_B1F", + "width": 20 + }, + "MOVE_DELETERS_HOUSE": { + "group": 5, + "height": 4, + "map": 7, + "name": "MOVE_DELETERS_HOUSE", + "width": 4 + }, + "MR_FUJIS_HOUSE": { + "group": 18, + "height": 4, + "map": 7, + "name": "MR_FUJIS_HOUSE", + "width": 5 + }, + "MR_POKEMONS_HOUSE": { + "group": 26, + "height": 4, + "map": 10, + "name": "MR_POKEMONS_HOUSE", + "width": 4 + }, + "MR_PSYCHICS_HOUSE": { + "group": 25, + "height": 4, + "map": 8, + "name": "MR_PSYCHICS_HOUSE", + "width": 4 + }, + "NATIONAL_PARK": { + "group": 3, + "height": 27, + "map": 15, + "name": "NATIONAL_PARK", + "width": 20 + }, + "NATIONAL_PARK_BUG_CONTEST": { + "group": 3, + "height": 27, + "map": 16, + "name": "NATIONAL_PARK_BUG_CONTEST", + "width": 20 + }, + "NEW_BARK_TOWN": { + "group": 24, + "height": 9, + "map": 4, + "name": "NEW_BARK_TOWN", + "width": 10 + }, + "OAKS_LAB": { + "group": 13, + "height": 6, + "map": 6, + "name": "OAKS_LAB", + "width": 5 + }, + "OLIVINE_CAFE": { + "group": 1, + "height": 4, + "map": 7, + "name": "OLIVINE_CAFE", + "width": 4 + }, + "OLIVINE_CITY": { + "group": 1, + "height": 18, + "map": 14, + "name": "OLIVINE_CITY", + "width": 20 + }, + "OLIVINE_GOOD_ROD_HOUSE": { + "group": 1, + "height": 4, + "map": 6, + "name": "OLIVINE_GOOD_ROD_HOUSE", + "width": 4 + }, + "OLIVINE_GYM": { + "group": 1, + "height": 8, + "map": 2, + "name": "OLIVINE_GYM", + "width": 5 + }, + "OLIVINE_HOUSE_BETA": { + "group": 1, + "height": 4, + "map": 4, + "name": "OLIVINE_HOUSE_BETA", + "width": 4 + }, + "OLIVINE_LIGHTHOUSE_1F": { + "group": 3, + "height": 9, + "map": 34, + "name": "OLIVINE_LIGHTHOUSE_1F", + "width": 10 + }, + "OLIVINE_LIGHTHOUSE_2F": { + "group": 3, + "height": 9, + "map": 35, + "name": "OLIVINE_LIGHTHOUSE_2F", + "width": 10 + }, + "OLIVINE_LIGHTHOUSE_3F": { + "group": 3, + "height": 9, + "map": 36, + "name": "OLIVINE_LIGHTHOUSE_3F", + "width": 10 + }, + "OLIVINE_LIGHTHOUSE_4F": { + "group": 3, + "height": 9, + "map": 37, + "name": "OLIVINE_LIGHTHOUSE_4F", + "width": 10 + }, + "OLIVINE_LIGHTHOUSE_5F": { + "group": 3, + "height": 9, + "map": 38, + "name": "OLIVINE_LIGHTHOUSE_5F", + "width": 10 + }, + "OLIVINE_LIGHTHOUSE_6F": { + "group": 3, + "height": 9, + "map": 39, + "name": "OLIVINE_LIGHTHOUSE_6F", + "width": 10 + }, + "OLIVINE_MART": { + "group": 1, + "height": 4, + "map": 8, + "name": "OLIVINE_MART", + "width": 6 + }, + "OLIVINE_POKECENTER_1F": { + "group": 1, + "height": 4, + "map": 1, + "name": "OLIVINE_POKECENTER_1F", + "width": 5 + }, + "OLIVINE_PORT": { + "group": 15, + "height": 18, + "map": 1, + "name": "OLIVINE_PORT", + "width": 10 + }, + "OLIVINE_PORT_PASSAGE": { + "group": 15, + "height": 9, + "map": 8, + "name": "OLIVINE_PORT_PASSAGE", + "width": 10 + }, + "OLIVINE_PUNISHMENT_SPEECH_HOUSE": { + "group": 1, + "height": 4, + "map": 5, + "name": "OLIVINE_PUNISHMENT_SPEECH_HOUSE", + "width": 4 + }, + "OLIVINE_TIMS_HOUSE": { + "group": 1, + "height": 4, + "map": 3, + "name": "OLIVINE_TIMS_HOUSE", + "width": 4 + }, + "PALLET_TOWN": { + "group": 13, + "height": 9, + "map": 2, + "name": "PALLET_TOWN", + "width": 10 + }, + "PEWTER_CITY": { + "group": 14, + "height": 18, + "map": 2, + "name": "PEWTER_CITY", + "width": 20 + }, + "PEWTER_GYM": { + "group": 14, + "height": 7, + "map": 4, + "name": "PEWTER_GYM", + "width": 5 + }, + "PEWTER_MART": { + "group": 14, + "height": 4, + "map": 5, + "name": "PEWTER_MART", + "width": 6 + }, + "PEWTER_NIDORAN_SPEECH_HOUSE": { + "group": 14, + "height": 4, + "map": 3, + "name": "PEWTER_NIDORAN_SPEECH_HOUSE", + "width": 4 + }, + "PEWTER_POKECENTER_1F": { + "group": 14, + "height": 4, + "map": 6, + "name": "PEWTER_POKECENTER_1F", + "width": 5 + }, + "PEWTER_POKECENTER_2F_BETA": { + "group": 14, + "height": 4, + "map": 7, + "name": "PEWTER_POKECENTER_2F_BETA", + "width": 8 + }, + "PEWTER_SNOOZE_SPEECH_HOUSE": { + "group": 14, + "height": 4, + "map": 8, + "name": "PEWTER_SNOOZE_SPEECH_HOUSE", + "width": 4 + }, + "PLAYERS_HOUSE_1F": { + "group": 24, + "height": 4, + "map": 6, + "name": "PLAYERS_HOUSE_1F", + "width": 5 + }, + "PLAYERS_HOUSE_2F": { + "group": 24, + "height": 3, + "map": 7, + "name": "PLAYERS_HOUSE_2F", + "width": 4 + }, + "PLAYERS_NEIGHBORS_HOUSE": { + "group": 24, + "height": 4, + "map": 8, + "name": "PLAYERS_NEIGHBORS_HOUSE", + "width": 4 + }, + "POKECENTER_2F": { + "group": 20, + "height": 4, + "map": 1, + "name": "POKECENTER_2F", + "width": 8 + }, + "POKEMON_FAN_CLUB": { + "group": 12, + "height": 4, + "map": 7, + "name": "POKEMON_FAN_CLUB", + "width": 5 + }, + "POWER_PLANT": { + "group": 7, + "height": 9, + "map": 10, + "name": "POWER_PLANT", + "width": 10 + }, + "RADIO_TOWER_1F": { + "group": 3, + "height": 4, + "map": 17, + "name": "RADIO_TOWER_1F", + "width": 9 + }, + "RADIO_TOWER_2F": { + "group": 3, + "height": 4, + "map": 18, + "name": "RADIO_TOWER_2F", + "width": 9 + }, + "RADIO_TOWER_3F": { + "group": 3, + "height": 4, + "map": 19, + "name": "RADIO_TOWER_3F", + "width": 9 + }, + "RADIO_TOWER_4F": { + "group": 3, + "height": 4, + "map": 20, + "name": "RADIO_TOWER_4F", + "width": 9 + }, + "RADIO_TOWER_5F": { + "group": 3, + "height": 4, + "map": 21, + "name": "RADIO_TOWER_5F", + "width": 9 + }, + "REDS_HOUSE_1F": { + "group": 13, + "height": 4, + "map": 3, + "name": "REDS_HOUSE_1F", + "width": 4 + }, + "REDS_HOUSE_2F": { + "group": 13, + "height": 4, + "map": 4, + "name": "REDS_HOUSE_2F", + "width": 4 + }, + "ROCK_TUNNEL_1F": { + "group": 3, + "height": 18, + "map": 78, + "name": "ROCK_TUNNEL_1F", + "width": 15 + }, + "ROCK_TUNNEL_B1F": { + "group": 3, + "height": 18, + "map": 79, + "name": "ROCK_TUNNEL_B1F", + "width": 15 + }, + "ROUTE_1": { + "group": 13, + "height": 18, + "map": 1, + "name": "ROUTE_1", + "width": 10 + }, + "ROUTE_10_NORTH": { + "group": 7, + "height": 9, + "map": 14, + "name": "ROUTE_10_NORTH", + "width": 10 + }, + "ROUTE_10_POKECENTER_1F": { + "group": 7, + "height": 4, + "map": 8, + "name": "ROUTE_10_POKECENTER_1F", + "width": 5 + }, + "ROUTE_10_POKECENTER_2F_BETA": { + "group": 7, + "height": 4, + "map": 9, + "name": "ROUTE_10_POKECENTER_2F_BETA", + "width": 8 + }, + "ROUTE_10_SOUTH": { + "group": 18, + "height": 9, + "map": 3, + "name": "ROUTE_10_SOUTH", + "width": 10 + }, + "ROUTE_11": { + "group": 12, + "height": 9, + "map": 2, + "name": "ROUTE_11", + "width": 20 + }, + "ROUTE_12": { + "group": 18, + "height": 27, + "map": 2, + "name": "ROUTE_12", + "width": 10 + }, + "ROUTE_12_SUPER_ROD_HOUSE": { + "group": 18, + "height": 4, + "map": 14, + "name": "ROUTE_12_SUPER_ROD_HOUSE", + "width": 4 + }, + "ROUTE_13": { + "group": 17, + "height": 9, + "map": 1, + "name": "ROUTE_13", + "width": 30 + }, + "ROUTE_14": { + "group": 17, + "height": 18, + "map": 2, + "name": "ROUTE_14", + "width": 10 + }, + "ROUTE_15": { + "group": 17, + "height": 9, + "map": 3, + "name": "ROUTE_15", + "width": 20 + }, + "ROUTE_15_FUCHSIA_GATE": { + "group": 17, + "height": 4, + "map": 13, + "name": "ROUTE_15_FUCHSIA_GATE", + "width": 5 + }, + "ROUTE_16": { + "group": 21, + "height": 9, + "map": 2, + "name": "ROUTE_16", + "width": 10 + }, + "ROUTE_16_FUCHSIA_SPEECH_HOUSE": { + "group": 21, + "height": 4, + "map": 23, + "name": "ROUTE_16_FUCHSIA_SPEECH_HOUSE", + "width": 4 + }, + "ROUTE_16_GATE": { + "group": 21, + "height": 4, + "map": 24, + "name": "ROUTE_16_GATE", + "width": 5 + }, + "ROUTE_17": { + "group": 21, + "height": 45, + "map": 3, + "name": "ROUTE_17", + "width": 10 + }, + "ROUTE_17_ROUTE_18_GATE": { + "group": 21, + "height": 4, + "map": 26, + "name": "ROUTE_17_ROUTE_18_GATE", + "width": 5 + }, + "ROUTE_18": { + "group": 17, + "height": 9, + "map": 4, + "name": "ROUTE_18", + "width": 10 + }, + "ROUTE_19": { + "group": 6, + "height": 18, + "map": 5, + "name": "ROUTE_19", + "width": 10 + }, + "ROUTE_19_FUCHSIA_GATE": { + "group": 6, + "height": 4, + "map": 3, + "name": "ROUTE_19_FUCHSIA_GATE", + "width": 5 + }, + "ROUTE_2": { + "group": 23, + "height": 27, + "map": 1, + "name": "ROUTE_2", + "width": 10 + }, + "ROUTE_20": { + "group": 6, + "height": 9, + "map": 6, + "name": "ROUTE_20", + "width": 30 + }, + "ROUTE_21": { + "group": 6, + "height": 18, + "map": 7, + "name": "ROUTE_21", + "width": 10 + }, + "ROUTE_22": { + "group": 23, + "height": 9, + "map": 2, + "name": "ROUTE_22", + "width": 20 + }, + "ROUTE_23": { + "group": 16, + "height": 9, + "map": 1, + "name": "ROUTE_23", + "width": 10 + }, + "ROUTE_24": { + "group": 7, + "height": 9, + "map": 15, + "name": "ROUTE_24", + "width": 10 + }, + "ROUTE_25": { + "group": 7, + "height": 9, + "map": 16, + "name": "ROUTE_25", + "width": 30 + }, + "ROUTE_26": { + "group": 24, + "height": 54, + "map": 1, + "name": "ROUTE_26", + "width": 10 + }, + "ROUTE_26_HEAL_HOUSE": { + "group": 24, + "height": 4, + "map": 10, + "name": "ROUTE_26_HEAL_HOUSE", + "width": 4 + }, + "ROUTE_27": { + "group": 24, + "height": 9, + "map": 2, + "name": "ROUTE_27", + "width": 40 + }, + "ROUTE_27_SANDSTORM_HOUSE": { + "group": 24, + "height": 4, + "map": 12, + "name": "ROUTE_27_SANDSTORM_HOUSE", + "width": 4 + }, + "ROUTE_28": { + "group": 19, + "height": 9, + "map": 1, + "name": "ROUTE_28", + "width": 20 + }, + "ROUTE_28_STEEL_WING_HOUSE": { + "group": 19, + "height": 4, + "map": 4, + "name": "ROUTE_28_STEEL_WING_HOUSE", + "width": 4 + }, + "ROUTE_29": { + "group": 24, + "height": 9, + "map": 3, + "name": "ROUTE_29", + "width": 30 + }, + "ROUTE_29_ROUTE_46_GATE": { + "group": 24, + "height": 4, + "map": 13, + "name": "ROUTE_29_ROUTE_46_GATE", + "width": 5 + }, + "ROUTE_2_GATE": { + "group": 23, + "height": 4, + "map": 12, + "name": "ROUTE_2_GATE", + "width": 5 + }, + "ROUTE_2_NUGGET_HOUSE": { + "group": 23, + "height": 4, + "map": 11, + "name": "ROUTE_2_NUGGET_HOUSE", + "width": 4 + }, + "ROUTE_3": { + "group": 14, + "height": 9, + "map": 1, + "name": "ROUTE_3", + "width": 30 + }, + "ROUTE_30": { + "group": 26, + "height": 27, + "map": 1, + "name": "ROUTE_30", + "width": 10 + }, + "ROUTE_30_BERRY_HOUSE": { + "group": 26, + "height": 4, + "map": 9, + "name": "ROUTE_30_BERRY_HOUSE", + "width": 4 + }, + "ROUTE_31": { + "group": 26, + "height": 9, + "map": 2, + "name": "ROUTE_31", + "width": 20 + }, + "ROUTE_31_VIOLET_GATE": { + "group": 26, + "height": 4, + "map": 11, + "name": "ROUTE_31_VIOLET_GATE", + "width": 5 + }, + "ROUTE_32": { + "group": 10, + "height": 45, + "map": 1, + "name": "ROUTE_32", + "width": 10 + }, + "ROUTE_32_POKECENTER_1F": { + "group": 10, + "height": 4, + "map": 13, + "name": "ROUTE_32_POKECENTER_1F", + "width": 5 + }, + "ROUTE_32_RUINS_OF_ALPH_GATE": { + "group": 10, + "height": 4, + "map": 12, + "name": "ROUTE_32_RUINS_OF_ALPH_GATE", + "width": 5 + }, + "ROUTE_33": { + "group": 8, + "height": 9, + "map": 6, + "name": "ROUTE_33", + "width": 10 + }, + "ROUTE_34": { + "group": 11, + "height": 27, + "map": 1, + "name": "ROUTE_34", + "width": 10 + }, + "ROUTE_34_ILEX_FOREST_GATE": { + "group": 11, + "height": 4, + "map": 21, + "name": "ROUTE_34_ILEX_FOREST_GATE", + "width": 5 + }, + "ROUTE_35": { + "group": 10, + "height": 18, + "map": 2, + "name": "ROUTE_35", + "width": 10 + }, + "ROUTE_35_GOLDENROD_GATE": { + "group": 10, + "height": 4, + "map": 14, + "name": "ROUTE_35_GOLDENROD_GATE", + "width": 5 + }, + "ROUTE_35_NATIONAL_PARK_GATE": { + "group": 10, + "height": 4, + "map": 15, + "name": "ROUTE_35_NATIONAL_PARK_GATE", + "width": 4 + }, + "ROUTE_36": { + "group": 10, + "height": 9, + "map": 3, + "name": "ROUTE_36", + "width": 30 + }, + "ROUTE_36_NATIONAL_PARK_GATE": { + "group": 10, + "height": 4, + "map": 17, + "name": "ROUTE_36_NATIONAL_PARK_GATE", + "width": 5 + }, + "ROUTE_36_RUINS_OF_ALPH_GATE": { + "group": 10, + "height": 4, + "map": 16, + "name": "ROUTE_36_RUINS_OF_ALPH_GATE", + "width": 5 + }, + "ROUTE_37": { + "group": 10, + "height": 9, + "map": 4, + "name": "ROUTE_37", + "width": 10 + }, + "ROUTE_38": { + "group": 1, + "height": 9, + "map": 12, + "name": "ROUTE_38", + "width": 20 + }, + "ROUTE_38_ECRUTEAK_GATE": { + "group": 1, + "height": 4, + "map": 9, + "name": "ROUTE_38_ECRUTEAK_GATE", + "width": 5 + }, + "ROUTE_39": { + "group": 1, + "height": 18, + "map": 13, + "name": "ROUTE_39", + "width": 10 + }, + "ROUTE_39_BARN": { + "group": 1, + "height": 4, + "map": 10, + "name": "ROUTE_39_BARN", + "width": 4 + }, + "ROUTE_39_FARMHOUSE": { + "group": 1, + "height": 4, + "map": 11, + "name": "ROUTE_39_FARMHOUSE", + "width": 4 + }, + "ROUTE_4": { + "group": 7, + "height": 9, + "map": 12, + "name": "ROUTE_4", + "width": 20 + }, + "ROUTE_40": { + "group": 22, + "height": 18, + "map": 1, + "name": "ROUTE_40", + "width": 10 + }, + "ROUTE_41": { + "group": 22, + "height": 27, + "map": 2, + "name": "ROUTE_41", + "width": 25 + }, + "ROUTE_42": { + "group": 2, + "height": 9, + "map": 5, + "name": "ROUTE_42", + "width": 30 + }, + "ROUTE_42_ECRUTEAK_GATE": { + "group": 2, + "height": 4, + "map": 4, + "name": "ROUTE_42_ECRUTEAK_GATE", + "width": 5 + }, + "ROUTE_43": { + "group": 9, + "height": 27, + "map": 5, + "name": "ROUTE_43", + "width": 10 + }, + "ROUTE_43_GATE": { + "group": 9, + "height": 4, + "map": 4, + "name": "ROUTE_43_GATE", + "width": 5 + }, + "ROUTE_43_MAHOGANY_GATE": { + "group": 9, + "height": 4, + "map": 3, + "name": "ROUTE_43_MAHOGANY_GATE", + "width": 5 + }, + "ROUTE_44": { + "group": 2, + "height": 9, + "map": 6, + "name": "ROUTE_44", + "width": 30 + }, + "ROUTE_45": { + "group": 5, + "height": 45, + "map": 8, + "name": "ROUTE_45", + "width": 10 + }, + "ROUTE_46": { + "group": 5, + "height": 18, + "map": 9, + "name": "ROUTE_46", + "width": 10 + }, + "ROUTE_5": { + "group": 25, + "height": 9, + "map": 1, + "name": "ROUTE_5", + "width": 10 + }, + "ROUTE_5_CLEANSE_TAG_HOUSE": { + "group": 25, + "height": 4, + "map": 15, + "name": "ROUTE_5_CLEANSE_TAG_HOUSE", + "width": 4 + }, + "ROUTE_5_SAFFRON_GATE": { + "group": 25, + "height": 4, + "map": 14, + "name": "ROUTE_5_SAFFRON_GATE", + "width": 5 + }, + "ROUTE_5_UNDERGROUND_PATH_ENTRANCE": { + "group": 25, + "height": 4, + "map": 13, + "name": "ROUTE_5_UNDERGROUND_PATH_ENTRANCE", + "width": 4 + }, + "ROUTE_6": { + "group": 12, + "height": 9, + "map": 1, + "name": "ROUTE_6", + "width": 10 + }, + "ROUTE_6_SAFFRON_GATE": { + "group": 12, + "height": 4, + "map": 12, + "name": "ROUTE_6_SAFFRON_GATE", + "width": 5 + }, + "ROUTE_6_UNDERGROUND_PATH_ENTRANCE": { + "group": 12, + "height": 4, + "map": 13, + "name": "ROUTE_6_UNDERGROUND_PATH_ENTRANCE", + "width": 4 + }, + "ROUTE_7": { + "group": 21, + "height": 9, + "map": 1, + "name": "ROUTE_7", + "width": 10 + }, + "ROUTE_7_SAFFRON_GATE": { + "group": 21, + "height": 4, + "map": 25, + "name": "ROUTE_7_SAFFRON_GATE", + "width": 5 + }, + "ROUTE_8": { + "group": 18, + "height": 9, + "map": 1, + "name": "ROUTE_8", + "width": 20 + }, + "ROUTE_8_SAFFRON_GATE": { + "group": 18, + "height": 4, + "map": 13, + "name": "ROUTE_8_SAFFRON_GATE", + "width": 5 + }, + "ROUTE_9": { + "group": 7, + "height": 9, + "map": 13, + "name": "ROUTE_9", + "width": 30 + }, + "RUINS_OF_ALPH_AERODACTYL_CHAMBER": { + "group": 3, + "height": 5, + "map": 26, + "name": "RUINS_OF_ALPH_AERODACTYL_CHAMBER", + "width": 4 + }, + "RUINS_OF_ALPH_HO_OH_CHAMBER": { + "group": 3, + "height": 5, + "map": 23, + "name": "RUINS_OF_ALPH_HO_OH_CHAMBER", + "width": 4 + }, + "RUINS_OF_ALPH_INNER_CHAMBER": { + "group": 3, + "height": 14, + "map": 27, + "name": "RUINS_OF_ALPH_INNER_CHAMBER", + "width": 10 + }, + "RUINS_OF_ALPH_KABUTO_CHAMBER": { + "group": 3, + "height": 5, + "map": 24, + "name": "RUINS_OF_ALPH_KABUTO_CHAMBER", + "width": 4 + }, + "RUINS_OF_ALPH_OMANYTE_CHAMBER": { + "group": 3, + "height": 5, + "map": 25, + "name": "RUINS_OF_ALPH_OMANYTE_CHAMBER", + "width": 4 + }, + "RUINS_OF_ALPH_OUTSIDE": { + "group": 3, + "height": 18, + "map": 22, + "name": "RUINS_OF_ALPH_OUTSIDE", + "width": 10 + }, + "RUINS_OF_ALPH_RESEARCH_CENTER": { + "group": 3, + "height": 4, + "map": 28, + "name": "RUINS_OF_ALPH_RESEARCH_CENTER", + "width": 4 + }, + "SAFARI_ZONE_BETA": { + "group": 3, + "height": 18, + "map": 81, + "name": "SAFARI_ZONE_BETA", + "width": 10 + }, + "SAFARI_ZONE_FUCHSIA_GATE_BETA": { + "group": 3, + "height": 4, + "map": 80, + "name": "SAFARI_ZONE_FUCHSIA_GATE_BETA", + "width": 5 + }, + "SAFARI_ZONE_MAIN_OFFICE": { + "group": 17, + "height": 4, + "map": 7, + "name": "SAFARI_ZONE_MAIN_OFFICE", + "width": 4 + }, + "SAFARI_ZONE_WARDENS_HOME": { + "group": 17, + "height": 4, + "map": 12, + "name": "SAFARI_ZONE_WARDENS_HOME", + "width": 5 + }, + "SAFFRON_CITY": { + "group": 25, + "height": 18, + "map": 2, + "name": "SAFFRON_CITY", + "width": 20 + }, + "SAFFRON_GYM": { + "group": 25, + "height": 9, + "map": 4, + "name": "SAFFRON_GYM", + "width": 10 + }, + "SAFFRON_MAGNET_TRAIN_STATION": { + "group": 25, + "height": 9, + "map": 9, + "name": "SAFFRON_MAGNET_TRAIN_STATION", + "width": 10 + }, + "SAFFRON_MART": { + "group": 25, + "height": 4, + "map": 5, + "name": "SAFFRON_MART", + "width": 6 + }, + "SAFFRON_POKECENTER_1F": { + "group": 25, + "height": 4, + "map": 6, + "name": "SAFFRON_POKECENTER_1F", + "width": 5 + }, + "SAFFRON_POKECENTER_2F_BETA": { + "group": 25, + "height": 4, + "map": 7, + "name": "SAFFRON_POKECENTER_2F_BETA", + "width": 8 + }, + "SEAFOAM_GYM": { + "group": 6, + "height": 4, + "map": 4, + "name": "SEAFOAM_GYM", + "width": 5 + }, + "SILPH_CO_1F": { + "group": 25, + "height": 4, + "map": 10, + "name": "SILPH_CO_1F", + "width": 8 + }, + "SILVER_CAVE_ITEM_ROOMS": { + "group": 3, + "height": 9, + "map": 69, + "name": "SILVER_CAVE_ITEM_ROOMS", + "width": 10 + }, + "SILVER_CAVE_OUTSIDE": { + "group": 19, + "height": 18, + "map": 2, + "name": "SILVER_CAVE_OUTSIDE", + "width": 20 + }, + "SILVER_CAVE_POKECENTER_1F": { + "group": 19, + "height": 4, + "map": 3, + "name": "SILVER_CAVE_POKECENTER_1F", + "width": 5 + }, + "SILVER_CAVE_ROOM_1": { + "group": 3, + "height": 18, + "map": 66, + "name": "SILVER_CAVE_ROOM_1", + "width": 10 + }, + "SILVER_CAVE_ROOM_2": { + "group": 3, + "height": 18, + "map": 67, + "name": "SILVER_CAVE_ROOM_2", + "width": 15 + }, + "SILVER_CAVE_ROOM_3": { + "group": 3, + "height": 18, + "map": 68, + "name": "SILVER_CAVE_ROOM_3", + "width": 10 + }, + "SLOWPOKE_WELL_B1F": { + "group": 3, + "height": 9, + "map": 32, + "name": "SLOWPOKE_WELL_B1F", + "width": 10 + }, + "SLOWPOKE_WELL_B2F": { + "group": 3, + "height": 9, + "map": 33, + "name": "SLOWPOKE_WELL_B2F", + "width": 10 + }, + "SOUL_HOUSE": { + "group": 18, + "height": 4, + "map": 11, + "name": "SOUL_HOUSE", + "width": 5 + }, + "SPROUT_TOWER_1F": { + "group": 3, + "height": 8, + "map": 1, + "name": "SPROUT_TOWER_1F", + "width": 10 + }, + "SPROUT_TOWER_2F": { + "group": 3, + "height": 8, + "map": 2, + "name": "SPROUT_TOWER_2F", + "width": 10 + }, + "SPROUT_TOWER_3F": { + "group": 3, + "height": 8, + "map": 3, + "name": "SPROUT_TOWER_3F", + "width": 10 + }, + "TEAM_ROCKET_BASE_B1F": { + "group": 3, + "height": 9, + "map": 41, + "name": "TEAM_ROCKET_BASE_B1F", + "width": 15 + }, + "TEAM_ROCKET_BASE_B2F": { + "group": 3, + "height": 9, + "map": 42, + "name": "TEAM_ROCKET_BASE_B2F", + "width": 15 + }, + "TEAM_ROCKET_BASE_B3F": { + "group": 3, + "height": 9, + "map": 43, + "name": "TEAM_ROCKET_BASE_B3F", + "width": 15 + }, + "TIME_CAPSULE": { + "group": 20, + "height": 4, + "map": 4, + "name": "TIME_CAPSULE", + "width": 5 + }, + "TIN_TOWER_1F": { + "group": 3, + "height": 9, + "map": 4, + "name": "TIN_TOWER_1F", + "width": 10 + }, + "TIN_TOWER_2F": { + "group": 3, + "height": 9, + "map": 5, + "name": "TIN_TOWER_2F", + "width": 10 + }, + "TIN_TOWER_3F": { + "group": 3, + "height": 9, + "map": 6, + "name": "TIN_TOWER_3F", + "width": 10 + }, + "TIN_TOWER_4F": { + "group": 3, + "height": 9, + "map": 7, + "name": "TIN_TOWER_4F", + "width": 10 + }, + "TIN_TOWER_5F": { + "group": 3, + "height": 9, + "map": 8, + "name": "TIN_TOWER_5F", + "width": 10 + }, + "TIN_TOWER_6F": { + "group": 3, + "height": 9, + "map": 9, + "name": "TIN_TOWER_6F", + "width": 10 + }, + "TIN_TOWER_7F": { + "group": 3, + "height": 9, + "map": 10, + "name": "TIN_TOWER_7F", + "width": 10 + }, + "TIN_TOWER_8F": { + "group": 3, + "height": 9, + "map": 11, + "name": "TIN_TOWER_8F", + "width": 10 + }, + "TIN_TOWER_9F": { + "group": 3, + "height": 9, + "map": 12, + "name": "TIN_TOWER_9F", + "width": 10 + }, + "TIN_TOWER_ROOF": { + "group": 15, + "height": 9, + "map": 12, + "name": "TIN_TOWER_ROOF", + "width": 10 + }, + "TOHJO_FALLS": { + "group": 3, + "height": 9, + "map": 74, + "name": "TOHJO_FALLS", + "width": 15 + }, + "TRADE_CENTER": { + "group": 20, + "height": 4, + "map": 2, + "name": "TRADE_CENTER", + "width": 5 + }, + "TRAINER_HOUSE_1F": { + "group": 23, + "height": 7, + "map": 6, + "name": "TRAINER_HOUSE_1F", + "width": 5 + }, + "TRAINER_HOUSE_B1F": { + "group": 23, + "height": 8, + "map": 7, + "name": "TRAINER_HOUSE_B1F", + "width": 5 + }, + "UNDERGROUND_PATH": { + "group": 3, + "height": 14, + "map": 77, + "name": "UNDERGROUND_PATH", + "width": 3 + }, + "UNION_CAVE_1F": { + "group": 3, + "height": 18, + "map": 29, + "name": "UNION_CAVE_1F", + "width": 10 + }, + "UNION_CAVE_B1F": { + "group": 3, + "height": 18, + "map": 30, + "name": "UNION_CAVE_B1F", + "width": 10 + }, + "UNION_CAVE_B2F": { + "group": 3, + "height": 18, + "map": 31, + "name": "UNION_CAVE_B2F", + "width": 10 + }, + "VERMILION_CITY": { + "group": 12, + "height": 18, + "map": 3, + "name": "VERMILION_CITY", + "width": 20 + }, + "VERMILION_DIGLETTS_CAVE_SPEECH_HOUSE": { + "group": 12, + "height": 4, + "map": 10, + "name": "VERMILION_DIGLETTS_CAVE_SPEECH_HOUSE", + "width": 4 + }, + "VERMILION_FISHING_SPEECH_HOUSE": { + "group": 12, + "height": 4, + "map": 4, + "name": "VERMILION_FISHING_SPEECH_HOUSE", + "width": 4 + }, + "VERMILION_GYM": { + "group": 12, + "height": 9, + "map": 11, + "name": "VERMILION_GYM", + "width": 5 + }, + "VERMILION_MAGNET_TRAIN_SPEECH_HOUSE": { + "group": 12, + "height": 4, + "map": 8, + "name": "VERMILION_MAGNET_TRAIN_SPEECH_HOUSE", + "width": 4 + }, + "VERMILION_MART": { + "group": 12, + "height": 4, + "map": 9, + "name": "VERMILION_MART", + "width": 6 + }, + "VERMILION_POKECENTER_1F": { + "group": 12, + "height": 4, + "map": 5, + "name": "VERMILION_POKECENTER_1F", + "width": 5 + }, + "VERMILION_POKECENTER_2F_BETA": { + "group": 12, + "height": 4, + "map": 6, + "name": "VERMILION_POKECENTER_2F_BETA", + "width": 8 + }, + "VERMILION_PORT": { + "group": 15, + "height": 18, + "map": 2, + "name": "VERMILION_PORT", + "width": 10 + }, + "VERMILION_PORT_PASSAGE": { + "group": 15, + "height": 9, + "map": 9, + "name": "VERMILION_PORT_PASSAGE", + "width": 10 + }, + "VICTORY_ROAD": { + "group": 3, + "height": 36, + "map": 82, + "name": "VICTORY_ROAD", + "width": 10 + }, + "VICTORY_ROAD_GATE": { + "group": 23, + "height": 9, + "map": 13, + "name": "VICTORY_ROAD_GATE", + "width": 10 + }, + "VIOLET_CITY": { + "group": 10, + "height": 18, + "map": 5, + "name": "VIOLET_CITY", + "width": 20 + }, + "VIOLET_GYM": { + "group": 10, + "height": 8, + "map": 7, + "name": "VIOLET_GYM", + "width": 5 + }, + "VIOLET_KYLES_HOUSE": { + "group": 10, + "height": 4, + "map": 11, + "name": "VIOLET_KYLES_HOUSE", + "width": 4 + }, + "VIOLET_MART": { + "group": 10, + "height": 4, + "map": 6, + "name": "VIOLET_MART", + "width": 6 + }, + "VIOLET_NICKNAME_SPEECH_HOUSE": { + "group": 10, + "height": 4, + "map": 9, + "name": "VIOLET_NICKNAME_SPEECH_HOUSE", + "width": 4 + }, + "VIOLET_POKECENTER_1F": { + "group": 10, + "height": 4, + "map": 10, + "name": "VIOLET_POKECENTER_1F", + "width": 5 + }, + "VIRIDIAN_CITY": { + "group": 23, + "height": 18, + "map": 3, + "name": "VIRIDIAN_CITY", + "width": 20 + }, + "VIRIDIAN_GYM": { + "group": 23, + "height": 9, + "map": 4, + "name": "VIRIDIAN_GYM", + "width": 5 + }, + "VIRIDIAN_MART": { + "group": 23, + "height": 4, + "map": 8, + "name": "VIRIDIAN_MART", + "width": 6 + }, + "VIRIDIAN_NICKNAME_SPEECH_HOUSE": { + "group": 23, + "height": 4, + "map": 5, + "name": "VIRIDIAN_NICKNAME_SPEECH_HOUSE", + "width": 4 + }, + "VIRIDIAN_POKECENTER_1F": { + "group": 23, + "height": 4, + "map": 9, + "name": "VIRIDIAN_POKECENTER_1F", + "width": 5 + }, + "VIRIDIAN_POKECENTER_2F_BETA": { + "group": 23, + "height": 4, + "map": 10, + "name": "VIRIDIAN_POKECENTER_2F_BETA", + "width": 8 + }, + "WHIRL_ISLAND_B1F": { + "group": 3, + "height": 18, + "map": 63, + "name": "WHIRL_ISLAND_B1F", + "width": 20 + }, + "WHIRL_ISLAND_B2F": { + "group": 3, + "height": 18, + "map": 64, + "name": "WHIRL_ISLAND_B2F", + "width": 10 + }, + "WHIRL_ISLAND_CAVE": { + "group": 3, + "height": 9, + "map": 61, + "name": "WHIRL_ISLAND_CAVE", + "width": 5 + }, + "WHIRL_ISLAND_LUGIA_CHAMBER": { + "group": 3, + "height": 9, + "map": 65, + "name": "WHIRL_ISLAND_LUGIA_CHAMBER", + "width": 10 + }, + "WHIRL_ISLAND_NE": { + "group": 3, + "height": 9, + "map": 59, + "name": "WHIRL_ISLAND_NE", + "width": 10 + }, + "WHIRL_ISLAND_NW": { + "group": 3, + "height": 9, + "map": 58, + "name": "WHIRL_ISLAND_NW", + "width": 5 + }, + "WHIRL_ISLAND_SE": { + "group": 3, + "height": 9, + "map": 62, + "name": "WHIRL_ISLAND_SE", + "width": 5 + }, + "WHIRL_ISLAND_SW": { + "group": 3, + "height": 9, + "map": 60, + "name": "WHIRL_ISLAND_SW", + "width": 10 + }, + "WILLS_ROOM": { + "group": 16, + "height": 9, + "map": 3, + "name": "WILLS_ROOM", + "width": 5 + } + }, + "pokemonAssets": { + "ABRA": { + "back": "abra_back", + "backLabel": "AbraBackpic", + "dexLabel": "AbraPokedexEntry", + "front": "abra", + "frontLabel": "AbraFrontpic", + "id": "ABRA", + "name": "ABRA" + }, + "AERODACTYL": { + "back": "aerodactyl_back", + "backLabel": "AerodactylBackpic", + "dexLabel": "AerodactylPokedexEntry", + "front": "aerodactyl", + "frontLabel": "AerodactylFrontpic", + "id": "AERODACTYL", + "name": "AERODACTYL" + }, + "AIPOM": { + "back": "aipom_back", + "backLabel": "AipomBackpic", + "dexLabel": "AipomPokedexEntry", + "front": "aipom", + "frontLabel": "AipomFrontpic", + "id": "AIPOM", + "name": "AIPOM" + }, + "ALAKAZAM": { + "back": "alakazam_back", + "backLabel": "AlakazamBackpic", + "dexLabel": "AlakazamPokedexEntry", + "front": "alakazam", + "frontLabel": "AlakazamFrontpic", + "id": "ALAKAZAM", + "name": "ALAKAZAM" + }, + "AMPHAROS": { + "back": "ampharos_back", + "backLabel": "AmpharosBackpic", + "dexLabel": "AmpharosPokedexEntry", + "front": "ampharos", + "frontLabel": "AmpharosFrontpic", + "id": "AMPHAROS", + "name": "AMPHAROS" + }, + "ARBOK": { + "back": "arbok_back", + "backLabel": "ArbokBackpic", + "dexLabel": "ArbokPokedexEntry", + "front": "arbok", + "frontLabel": "ArbokFrontpic", + "id": "ARBOK", + "name": "ARBOK" + }, + "ARCANINE": { + "back": "arcanine_back", + "backLabel": "ArcanineBackpic", + "dexLabel": "ArcaninePokedexEntry", + "front": "arcanine", + "frontLabel": "ArcanineFrontpic", + "id": "ARCANINE", + "name": "ARCANINE" + }, + "ARIADOS": { + "back": "ariados_back", + "backLabel": "AriadosBackpic", + "dexLabel": "AriadosPokedexEntry", + "front": "ariados", + "frontLabel": "AriadosFrontpic", + "id": "ARIADOS", + "name": "ARIADOS" + }, + "ARTICUNO": { + "back": "articuno_back", + "backLabel": "ArticunoBackpic", + "dexLabel": "ArticunoPokedexEntry", + "front": "articuno", + "frontLabel": "ArticunoFrontpic", + "id": "ARTICUNO", + "name": "ARTICUNO" + }, + "AZUMARILL": { + "back": "azumarill_back", + "backLabel": "AzumarillBackpic", + "dexLabel": "AzumarillPokedexEntry", + "front": "azumarill", + "frontLabel": "AzumarillFrontpic", + "id": "AZUMARILL", + "name": "AZUMARILL" + }, + "BAYLEEF": { + "back": "bayleef_back", + "backLabel": "BayleefBackpic", + "dexLabel": "BayleefPokedexEntry", + "front": "bayleef", + "frontLabel": "BayleefFrontpic", + "id": "BAYLEEF", + "name": "BAYLEEF" + }, + "BEEDRILL": { + "back": "beedrill_back", + "backLabel": "BeedrillBackpic", + "dexLabel": "BeedrillPokedexEntry", + "front": "beedrill", + "frontLabel": "BeedrillFrontpic", + "id": "BEEDRILL", + "name": "BEEDRILL" + }, + "BELLOSSOM": { + "back": "bellossom_back", + "backLabel": "BellossomBackpic", + "dexLabel": "BellossomPokedexEntry", + "front": "bellossom", + "frontLabel": "BellossomFrontpic", + "id": "BELLOSSOM", + "name": "BELLOSSOM" + }, + "BELLSPROUT": { + "back": "bellsprout_back", + "backLabel": "BellsproutBackpic", + "dexLabel": "BellsproutPokedexEntry", + "front": "bellsprout", + "frontLabel": "BellsproutFrontpic", + "id": "BELLSPROUT", + "name": "BELLSPROUT" + }, + "BLASTOISE": { + "back": "blastoise_back", + "backLabel": "BlastoiseBackpic", + "dexLabel": "BlastoisePokedexEntry", + "front": "blastoise", + "frontLabel": "BlastoiseFrontpic", + "id": "BLASTOISE", + "name": "BLASTOISE" + }, + "BLISSEY": { + "back": "blissey_back", + "backLabel": "BlisseyBackpic", + "dexLabel": "BlisseyPokedexEntry", + "front": "blissey", + "frontLabel": "BlisseyFrontpic", + "id": "BLISSEY", + "name": "BLISSEY" + }, + "BULBASAUR": { + "back": "bulbasaur_back", + "backLabel": "BulbasaurBackpic", + "dexLabel": "BulbasaurPokedexEntry", + "front": "bulbasaur", + "frontLabel": "BulbasaurFrontpic", + "id": "BULBASAUR", + "name": "BULBASAUR" + }, + "BUTTERFREE": { + "back": "butterfree_back", + "backLabel": "ButterfreeBackpic", + "dexLabel": "ButterfreePokedexEntry", + "front": "butterfree", + "frontLabel": "ButterfreeFrontpic", + "id": "BUTTERFREE", + "name": "BUTTERFREE" + }, + "CATERPIE": { + "back": "caterpie_back", + "backLabel": "CaterpieBackpic", + "dexLabel": "CaterpiePokedexEntry", + "front": "caterpie", + "frontLabel": "CaterpieFrontpic", + "id": "CATERPIE", + "name": "CATERPIE" + }, + "CELEBI": { + "back": "celebi_back", + "backLabel": "CelebiBackpic", + "dexLabel": "CelebiPokedexEntry", + "front": "celebi", + "frontLabel": "CelebiFrontpic", + "id": "CELEBI", + "name": "CELEBI" + }, + "CHANSEY": { + "back": "chansey_back", + "backLabel": "ChanseyBackpic", + "dexLabel": "ChanseyPokedexEntry", + "front": "chansey", + "frontLabel": "ChanseyFrontpic", + "id": "CHANSEY", + "name": "CHANSEY" + }, + "CHARIZARD": { + "back": "charizard_back", + "backLabel": "CharizardBackpic", + "dexLabel": "CharizardPokedexEntry", + "front": "charizard", + "frontLabel": "CharizardFrontpic", + "id": "CHARIZARD", + "name": "CHARIZARD" + }, + "CHARMANDER": { + "back": "charmander_back", + "backLabel": "CharmanderBackpic", + "dexLabel": "CharmanderPokedexEntry", + "front": "charmander", + "frontLabel": "CharmanderFrontpic", + "id": "CHARMANDER", + "name": "CHARMANDER" + }, + "CHARMELEON": { + "back": "charmeleon_back", + "backLabel": "CharmeleonBackpic", + "dexLabel": "CharmeleonPokedexEntry", + "front": "charmeleon", + "frontLabel": "CharmeleonFrontpic", + "id": "CHARMELEON", + "name": "CHARMELEON" + }, + "CHIKORITA": { + "back": "chikorita_back", + "backLabel": "ChikoritaBackpic", + "dexLabel": "ChikoritaPokedexEntry", + "front": "chikorita", + "frontLabel": "ChikoritaFrontpic", + "id": "CHIKORITA", + "name": "CHIKORITA" + }, + "CHINCHOU": { + "back": "chinchou_back", + "backLabel": "ChinchouBackpic", + "dexLabel": "ChinchouPokedexEntry", + "front": "chinchou", + "frontLabel": "ChinchouFrontpic", + "id": "CHINCHOU", + "name": "CHINCHOU" + }, + "CLEFABLE": { + "back": "clefable_back", + "backLabel": "ClefableBackpic", + "dexLabel": "ClefablePokedexEntry", + "front": "clefable", + "frontLabel": "ClefableFrontpic", + "id": "CLEFABLE", + "name": "CLEFABLE" + }, + "CLEFAIRY": { + "back": "clefairy_back", + "backLabel": "ClefairyBackpic", + "dexLabel": "ClefairyPokedexEntry", + "front": "clefairy", + "frontLabel": "ClefairyFrontpic", + "id": "CLEFAIRY", + "name": "CLEFAIRY" + }, + "CLEFFA": { + "back": "cleffa_back", + "backLabel": "CleffaBackpic", + "dexLabel": "CleffaPokedexEntry", + "front": "cleffa", + "frontLabel": "CleffaFrontpic", + "id": "CLEFFA", + "name": "CLEFFA" + }, + "CLOYSTER": { + "back": "cloyster_back", + "backLabel": "CloysterBackpic", + "dexLabel": "CloysterPokedexEntry", + "front": "cloyster", + "frontLabel": "CloysterFrontpic", + "id": "CLOYSTER", + "name": "CLOYSTER" + }, + "CORSOLA": { + "back": "corsola_back", + "backLabel": "CorsolaBackpic", + "dexLabel": "CorsolaPokedexEntry", + "front": "corsola", + "frontLabel": "CorsolaFrontpic", + "id": "CORSOLA", + "name": "CORSOLA" + }, + "CROBAT": { + "back": "crobat_back", + "backLabel": "CrobatBackpic", + "dexLabel": "CrobatPokedexEntry", + "front": "crobat", + "frontLabel": "CrobatFrontpic", + "id": "CROBAT", + "name": "CROBAT" + }, + "CROCONAW": { + "back": "croconaw_back", + "backLabel": "CroconawBackpic", + "dexLabel": "CroconawPokedexEntry", + "front": "croconaw", + "frontLabel": "CroconawFrontpic", + "id": "CROCONAW", + "name": "CROCONAW" + }, + "CUBONE": { + "back": "cubone_back", + "backLabel": "CuboneBackpic", + "dexLabel": "CubonePokedexEntry", + "front": "cubone", + "frontLabel": "CuboneFrontpic", + "id": "CUBONE", + "name": "CUBONE" + }, + "CYNDAQUIL": { + "back": "cyndaquil_back", + "backLabel": "CyndaquilBackpic", + "dexLabel": "CyndaquilPokedexEntry", + "front": "cyndaquil", + "frontLabel": "CyndaquilFrontpic", + "id": "CYNDAQUIL", + "name": "CYNDAQUIL" + }, + "DELIBIRD": { + "back": "delibird_back", + "backLabel": "DelibirdBackpic", + "dexLabel": "DelibirdPokedexEntry", + "front": "delibird", + "frontLabel": "DelibirdFrontpic", + "id": "DELIBIRD", + "name": "DELIBIRD" + }, + "DEWGONG": { + "back": "dewgong_back", + "backLabel": "DewgongBackpic", + "dexLabel": "DewgongPokedexEntry", + "front": "dewgong", + "frontLabel": "DewgongFrontpic", + "id": "DEWGONG", + "name": "DEWGONG" + }, + "DIGLETT": { + "back": "diglett_back", + "backLabel": "DiglettBackpic", + "dexLabel": "DiglettPokedexEntry", + "front": "diglett", + "frontLabel": "DiglettFrontpic", + "id": "DIGLETT", + "name": "DIGLETT" + }, + "DITTO": { + "back": "ditto_back", + "backLabel": "DittoBackpic", + "dexLabel": "DittoPokedexEntry", + "front": "ditto", + "frontLabel": "DittoFrontpic", + "id": "DITTO", + "name": "DITTO" + }, + "DODRIO": { + "back": "dodrio_back", + "backLabel": "DodrioBackpic", + "dexLabel": "DodrioPokedexEntry", + "front": "dodrio", + "frontLabel": "DodrioFrontpic", + "id": "DODRIO", + "name": "DODRIO" + }, + "DODUO": { + "back": "doduo_back", + "backLabel": "DoduoBackpic", + "dexLabel": "DoduoPokedexEntry", + "front": "doduo", + "frontLabel": "DoduoFrontpic", + "id": "DODUO", + "name": "DODUO" + }, + "DONPHAN": { + "back": "donphan_back", + "backLabel": "DonphanBackpic", + "dexLabel": "DonphanPokedexEntry", + "front": "donphan", + "frontLabel": "DonphanFrontpic", + "id": "DONPHAN", + "name": "DONPHAN" + }, + "DRAGONAIR": { + "back": "dragonair_back", + "backLabel": "DragonairBackpic", + "dexLabel": "DragonairPokedexEntry", + "front": "dragonair", + "frontLabel": "DragonairFrontpic", + "id": "DRAGONAIR", + "name": "DRAGONAIR" + }, + "DRAGONITE": { + "back": "dragonite_back", + "backLabel": "DragoniteBackpic", + "dexLabel": "DragonitePokedexEntry", + "front": "dragonite", + "frontLabel": "DragoniteFrontpic", + "id": "DRAGONITE", + "name": "DRAGONITE" + }, + "DRATINI": { + "back": "dratini_back", + "backLabel": "DratiniBackpic", + "dexLabel": "DratiniPokedexEntry", + "front": "dratini", + "frontLabel": "DratiniFrontpic", + "id": "DRATINI", + "name": "DRATINI" + }, + "DROWZEE": { + "back": "drowzee_back", + "backLabel": "DrowzeeBackpic", + "dexLabel": "DrowzeePokedexEntry", + "front": "drowzee", + "frontLabel": "DrowzeeFrontpic", + "id": "DROWZEE", + "name": "DROWZEE" + }, + "DUGTRIO": { + "back": "dugtrio_back", + "backLabel": "DugtrioBackpic", + "dexLabel": "DugtrioPokedexEntry", + "front": "dugtrio", + "frontLabel": "DugtrioFrontpic", + "id": "DUGTRIO", + "name": "DUGTRIO" + }, + "DUNSPARCE": { + "back": "dunsparce_back", + "backLabel": "DunsparceBackpic", + "dexLabel": "DunsparcePokedexEntry", + "front": "dunsparce", + "frontLabel": "DunsparceFrontpic", + "id": "DUNSPARCE", + "name": "DUNSPARCE" + }, + "EEVEE": { + "back": "eevee_back", + "backLabel": "EeveeBackpic", + "dexLabel": "EeveePokedexEntry", + "front": "eevee", + "frontLabel": "EeveeFrontpic", + "id": "EEVEE", + "name": "EEVEE" + }, + "EKANS": { + "back": "ekans_back", + "backLabel": "EkansBackpic", + "dexLabel": "EkansPokedexEntry", + "front": "ekans", + "frontLabel": "EkansFrontpic", + "id": "EKANS", + "name": "EKANS" + }, + "ELECTABUZZ": { + "back": "electabuzz_back", + "backLabel": "ElectabuzzBackpic", + "dexLabel": "ElectabuzzPokedexEntry", + "front": "electabuzz", + "frontLabel": "ElectabuzzFrontpic", + "id": "ELECTABUZZ", + "name": "ELECTABUZZ" + }, + "ELECTRODE": { + "back": "electrode_back", + "backLabel": "ElectrodeBackpic", + "dexLabel": "ElectrodePokedexEntry", + "front": "electrode", + "frontLabel": "ElectrodeFrontpic", + "id": "ELECTRODE", + "name": "ELECTRODE" + }, + "ELEKID": { + "back": "elekid_back", + "backLabel": "ElekidBackpic", + "dexLabel": "ElekidPokedexEntry", + "front": "elekid", + "frontLabel": "ElekidFrontpic", + "id": "ELEKID", + "name": "ELEKID" + }, + "ENTEI": { + "back": "entei_back", + "backLabel": "EnteiBackpic", + "dexLabel": "EnteiPokedexEntry", + "front": "entei", + "frontLabel": "EnteiFrontpic", + "id": "ENTEI", + "name": "ENTEI" + }, + "ESPEON": { + "back": "espeon_back", + "backLabel": "EspeonBackpic", + "dexLabel": "EspeonPokedexEntry", + "front": "espeon", + "frontLabel": "EspeonFrontpic", + "id": "ESPEON", + "name": "ESPEON" + }, + "EXEGGCUTE": { + "back": "exeggcute_back", + "backLabel": "ExeggcuteBackpic", + "dexLabel": "ExeggcutePokedexEntry", + "front": "exeggcute", + "frontLabel": "ExeggcuteFrontpic", + "id": "EXEGGCUTE", + "name": "EXEGGCUTE" + }, + "EXEGGUTOR": { + "back": "exeggutor_back", + "backLabel": "ExeggutorBackpic", + "dexLabel": "ExeggutorPokedexEntry", + "front": "exeggutor", + "frontLabel": "ExeggutorFrontpic", + "id": "EXEGGUTOR", + "name": "EXEGGUTOR" + }, + "FARFETCH_D": { + "back": "farfetchd_back", + "backLabel": "FarfetchDBackpic", + "dexLabel": "FarfetchDPokedexEntry", + "front": "farfetchd", + "frontLabel": "FarfetchDFrontpic", + "id": "FARFETCH_D", + "name": "FARFETCH'D" + }, + "FEAROW": { + "back": "fearow_back", + "backLabel": "FearowBackpic", + "dexLabel": "FearowPokedexEntry", + "front": "fearow", + "frontLabel": "FearowFrontpic", + "id": "FEAROW", + "name": "FEAROW" + }, + "FERALIGATR": { + "back": "feraligatr_back", + "backLabel": "FeraligatrBackpic", + "dexLabel": "FeraligatrPokedexEntry", + "front": "feraligatr", + "frontLabel": "FeraligatrFrontpic", + "id": "FERALIGATR", + "name": "FERALIGATR" + }, + "FLAAFFY": { + "back": "flaaffy_back", + "backLabel": "FlaaffyBackpic", + "dexLabel": "FlaaffyPokedexEntry", + "front": "flaaffy", + "frontLabel": "FlaaffyFrontpic", + "id": "FLAAFFY", + "name": "FLAAFFY" + }, + "FLAREON": { + "back": "flareon_back", + "backLabel": "FlareonBackpic", + "dexLabel": "FlareonPokedexEntry", + "front": "flareon", + "frontLabel": "FlareonFrontpic", + "id": "FLAREON", + "name": "FLAREON" + }, + "FORRETRESS": { + "back": "forretress_back", + "backLabel": "ForretressBackpic", + "dexLabel": "ForretressPokedexEntry", + "front": "forretress", + "frontLabel": "ForretressFrontpic", + "id": "FORRETRESS", + "name": "FORRETRESS" + }, + "FURRET": { + "back": "furret_back", + "backLabel": "FurretBackpic", + "dexLabel": "FurretPokedexEntry", + "front": "furret", + "frontLabel": "FurretFrontpic", + "id": "FURRET", + "name": "FURRET" + }, + "GASTLY": { + "back": "gastly_back", + "backLabel": "GastlyBackpic", + "dexLabel": "GastlyPokedexEntry", + "front": "gastly", + "frontLabel": "GastlyFrontpic", + "id": "GASTLY", + "name": "GASTLY" + }, + "GENGAR": { + "back": "gengar_back", + "backLabel": "GengarBackpic", + "dexLabel": "GengarPokedexEntry", + "front": "gengar", + "frontLabel": "GengarFrontpic", + "id": "GENGAR", + "name": "GENGAR" + }, + "GEODUDE": { + "back": "geodude_back", + "backLabel": "GeodudeBackpic", + "dexLabel": "GeodudePokedexEntry", + "front": "geodude", + "frontLabel": "GeodudeFrontpic", + "id": "GEODUDE", + "name": "GEODUDE" + }, + "GIRAFARIG": { + "back": "girafarig_back", + "backLabel": "GirafarigBackpic", + "dexLabel": "GirafarigPokedexEntry", + "front": "girafarig", + "frontLabel": "GirafarigFrontpic", + "id": "GIRAFARIG", + "name": "GIRAFARIG" + }, + "GLIGAR": { + "back": "gligar_back", + "backLabel": "GligarBackpic", + "dexLabel": "GligarPokedexEntry", + "front": "gligar", + "frontLabel": "GligarFrontpic", + "id": "GLIGAR", + "name": "GLIGAR" + }, + "GLOOM": { + "back": "gloom_back", + "backLabel": "GloomBackpic", + "dexLabel": "GloomPokedexEntry", + "front": "gloom", + "frontLabel": "GloomFrontpic", + "id": "GLOOM", + "name": "GLOOM" + }, + "GOLBAT": { + "back": "golbat_back", + "backLabel": "GolbatBackpic", + "dexLabel": "GolbatPokedexEntry", + "front": "golbat", + "frontLabel": "GolbatFrontpic", + "id": "GOLBAT", + "name": "GOLBAT" + }, + "GOLDEEN": { + "back": "goldeen_back", + "backLabel": "GoldeenBackpic", + "dexLabel": "GoldeenPokedexEntry", + "front": "goldeen", + "frontLabel": "GoldeenFrontpic", + "id": "GOLDEEN", + "name": "GOLDEEN" + }, + "GOLDUCK": { + "back": "golduck_back", + "backLabel": "GolduckBackpic", + "dexLabel": "GolduckPokedexEntry", + "front": "golduck", + "frontLabel": "GolduckFrontpic", + "id": "GOLDUCK", + "name": "GOLDUCK" + }, + "GOLEM": { + "back": "golem_back", + "backLabel": "GolemBackpic", + "dexLabel": "GolemPokedexEntry", + "front": "golem", + "frontLabel": "GolemFrontpic", + "id": "GOLEM", + "name": "GOLEM" + }, + "GRANBULL": { + "back": "granbull_back", + "backLabel": "GranbullBackpic", + "dexLabel": "GranbullPokedexEntry", + "front": "granbull", + "frontLabel": "GranbullFrontpic", + "id": "GRANBULL", + "name": "GRANBULL" + }, + "GRAVELER": { + "back": "graveler_back", + "backLabel": "GravelerBackpic", + "dexLabel": "GravelerPokedexEntry", + "front": "graveler", + "frontLabel": "GravelerFrontpic", + "id": "GRAVELER", + "name": "GRAVELER" + }, + "GRIMER": { + "back": "grimer_back", + "backLabel": "GrimerBackpic", + "dexLabel": "GrimerPokedexEntry", + "front": "grimer", + "frontLabel": "GrimerFrontpic", + "id": "GRIMER", + "name": "GRIMER" + }, + "GROWLITHE": { + "back": "growlithe_back", + "backLabel": "GrowlitheBackpic", + "dexLabel": "GrowlithePokedexEntry", + "front": "growlithe", + "frontLabel": "GrowlitheFrontpic", + "id": "GROWLITHE", + "name": "GROWLITHE" + }, + "GYARADOS": { + "back": "gyarados_back", + "backLabel": "GyaradosBackpic", + "dexLabel": "GyaradosPokedexEntry", + "front": "gyarados", + "frontLabel": "GyaradosFrontpic", + "id": "GYARADOS", + "name": "GYARADOS" + }, + "HAUNTER": { + "back": "haunter_back", + "backLabel": "HaunterBackpic", + "dexLabel": "HaunterPokedexEntry", + "front": "haunter", + "frontLabel": "HaunterFrontpic", + "id": "HAUNTER", + "name": "HAUNTER" + }, + "HERACROSS": { + "back": "heracross_back", + "backLabel": "HeracrossBackpic", + "dexLabel": "HeracrossPokedexEntry", + "front": "heracross", + "frontLabel": "HeracrossFrontpic", + "id": "HERACROSS", + "name": "HERACROSS" + }, + "HITMONCHAN": { + "back": "hitmonchan_back", + "backLabel": "HitmonchanBackpic", + "dexLabel": "HitmonchanPokedexEntry", + "front": "hitmonchan", + "frontLabel": "HitmonchanFrontpic", + "id": "HITMONCHAN", + "name": "HITMONCHAN" + }, + "HITMONLEE": { + "back": "hitmonlee_back", + "backLabel": "HitmonleeBackpic", + "dexLabel": "HitmonleePokedexEntry", + "front": "hitmonlee", + "frontLabel": "HitmonleeFrontpic", + "id": "HITMONLEE", + "name": "HITMONLEE" + }, + "HITMONTOP": { + "back": "hitmontop_back", + "backLabel": "HitmontopBackpic", + "dexLabel": "HitmontopPokedexEntry", + "front": "hitmontop", + "frontLabel": "HitmontopFrontpic", + "id": "HITMONTOP", + "name": "HITMONTOP" + }, + "HOOTHOOT": { + "back": "hoothoot_back", + "backLabel": "HoothootBackpic", + "dexLabel": "HoothootPokedexEntry", + "front": "hoothoot", + "frontLabel": "HoothootFrontpic", + "id": "HOOTHOOT", + "name": "HOOTHOOT" + }, + "HOPPIP": { + "back": "hoppip_back", + "backLabel": "HoppipBackpic", + "dexLabel": "HoppipPokedexEntry", + "front": "hoppip", + "frontLabel": "HoppipFrontpic", + "id": "HOPPIP", + "name": "HOPPIP" + }, + "HORSEA": { + "back": "horsea_back", + "backLabel": "HorseaBackpic", + "dexLabel": "HorseaPokedexEntry", + "front": "horsea", + "frontLabel": "HorseaFrontpic", + "id": "HORSEA", + "name": "HORSEA" + }, + "HOUNDOOM": { + "back": "houndoom_back", + "backLabel": "HoundoomBackpic", + "dexLabel": "HoundoomPokedexEntry", + "front": "houndoom", + "frontLabel": "HoundoomFrontpic", + "id": "HOUNDOOM", + "name": "HOUNDOOM" + }, + "HOUNDOUR": { + "back": "houndour_back", + "backLabel": "HoundourBackpic", + "dexLabel": "HoundourPokedexEntry", + "front": "houndour", + "frontLabel": "HoundourFrontpic", + "id": "HOUNDOUR", + "name": "HOUNDOUR" + }, + "HO_OH": { + "back": "hooh_back", + "backLabel": "HoOhBackpic", + "dexLabel": "HoOhPokedexEntry", + "front": "hooh", + "frontLabel": "HoOhFrontpic", + "id": "HO_OH", + "name": "HO-OH" + }, + "HYPNO": { + "back": "hypno_back", + "backLabel": "HypnoBackpic", + "dexLabel": "HypnoPokedexEntry", + "front": "hypno", + "frontLabel": "HypnoFrontpic", + "id": "HYPNO", + "name": "HYPNO" + }, + "IGGLYBUFF": { + "back": "igglybuff_back", + "backLabel": "IgglybuffBackpic", + "dexLabel": "IgglybuffPokedexEntry", + "front": "igglybuff", + "frontLabel": "IgglybuffFrontpic", + "id": "IGGLYBUFF", + "name": "IGGLYBUFF" + }, + "IVYSAUR": { + "back": "ivysaur_back", + "backLabel": "IvysaurBackpic", + "dexLabel": "IvysaurPokedexEntry", + "front": "ivysaur", + "frontLabel": "IvysaurFrontpic", + "id": "IVYSAUR", + "name": "IVYSAUR" + }, + "JIGGLYPUFF": { + "back": "jigglypuff_back", + "backLabel": "JigglypuffBackpic", + "dexLabel": "JigglypuffPokedexEntry", + "front": "jigglypuff", + "frontLabel": "JigglypuffFrontpic", + "id": "JIGGLYPUFF", + "name": "JIGGLYPUFF" + }, + "JOLTEON": { + "back": "jolteon_back", + "backLabel": "JolteonBackpic", + "dexLabel": "JolteonPokedexEntry", + "front": "jolteon", + "frontLabel": "JolteonFrontpic", + "id": "JOLTEON", + "name": "JOLTEON" + }, + "JUMPLUFF": { + "back": "jumpluff_back", + "backLabel": "JumpluffBackpic", + "dexLabel": "JumpluffPokedexEntry", + "front": "jumpluff", + "frontLabel": "JumpluffFrontpic", + "id": "JUMPLUFF", + "name": "JUMPLUFF" + }, + "JYNX": { + "back": "jynx_back", + "backLabel": "JynxBackpic", + "dexLabel": "JynxPokedexEntry", + "front": "jynx", + "frontLabel": "JynxFrontpic", + "id": "JYNX", + "name": "JYNX" + }, + "KABUTO": { + "back": "kabuto_back", + "backLabel": "KabutoBackpic", + "dexLabel": "KabutoPokedexEntry", + "front": "kabuto", + "frontLabel": "KabutoFrontpic", + "id": "KABUTO", + "name": "KABUTO" + }, + "KABUTOPS": { + "back": "kabutops_back", + "backLabel": "KabutopsBackpic", + "dexLabel": "KabutopsPokedexEntry", + "front": "kabutops", + "frontLabel": "KabutopsFrontpic", + "id": "KABUTOPS", + "name": "KABUTOPS" + }, + "KADABRA": { + "back": "kadabra_back", + "backLabel": "KadabraBackpic", + "dexLabel": "KadabraPokedexEntry", + "front": "kadabra", + "frontLabel": "KadabraFrontpic", + "id": "KADABRA", + "name": "KADABRA" + }, + "KAKUNA": { + "back": "kakuna_back", + "backLabel": "KakunaBackpic", + "dexLabel": "KakunaPokedexEntry", + "front": "kakuna", + "frontLabel": "KakunaFrontpic", + "id": "KAKUNA", + "name": "KAKUNA" + }, + "KANGASKHAN": { + "back": "kangaskhan_back", + "backLabel": "KangaskhanBackpic", + "dexLabel": "KangaskhanPokedexEntry", + "front": "kangaskhan", + "frontLabel": "KangaskhanFrontpic", + "id": "KANGASKHAN", + "name": "KANGASKHAN" + }, + "KINGDRA": { + "back": "kingdra_back", + "backLabel": "KingdraBackpic", + "dexLabel": "KingdraPokedexEntry", + "front": "kingdra", + "frontLabel": "KingdraFrontpic", + "id": "KINGDRA", + "name": "KINGDRA" + }, + "KINGLER": { + "back": "kingler_back", + "backLabel": "KinglerBackpic", + "dexLabel": "KinglerPokedexEntry", + "front": "kingler", + "frontLabel": "KinglerFrontpic", + "id": "KINGLER", + "name": "KINGLER" + }, + "KOFFING": { + "back": "koffing_back", + "backLabel": "KoffingBackpic", + "dexLabel": "KoffingPokedexEntry", + "front": "koffing", + "frontLabel": "KoffingFrontpic", + "id": "KOFFING", + "name": "KOFFING" + }, + "KRABBY": { + "back": "krabby_back", + "backLabel": "KrabbyBackpic", + "dexLabel": "KrabbyPokedexEntry", + "front": "krabby", + "frontLabel": "KrabbyFrontpic", + "id": "KRABBY", + "name": "KRABBY" + }, + "LANTURN": { + "back": "lanturn_back", + "backLabel": "LanturnBackpic", + "dexLabel": "LanturnPokedexEntry", + "front": "lanturn", + "frontLabel": "LanturnFrontpic", + "id": "LANTURN", + "name": "LANTURN" + }, + "LAPRAS": { + "back": "lapras_back", + "backLabel": "LaprasBackpic", + "dexLabel": "LaprasPokedexEntry", + "front": "lapras", + "frontLabel": "LaprasFrontpic", + "id": "LAPRAS", + "name": "LAPRAS" + }, + "LARVITAR": { + "back": "larvitar_back", + "backLabel": "LarvitarBackpic", + "dexLabel": "LarvitarPokedexEntry", + "front": "larvitar", + "frontLabel": "LarvitarFrontpic", + "id": "LARVITAR", + "name": "LARVITAR" + }, + "LEDIAN": { + "back": "ledian_back", + "backLabel": "LedianBackpic", + "dexLabel": "LedianPokedexEntry", + "front": "ledian", + "frontLabel": "LedianFrontpic", + "id": "LEDIAN", + "name": "LEDIAN" + }, + "LEDYBA": { + "back": "ledyba_back", + "backLabel": "LedybaBackpic", + "dexLabel": "LedybaPokedexEntry", + "front": "ledyba", + "frontLabel": "LedybaFrontpic", + "id": "LEDYBA", + "name": "LEDYBA" + }, + "LICKITUNG": { + "back": "lickitung_back", + "backLabel": "LickitungBackpic", + "dexLabel": "LickitungPokedexEntry", + "front": "lickitung", + "frontLabel": "LickitungFrontpic", + "id": "LICKITUNG", + "name": "LICKITUNG" + }, + "LUGIA": { + "back": "lugia_back", + "backLabel": "LugiaBackpic", + "dexLabel": "LugiaPokedexEntry", + "front": "lugia", + "frontLabel": "LugiaFrontpic", + "id": "LUGIA", + "name": "LUGIA" + }, + "MACHAMP": { + "back": "machamp_back", + "backLabel": "MachampBackpic", + "dexLabel": "MachampPokedexEntry", + "front": "machamp", + "frontLabel": "MachampFrontpic", + "id": "MACHAMP", + "name": "MACHAMP" + }, + "MACHOKE": { + "back": "machoke_back", + "backLabel": "MachokeBackpic", + "dexLabel": "MachokePokedexEntry", + "front": "machoke", + "frontLabel": "MachokeFrontpic", + "id": "MACHOKE", + "name": "MACHOKE" + }, + "MACHOP": { + "back": "machop_back", + "backLabel": "MachopBackpic", + "dexLabel": "MachopPokedexEntry", + "front": "machop", + "frontLabel": "MachopFrontpic", + "id": "MACHOP", + "name": "MACHOP" + }, + "MAGBY": { + "back": "magby_back", + "backLabel": "MagbyBackpic", + "dexLabel": "MagbyPokedexEntry", + "front": "magby", + "frontLabel": "MagbyFrontpic", + "id": "MAGBY", + "name": "MAGBY" + }, + "MAGCARGO": { + "back": "magcargo_back", + "backLabel": "MagcargoBackpic", + "dexLabel": "MagcargoPokedexEntry", + "front": "magcargo", + "frontLabel": "MagcargoFrontpic", + "id": "MAGCARGO", + "name": "MAGCARGO" + }, + "MAGIKARP": { + "back": "magikarp_back", + "backLabel": "MagikarpBackpic", + "dexLabel": "MagikarpPokedexEntry", + "front": "magikarp", + "frontLabel": "MagikarpFrontpic", + "id": "MAGIKARP", + "name": "MAGIKARP" + }, + "MAGMAR": { + "back": "magmar_back", + "backLabel": "MagmarBackpic", + "dexLabel": "MagmarPokedexEntry", + "front": "magmar", + "frontLabel": "MagmarFrontpic", + "id": "MAGMAR", + "name": "MAGMAR" + }, + "MAGNEMITE": { + "back": "magnemite_back", + "backLabel": "MagnemiteBackpic", + "dexLabel": "MagnemitePokedexEntry", + "front": "magnemite", + "frontLabel": "MagnemiteFrontpic", + "id": "MAGNEMITE", + "name": "MAGNEMITE" + }, + "MAGNETON": { + "back": "magneton_back", + "backLabel": "MagnetonBackpic", + "dexLabel": "MagnetonPokedexEntry", + "front": "magneton", + "frontLabel": "MagnetonFrontpic", + "id": "MAGNETON", + "name": "MAGNETON" + }, + "MANKEY": { + "back": "mankey_back", + "backLabel": "MankeyBackpic", + "dexLabel": "MankeyPokedexEntry", + "front": "mankey", + "frontLabel": "MankeyFrontpic", + "id": "MANKEY", + "name": "MANKEY" + }, + "MANTINE": { + "back": "mantine_back", + "backLabel": "MantineBackpic", + "dexLabel": "MantinePokedexEntry", + "front": "mantine", + "frontLabel": "MantineFrontpic", + "id": "MANTINE", + "name": "MANTINE" + }, + "MAREEP": { + "back": "mareep_back", + "backLabel": "MareepBackpic", + "dexLabel": "MareepPokedexEntry", + "front": "mareep", + "frontLabel": "MareepFrontpic", + "id": "MAREEP", + "name": "MAREEP" + }, + "MARILL": { + "back": "marill_back", + "backLabel": "MarillBackpic", + "dexLabel": "MarillPokedexEntry", + "front": "marill", + "frontLabel": "MarillFrontpic", + "id": "MARILL", + "name": "MARILL" + }, + "MAROWAK": { + "back": "marowak_back", + "backLabel": "MarowakBackpic", + "dexLabel": "MarowakPokedexEntry", + "front": "marowak", + "frontLabel": "MarowakFrontpic", + "id": "MAROWAK", + "name": "MAROWAK" + }, + "MEGANIUM": { + "back": "meganium_back", + "backLabel": "MeganiumBackpic", + "dexLabel": "MeganiumPokedexEntry", + "front": "meganium", + "frontLabel": "MeganiumFrontpic", + "id": "MEGANIUM", + "name": "MEGANIUM" + }, + "MEOWTH": { + "back": "meowth_back", + "backLabel": "MeowthBackpic", + "dexLabel": "MeowthPokedexEntry", + "front": "meowth", + "frontLabel": "MeowthFrontpic", + "id": "MEOWTH", + "name": "MEOWTH" + }, + "METAPOD": { + "back": "metapod_back", + "backLabel": "MetapodBackpic", + "dexLabel": "MetapodPokedexEntry", + "front": "metapod", + "frontLabel": "MetapodFrontpic", + "id": "METAPOD", + "name": "METAPOD" + }, + "MEW": { + "back": "mew_back", + "backLabel": "MewBackpic", + "dexLabel": "MewPokedexEntry", + "front": "mew", + "frontLabel": "MewFrontpic", + "id": "MEW", + "name": "MEW" + }, + "MEWTWO": { + "back": "mewtwo_back", + "backLabel": "MewtwoBackpic", + "dexLabel": "MewtwoPokedexEntry", + "front": "mewtwo", + "frontLabel": "MewtwoFrontpic", + "id": "MEWTWO", + "name": "MEWTWO" + }, + "MILTANK": { + "back": "miltank_back", + "backLabel": "MiltankBackpic", + "dexLabel": "MiltankPokedexEntry", + "front": "miltank", + "frontLabel": "MiltankFrontpic", + "id": "MILTANK", + "name": "MILTANK" + }, + "MISDREAVUS": { + "back": "misdreavus_back", + "backLabel": "MisdreavusBackpic", + "dexLabel": "MisdreavusPokedexEntry", + "front": "misdreavus", + "frontLabel": "MisdreavusFrontpic", + "id": "MISDREAVUS", + "name": "MISDREAVUS" + }, + "MOLTRES": { + "back": "moltres_back", + "backLabel": "MoltresBackpic", + "dexLabel": "MoltresPokedexEntry", + "front": "moltres", + "frontLabel": "MoltresFrontpic", + "id": "MOLTRES", + "name": "MOLTRES" + }, + "MR__MIME": { + "back": "mrmime_back", + "backLabel": "MrMimeBackpic", + "dexLabel": "MrMimePokedexEntry", + "front": "mrmime", + "frontLabel": "MrMimeFrontpic", + "id": "MR__MIME", + "name": "MR.MIME" + }, + "MUK": { + "back": "muk_back", + "backLabel": "MukBackpic", + "dexLabel": "MukPokedexEntry", + "front": "muk", + "frontLabel": "MukFrontpic", + "id": "MUK", + "name": "MUK" + }, + "MURKROW": { + "back": "murkrow_back", + "backLabel": "MurkrowBackpic", + "dexLabel": "MurkrowPokedexEntry", + "front": "murkrow", + "frontLabel": "MurkrowFrontpic", + "id": "MURKROW", + "name": "MURKROW" + }, + "NATU": { + "back": "natu_back", + "backLabel": "NatuBackpic", + "dexLabel": "NatuPokedexEntry", + "front": "natu", + "frontLabel": "NatuFrontpic", + "id": "NATU", + "name": "NATU" + }, + "NIDOKING": { + "back": "nidoking_back", + "backLabel": "NidokingBackpic", + "dexLabel": "NidokingPokedexEntry", + "front": "nidoking", + "frontLabel": "NidokingFrontpic", + "id": "NIDOKING", + "name": "NIDOKING" + }, + "NIDOQUEEN": { + "back": "nidoqueen_back", + "backLabel": "NidoqueenBackpic", + "dexLabel": "NidoqueenPokedexEntry", + "front": "nidoqueen", + "frontLabel": "NidoqueenFrontpic", + "id": "NIDOQUEEN", + "name": "NIDOQUEEN" + }, + "NIDORAN_F": { + "back": "nidoranf_back", + "backLabel": "NidoranFBackpic", + "dexLabel": "NidoranFPokedexEntry", + "front": "nidoranf", + "frontLabel": "NidoranFFrontpic", + "id": "NIDORAN_F", + "name": "NIDORAN♀" + }, + "NIDORAN_M": { + "back": "nidoranm_back", + "backLabel": "NidoranMBackpic", + "dexLabel": "NidoranMPokedexEntry", + "front": "nidoranm", + "frontLabel": "NidoranMFrontpic", + "id": "NIDORAN_M", + "name": "NIDORAN♂" + }, + "NIDORINA": { + "back": "nidorina_back", + "backLabel": "NidorinaBackpic", + "dexLabel": "NidorinaPokedexEntry", + "front": "nidorina", + "frontLabel": "NidorinaFrontpic", + "id": "NIDORINA", + "name": "NIDORINA" + }, + "NIDORINO": { + "back": "nidorino_back", + "backLabel": "NidorinoBackpic", + "dexLabel": "NidorinoPokedexEntry", + "front": "nidorino", + "frontLabel": "NidorinoFrontpic", + "id": "NIDORINO", + "name": "NIDORINO" + }, + "NINETALES": { + "back": "ninetales_back", + "backLabel": "NinetalesBackpic", + "dexLabel": "NinetalesPokedexEntry", + "front": "ninetales", + "frontLabel": "NinetalesFrontpic", + "id": "NINETALES", + "name": "NINETALES" + }, + "NOCTOWL": { + "back": "noctowl_back", + "backLabel": "NoctowlBackpic", + "dexLabel": "NoctowlPokedexEntry", + "front": "noctowl", + "frontLabel": "NoctowlFrontpic", + "id": "NOCTOWL", + "name": "NOCTOWL" + }, + "OCTILLERY": { + "back": "octillery_back", + "backLabel": "OctilleryBackpic", + "dexLabel": "OctilleryPokedexEntry", + "front": "octillery", + "frontLabel": "OctilleryFrontpic", + "id": "OCTILLERY", + "name": "OCTILLERY" + }, + "ODDISH": { + "back": "oddish_back", + "backLabel": "OddishBackpic", + "dexLabel": "OddishPokedexEntry", + "front": "oddish", + "frontLabel": "OddishFrontpic", + "id": "ODDISH", + "name": "ODDISH" + }, + "OMANYTE": { + "back": "omanyte_back", + "backLabel": "OmanyteBackpic", + "dexLabel": "OmanytePokedexEntry", + "front": "omanyte", + "frontLabel": "OmanyteFrontpic", + "id": "OMANYTE", + "name": "OMANYTE" + }, + "OMASTAR": { + "back": "omastar_back", + "backLabel": "OmastarBackpic", + "dexLabel": "OmastarPokedexEntry", + "front": "omastar", + "frontLabel": "OmastarFrontpic", + "id": "OMASTAR", + "name": "OMASTAR" + }, + "ONIX": { + "back": "onix_back", + "backLabel": "OnixBackpic", + "dexLabel": "OnixPokedexEntry", + "front": "onix", + "frontLabel": "OnixFrontpic", + "id": "ONIX", + "name": "ONIX" + }, + "PARAS": { + "back": "paras_back", + "backLabel": "ParasBackpic", + "dexLabel": "ParasPokedexEntry", + "front": "paras", + "frontLabel": "ParasFrontpic", + "id": "PARAS", + "name": "PARAS" + }, + "PARASECT": { + "back": "parasect_back", + "backLabel": "ParasectBackpic", + "dexLabel": "ParasectPokedexEntry", + "front": "parasect", + "frontLabel": "ParasectFrontpic", + "id": "PARASECT", + "name": "PARASECT" + }, + "PERSIAN": { + "back": "persian_back", + "backLabel": "PersianBackpic", + "dexLabel": "PersianPokedexEntry", + "front": "persian", + "frontLabel": "PersianFrontpic", + "id": "PERSIAN", + "name": "PERSIAN" + }, + "PHANPY": { + "back": "phanpy_back", + "backLabel": "PhanpyBackpic", + "dexLabel": "PhanpyPokedexEntry", + "front": "phanpy", + "frontLabel": "PhanpyFrontpic", + "id": "PHANPY", + "name": "PHANPY" + }, + "PICHU": { + "back": "pichu_back", + "backLabel": "PichuBackpic", + "dexLabel": "PichuPokedexEntry", + "front": "pichu", + "frontLabel": "PichuFrontpic", + "id": "PICHU", + "name": "PICHU" + }, + "PIDGEOT": { + "back": "pidgeot_back", + "backLabel": "PidgeotBackpic", + "dexLabel": "PidgeotPokedexEntry", + "front": "pidgeot", + "frontLabel": "PidgeotFrontpic", + "id": "PIDGEOT", + "name": "PIDGEOT" + }, + "PIDGEOTTO": { + "back": "pidgeotto_back", + "backLabel": "PidgeottoBackpic", + "dexLabel": "PidgeottoPokedexEntry", + "front": "pidgeotto", + "frontLabel": "PidgeottoFrontpic", + "id": "PIDGEOTTO", + "name": "PIDGEOTTO" + }, + "PIDGEY": { + "back": "pidgey_back", + "backLabel": "PidgeyBackpic", + "dexLabel": "PidgeyPokedexEntry", + "front": "pidgey", + "frontLabel": "PidgeyFrontpic", + "id": "PIDGEY", + "name": "PIDGEY" + }, + "PIKACHU": { + "back": "pikachu_back", + "backLabel": "PikachuBackpic", + "dexLabel": "PikachuPokedexEntry", + "front": "pikachu", + "frontLabel": "PikachuFrontpic", + "id": "PIKACHU", + "name": "PIKACHU" + }, + "PILOSWINE": { + "back": "piloswine_back", + "backLabel": "PiloswineBackpic", + "dexLabel": "PiloswinePokedexEntry", + "front": "piloswine", + "frontLabel": "PiloswineFrontpic", + "id": "PILOSWINE", + "name": "PILOSWINE" + }, + "PINECO": { + "back": "pineco_back", + "backLabel": "PinecoBackpic", + "dexLabel": "PinecoPokedexEntry", + "front": "pineco", + "frontLabel": "PinecoFrontpic", + "id": "PINECO", + "name": "PINECO" + }, + "PINSIR": { + "back": "pinsir_back", + "backLabel": "PinsirBackpic", + "dexLabel": "PinsirPokedexEntry", + "front": "pinsir", + "frontLabel": "PinsirFrontpic", + "id": "PINSIR", + "name": "PINSIR" + }, + "POLITOED": { + "back": "politoed_back", + "backLabel": "PolitoedBackpic", + "dexLabel": "PolitoedPokedexEntry", + "front": "politoed", + "frontLabel": "PolitoedFrontpic", + "id": "POLITOED", + "name": "POLITOED" + }, + "POLIWAG": { + "back": "poliwag_back", + "backLabel": "PoliwagBackpic", + "dexLabel": "PoliwagPokedexEntry", + "front": "poliwag", + "frontLabel": "PoliwagFrontpic", + "id": "POLIWAG", + "name": "POLIWAG" + }, + "POLIWHIRL": { + "back": "poliwhirl_back", + "backLabel": "PoliwhirlBackpic", + "dexLabel": "PoliwhirlPokedexEntry", + "front": "poliwhirl", + "frontLabel": "PoliwhirlFrontpic", + "id": "POLIWHIRL", + "name": "POLIWHIRL" + }, + "POLIWRATH": { + "back": "poliwrath_back", + "backLabel": "PoliwrathBackpic", + "dexLabel": "PoliwrathPokedexEntry", + "front": "poliwrath", + "frontLabel": "PoliwrathFrontpic", + "id": "POLIWRATH", + "name": "POLIWRATH" + }, + "PONYTA": { + "back": "ponyta_back", + "backLabel": "PonytaBackpic", + "dexLabel": "PonytaPokedexEntry", + "front": "ponyta", + "frontLabel": "PonytaFrontpic", + "id": "PONYTA", + "name": "PONYTA" + }, + "PORYGON": { + "back": "porygon_back", + "backLabel": "PorygonBackpic", + "dexLabel": "PorygonPokedexEntry", + "front": "porygon", + "frontLabel": "PorygonFrontpic", + "id": "PORYGON", + "name": "PORYGON" + }, + "PORYGON2": { + "back": "porygon2_back", + "backLabel": "Porygon2Backpic", + "dexLabel": "Porygon2PokedexEntry", + "front": "porygon2", + "frontLabel": "Porygon2Frontpic", + "id": "PORYGON2", + "name": "PORYGON2" + }, + "PRIMEAPE": { + "back": "primeape_back", + "backLabel": "PrimeapeBackpic", + "dexLabel": "PrimeapePokedexEntry", + "front": "primeape", + "frontLabel": "PrimeapeFrontpic", + "id": "PRIMEAPE", + "name": "PRIMEAPE" + }, + "PSYDUCK": { + "back": "psyduck_back", + "backLabel": "PsyduckBackpic", + "dexLabel": "PsyduckPokedexEntry", + "front": "psyduck", + "frontLabel": "PsyduckFrontpic", + "id": "PSYDUCK", + "name": "PSYDUCK" + }, + "PUPITAR": { + "back": "pupitar_back", + "backLabel": "PupitarBackpic", + "dexLabel": "PupitarPokedexEntry", + "front": "pupitar", + "frontLabel": "PupitarFrontpic", + "id": "PUPITAR", + "name": "PUPITAR" + }, + "QUAGSIRE": { + "back": "quagsire_back", + "backLabel": "QuagsireBackpic", + "dexLabel": "QuagsirePokedexEntry", + "front": "quagsire", + "frontLabel": "QuagsireFrontpic", + "id": "QUAGSIRE", + "name": "QUAGSIRE" + }, + "QUILAVA": { + "back": "quilava_back", + "backLabel": "QuilavaBackpic", + "dexLabel": "QuilavaPokedexEntry", + "front": "quilava", + "frontLabel": "QuilavaFrontpic", + "id": "QUILAVA", + "name": "QUILAVA" + }, + "QWILFISH": { + "back": "qwilfish_back", + "backLabel": "QwilfishBackpic", + "dexLabel": "QwilfishPokedexEntry", + "front": "qwilfish", + "frontLabel": "QwilfishFrontpic", + "id": "QWILFISH", + "name": "QWILFISH" + }, + "RAICHU": { + "back": "raichu_back", + "backLabel": "RaichuBackpic", + "dexLabel": "RaichuPokedexEntry", + "front": "raichu", + "frontLabel": "RaichuFrontpic", + "id": "RAICHU", + "name": "RAICHU" + }, + "RAIKOU": { + "back": "raikou_back", + "backLabel": "RaikouBackpic", + "dexLabel": "RaikouPokedexEntry", + "front": "raikou", + "frontLabel": "RaikouFrontpic", + "id": "RAIKOU", + "name": "RAIKOU" + }, + "RAPIDASH": { + "back": "rapidash_back", + "backLabel": "RapidashBackpic", + "dexLabel": "RapidashPokedexEntry", + "front": "rapidash", + "frontLabel": "RapidashFrontpic", + "id": "RAPIDASH", + "name": "RAPIDASH" + }, + "RATICATE": { + "back": "raticate_back", + "backLabel": "RaticateBackpic", + "dexLabel": "RaticatePokedexEntry", + "front": "raticate", + "frontLabel": "RaticateFrontpic", + "id": "RATICATE", + "name": "RATICATE" + }, + "RATTATA": { + "back": "rattata_back", + "backLabel": "RattataBackpic", + "dexLabel": "RattataPokedexEntry", + "front": "rattata", + "frontLabel": "RattataFrontpic", + "id": "RATTATA", + "name": "RATTATA" + }, + "REMORAID": { + "back": "remoraid_back", + "backLabel": "RemoraidBackpic", + "dexLabel": "RemoraidPokedexEntry", + "front": "remoraid", + "frontLabel": "RemoraidFrontpic", + "id": "REMORAID", + "name": "REMORAID" + }, + "RHYDON": { + "back": "rhydon_back", + "backLabel": "RhydonBackpic", + "dexLabel": "RhydonPokedexEntry", + "front": "rhydon", + "frontLabel": "RhydonFrontpic", + "id": "RHYDON", + "name": "RHYDON" + }, + "RHYHORN": { + "back": "rhyhorn_back", + "backLabel": "RhyhornBackpic", + "dexLabel": "RhyhornPokedexEntry", + "front": "rhyhorn", + "frontLabel": "RhyhornFrontpic", + "id": "RHYHORN", + "name": "RHYHORN" + }, + "SANDSHREW": { + "back": "sandshrew_back", + "backLabel": "SandshrewBackpic", + "dexLabel": "SandshrewPokedexEntry", + "front": "sandshrew", + "frontLabel": "SandshrewFrontpic", + "id": "SANDSHREW", + "name": "SANDSHREW" + }, + "SANDSLASH": { + "back": "sandslash_back", + "backLabel": "SandslashBackpic", + "dexLabel": "SandslashPokedexEntry", + "front": "sandslash", + "frontLabel": "SandslashFrontpic", + "id": "SANDSLASH", + "name": "SANDSLASH" + }, + "SCIZOR": { + "back": "scizor_back", + "backLabel": "ScizorBackpic", + "dexLabel": "ScizorPokedexEntry", + "front": "scizor", + "frontLabel": "ScizorFrontpic", + "id": "SCIZOR", + "name": "SCIZOR" + }, + "SCYTHER": { + "back": "scyther_back", + "backLabel": "ScytherBackpic", + "dexLabel": "ScytherPokedexEntry", + "front": "scyther", + "frontLabel": "ScytherFrontpic", + "id": "SCYTHER", + "name": "SCYTHER" + }, + "SEADRA": { + "back": "seadra_back", + "backLabel": "SeadraBackpic", + "dexLabel": "SeadraPokedexEntry", + "front": "seadra", + "frontLabel": "SeadraFrontpic", + "id": "SEADRA", + "name": "SEADRA" + }, + "SEAKING": { + "back": "seaking_back", + "backLabel": "SeakingBackpic", + "dexLabel": "SeakingPokedexEntry", + "front": "seaking", + "frontLabel": "SeakingFrontpic", + "id": "SEAKING", + "name": "SEAKING" + }, + "SEEL": { + "back": "seel_back", + "backLabel": "SeelBackpic", + "dexLabel": "SeelPokedexEntry", + "front": "seel", + "frontLabel": "SeelFrontpic", + "id": "SEEL", + "name": "SEEL" + }, + "SENTRET": { + "back": "sentret_back", + "backLabel": "SentretBackpic", + "dexLabel": "SentretPokedexEntry", + "front": "sentret", + "frontLabel": "SentretFrontpic", + "id": "SENTRET", + "name": "SENTRET" + }, + "SHELLDER": { + "back": "shellder_back", + "backLabel": "ShellderBackpic", + "dexLabel": "ShellderPokedexEntry", + "front": "shellder", + "frontLabel": "ShellderFrontpic", + "id": "SHELLDER", + "name": "SHELLDER" + }, + "SHUCKLE": { + "back": "shuckle_back", + "backLabel": "ShuckleBackpic", + "dexLabel": "ShucklePokedexEntry", + "front": "shuckle", + "frontLabel": "ShuckleFrontpic", + "id": "SHUCKLE", + "name": "SHUCKLE" + }, + "SKARMORY": { + "back": "skarmory_back", + "backLabel": "SkarmoryBackpic", + "dexLabel": "SkarmoryPokedexEntry", + "front": "skarmory", + "frontLabel": "SkarmoryFrontpic", + "id": "SKARMORY", + "name": "SKARMORY" + }, + "SKIPLOOM": { + "back": "skiploom_back", + "backLabel": "SkiploomBackpic", + "dexLabel": "SkiploomPokedexEntry", + "front": "skiploom", + "frontLabel": "SkiploomFrontpic", + "id": "SKIPLOOM", + "name": "SKIPLOOM" + }, + "SLOWBRO": { + "back": "slowbro_back", + "backLabel": "SlowbroBackpic", + "dexLabel": "SlowbroPokedexEntry", + "front": "slowbro", + "frontLabel": "SlowbroFrontpic", + "id": "SLOWBRO", + "name": "SLOWBRO" + }, + "SLOWKING": { + "back": "slowking_back", + "backLabel": "SlowkingBackpic", + "dexLabel": "SlowkingPokedexEntry", + "front": "slowking", + "frontLabel": "SlowkingFrontpic", + "id": "SLOWKING", + "name": "SLOWKING" + }, + "SLOWPOKE": { + "back": "slowpoke_back", + "backLabel": "SlowpokeBackpic", + "dexLabel": "SlowpokePokedexEntry", + "front": "slowpoke", + "frontLabel": "SlowpokeFrontpic", + "id": "SLOWPOKE", + "name": "SLOWPOKE" + }, + "SLUGMA": { + "back": "slugma_back", + "backLabel": "SlugmaBackpic", + "dexLabel": "SlugmaPokedexEntry", + "front": "slugma", + "frontLabel": "SlugmaFrontpic", + "id": "SLUGMA", + "name": "SLUGMA" + }, + "SMEARGLE": { + "back": "smeargle_back", + "backLabel": "SmeargleBackpic", + "dexLabel": "SmearglePokedexEntry", + "front": "smeargle", + "frontLabel": "SmeargleFrontpic", + "id": "SMEARGLE", + "name": "SMEARGLE" + }, + "SMOOCHUM": { + "back": "smoochum_back", + "backLabel": "SmoochumBackpic", + "dexLabel": "SmoochumPokedexEntry", + "front": "smoochum", + "frontLabel": "SmoochumFrontpic", + "id": "SMOOCHUM", + "name": "SMOOCHUM" + }, + "SNEASEL": { + "back": "sneasel_back", + "backLabel": "SneaselBackpic", + "dexLabel": "SneaselPokedexEntry", + "front": "sneasel", + "frontLabel": "SneaselFrontpic", + "id": "SNEASEL", + "name": "SNEASEL" + }, + "SNORLAX": { + "back": "snorlax_back", + "backLabel": "SnorlaxBackpic", + "dexLabel": "SnorlaxPokedexEntry", + "front": "snorlax", + "frontLabel": "SnorlaxFrontpic", + "id": "SNORLAX", + "name": "SNORLAX" + }, + "SNUBBULL": { + "back": "snubbull_back", + "backLabel": "SnubbullBackpic", + "dexLabel": "SnubbullPokedexEntry", + "front": "snubbull", + "frontLabel": "SnubbullFrontpic", + "id": "SNUBBULL", + "name": "SNUBBULL" + }, + "SPEAROW": { + "back": "spearow_back", + "backLabel": "SpearowBackpic", + "dexLabel": "SpearowPokedexEntry", + "front": "spearow", + "frontLabel": "SpearowFrontpic", + "id": "SPEAROW", + "name": "SPEAROW" + }, + "SPINARAK": { + "back": "spinarak_back", + "backLabel": "SpinarakBackpic", + "dexLabel": "SpinarakPokedexEntry", + "front": "spinarak", + "frontLabel": "SpinarakFrontpic", + "id": "SPINARAK", + "name": "SPINARAK" + }, + "SQUIRTLE": { + "back": "squirtle_back", + "backLabel": "SquirtleBackpic", + "dexLabel": "SquirtlePokedexEntry", + "front": "squirtle", + "frontLabel": "SquirtleFrontpic", + "id": "SQUIRTLE", + "name": "SQUIRTLE" + }, + "STANTLER": { + "back": "stantler_back", + "backLabel": "StantlerBackpic", + "dexLabel": "StantlerPokedexEntry", + "front": "stantler", + "frontLabel": "StantlerFrontpic", + "id": "STANTLER", + "name": "STANTLER" + }, + "STARMIE": { + "back": "starmie_back", + "backLabel": "StarmieBackpic", + "dexLabel": "StarmiePokedexEntry", + "front": "starmie", + "frontLabel": "StarmieFrontpic", + "id": "STARMIE", + "name": "STARMIE" + }, + "STARYU": { + "back": "staryu_back", + "backLabel": "StaryuBackpic", + "dexLabel": "StaryuPokedexEntry", + "front": "staryu", + "frontLabel": "StaryuFrontpic", + "id": "STARYU", + "name": "STARYU" + }, + "STEELIX": { + "back": "steelix_back", + "backLabel": "SteelixBackpic", + "dexLabel": "SteelixPokedexEntry", + "front": "steelix", + "frontLabel": "SteelixFrontpic", + "id": "STEELIX", + "name": "STEELIX" + }, + "SUDOWOODO": { + "back": "sudowoodo_back", + "backLabel": "SudowoodoBackpic", + "dexLabel": "SudowoodoPokedexEntry", + "front": "sudowoodo", + "frontLabel": "SudowoodoFrontpic", + "id": "SUDOWOODO", + "name": "SUDOWOODO" + }, + "SUICUNE": { + "back": "suicune_back", + "backLabel": "SuicuneBackpic", + "dexLabel": "SuicunePokedexEntry", + "front": "suicune", + "frontLabel": "SuicuneFrontpic", + "id": "SUICUNE", + "name": "SUICUNE" + }, + "SUNFLORA": { + "back": "sunflora_back", + "backLabel": "SunfloraBackpic", + "dexLabel": "SunfloraPokedexEntry", + "front": "sunflora", + "frontLabel": "SunfloraFrontpic", + "id": "SUNFLORA", + "name": "SUNFLORA" + }, + "SUNKERN": { + "back": "sunkern_back", + "backLabel": "SunkernBackpic", + "dexLabel": "SunkernPokedexEntry", + "front": "sunkern", + "frontLabel": "SunkernFrontpic", + "id": "SUNKERN", + "name": "SUNKERN" + }, + "SWINUB": { + "back": "swinub_back", + "backLabel": "SwinubBackpic", + "dexLabel": "SwinubPokedexEntry", + "front": "swinub", + "frontLabel": "SwinubFrontpic", + "id": "SWINUB", + "name": "SWINUB" + }, + "TANGELA": { + "back": "tangela_back", + "backLabel": "TangelaBackpic", + "dexLabel": "TangelaPokedexEntry", + "front": "tangela", + "frontLabel": "TangelaFrontpic", + "id": "TANGELA", + "name": "TANGELA" + }, + "TAUROS": { + "back": "tauros_back", + "backLabel": "TaurosBackpic", + "dexLabel": "TaurosPokedexEntry", + "front": "tauros", + "frontLabel": "TaurosFrontpic", + "id": "TAUROS", + "name": "TAUROS" + }, + "TEDDIURSA": { + "back": "teddiursa_back", + "backLabel": "TeddiursaBackpic", + "dexLabel": "TeddiursaPokedexEntry", + "front": "teddiursa", + "frontLabel": "TeddiursaFrontpic", + "id": "TEDDIURSA", + "name": "TEDDIURSA" + }, + "TENTACOOL": { + "back": "tentacool_back", + "backLabel": "TentacoolBackpic", + "dexLabel": "TentacoolPokedexEntry", + "front": "tentacool", + "frontLabel": "TentacoolFrontpic", + "id": "TENTACOOL", + "name": "TENTACOOL" + }, + "TENTACRUEL": { + "back": "tentacruel_back", + "backLabel": "TentacruelBackpic", + "dexLabel": "TentacruelPokedexEntry", + "front": "tentacruel", + "frontLabel": "TentacruelFrontpic", + "id": "TENTACRUEL", + "name": "TENTACRUEL" + }, + "TOGEPI": { + "back": "togepi_back", + "backLabel": "TogepiBackpic", + "dexLabel": "TogepiPokedexEntry", + "front": "togepi", + "frontLabel": "TogepiFrontpic", + "id": "TOGEPI", + "name": "TOGEPI" + }, + "TOGETIC": { + "back": "togetic_back", + "backLabel": "TogeticBackpic", + "dexLabel": "TogeticPokedexEntry", + "front": "togetic", + "frontLabel": "TogeticFrontpic", + "id": "TOGETIC", + "name": "TOGETIC" + }, + "TOTODILE": { + "back": "totodile_back", + "backLabel": "TotodileBackpic", + "dexLabel": "TotodilePokedexEntry", + "front": "totodile", + "frontLabel": "TotodileFrontpic", + "id": "TOTODILE", + "name": "TOTODILE" + }, + "TYPHLOSION": { + "back": "typhlosion_back", + "backLabel": "TyphlosionBackpic", + "dexLabel": "TyphlosionPokedexEntry", + "front": "typhlosion", + "frontLabel": "TyphlosionFrontpic", + "id": "TYPHLOSION", + "name": "TYPHLOSION" + }, + "TYRANITAR": { + "back": "tyranitar_back", + "backLabel": "TyranitarBackpic", + "dexLabel": "TyranitarPokedexEntry", + "front": "tyranitar", + "frontLabel": "TyranitarFrontpic", + "id": "TYRANITAR", + "name": "TYRANITAR" + }, + "TYROGUE": { + "back": "tyrogue_back", + "backLabel": "TyrogueBackpic", + "dexLabel": "TyroguePokedexEntry", + "front": "tyrogue", + "frontLabel": "TyrogueFrontpic", + "id": "TYROGUE", + "name": "TYROGUE" + }, + "UMBREON": { + "back": "umbreon_back", + "backLabel": "UmbreonBackpic", + "dexLabel": "UmbreonPokedexEntry", + "front": "umbreon", + "frontLabel": "UmbreonFrontpic", + "id": "UMBREON", + "name": "UMBREON" + }, + "UNOWN": { + "back": null, + "backLabel": null, + "dexLabel": "UnownPokedexEntry", + "front": null, + "frontLabel": null, + "id": "UNOWN", + "name": "UNOWN" + }, + "URSARING": { + "back": "ursaring_back", + "backLabel": "UrsaringBackpic", + "dexLabel": "UrsaringPokedexEntry", + "front": "ursaring", + "frontLabel": "UrsaringFrontpic", + "id": "URSARING", + "name": "URSARING" + }, + "VAPOREON": { + "back": "vaporeon_back", + "backLabel": "VaporeonBackpic", + "dexLabel": "VaporeonPokedexEntry", + "front": "vaporeon", + "frontLabel": "VaporeonFrontpic", + "id": "VAPOREON", + "name": "VAPOREON" + }, + "VENOMOTH": { + "back": "venomoth_back", + "backLabel": "VenomothBackpic", + "dexLabel": "VenomothPokedexEntry", + "front": "venomoth", + "frontLabel": "VenomothFrontpic", + "id": "VENOMOTH", + "name": "VENOMOTH" + }, + "VENONAT": { + "back": "venonat_back", + "backLabel": "VenonatBackpic", + "dexLabel": "VenonatPokedexEntry", + "front": "venonat", + "frontLabel": "VenonatFrontpic", + "id": "VENONAT", + "name": "VENONAT" + }, + "VENUSAUR": { + "back": "venusaur_back", + "backLabel": "VenusaurBackpic", + "dexLabel": "VenusaurPokedexEntry", + "front": "venusaur", + "frontLabel": "VenusaurFrontpic", + "id": "VENUSAUR", + "name": "VENUSAUR" + }, + "VICTREEBEL": { + "back": "victreebel_back", + "backLabel": "VictreebelBackpic", + "dexLabel": "VictreebelPokedexEntry", + "front": "victreebel", + "frontLabel": "VictreebelFrontpic", + "id": "VICTREEBEL", + "name": "VICTREEBEL" + }, + "VILEPLUME": { + "back": "vileplume_back", + "backLabel": "VileplumeBackpic", + "dexLabel": "VileplumePokedexEntry", + "front": "vileplume", + "frontLabel": "VileplumeFrontpic", + "id": "VILEPLUME", + "name": "VILEPLUME" + }, + "VOLTORB": { + "back": "voltorb_back", + "backLabel": "VoltorbBackpic", + "dexLabel": "VoltorbPokedexEntry", + "front": "voltorb", + "frontLabel": "VoltorbFrontpic", + "id": "VOLTORB", + "name": "VOLTORB" + }, + "VULPIX": { + "back": "vulpix_back", + "backLabel": "VulpixBackpic", + "dexLabel": "VulpixPokedexEntry", + "front": "vulpix", + "frontLabel": "VulpixFrontpic", + "id": "VULPIX", + "name": "VULPIX" + }, + "WARTORTLE": { + "back": "wartortle_back", + "backLabel": "WartortleBackpic", + "dexLabel": "WartortlePokedexEntry", + "front": "wartortle", + "frontLabel": "WartortleFrontpic", + "id": "WARTORTLE", + "name": "WARTORTLE" + }, + "WEEDLE": { + "back": "weedle_back", + "backLabel": "WeedleBackpic", + "dexLabel": "WeedlePokedexEntry", + "front": "weedle", + "frontLabel": "WeedleFrontpic", + "id": "WEEDLE", + "name": "WEEDLE" + }, + "WEEPINBELL": { + "back": "weepinbell_back", + "backLabel": "WeepinbellBackpic", + "dexLabel": "WeepinbellPokedexEntry", + "front": "weepinbell", + "frontLabel": "WeepinbellFrontpic", + "id": "WEEPINBELL", + "name": "WEEPINBELL" + }, + "WEEZING": { + "back": "weezing_back", + "backLabel": "WeezingBackpic", + "dexLabel": "WeezingPokedexEntry", + "front": "weezing", + "frontLabel": "WeezingFrontpic", + "id": "WEEZING", + "name": "WEEZING" + }, + "WIGGLYTUFF": { + "back": "wigglytuff_back", + "backLabel": "WigglytuffBackpic", + "dexLabel": "WigglytuffPokedexEntry", + "front": "wigglytuff", + "frontLabel": "WigglytuffFrontpic", + "id": "WIGGLYTUFF", + "name": "WIGGLYTUFF" + }, + "WOBBUFFET": { + "back": "wobbuffet_back", + "backLabel": "WobbuffetBackpic", + "dexLabel": "WobbuffetPokedexEntry", + "front": "wobbuffet", + "frontLabel": "WobbuffetFrontpic", + "id": "WOBBUFFET", + "name": "WOBBUFFET" + }, + "WOOPER": { + "back": "wooper_back", + "backLabel": "WooperBackpic", + "dexLabel": "WooperPokedexEntry", + "front": "wooper", + "frontLabel": "WooperFrontpic", + "id": "WOOPER", + "name": "WOOPER" + }, + "XATU": { + "back": "xatu_back", + "backLabel": "XatuBackpic", + "dexLabel": "XatuPokedexEntry", + "front": "xatu", + "frontLabel": "XatuFrontpic", + "id": "XATU", + "name": "XATU" + }, + "YANMA": { + "back": "yanma_back", + "backLabel": "YanmaBackpic", + "dexLabel": "YanmaPokedexEntry", + "front": "yanma", + "frontLabel": "YanmaFrontpic", + "id": "YANMA", + "name": "YANMA" + }, + "ZAPDOS": { + "back": "zapdos_back", + "backLabel": "ZapdosBackpic", + "dexLabel": "ZapdosPokedexEntry", + "front": "zapdos", + "frontLabel": "ZapdosFrontpic", + "id": "ZAPDOS", + "name": "ZAPDOS" + }, + "ZUBAT": { + "back": "zubat_back", + "backLabel": "ZubatBackpic", + "dexLabel": "ZubatPokedexEntry", + "front": "zubat", + "frontLabel": "ZubatFrontpic", + "id": "ZUBAT", + "name": "ZUBAT" + } + }, + "romSha1": "49b163f7e57702bc939d642a18f591de55d92dae", + "symbols": { + "AbraBackpic": [ + 27, + 20442 + ], + "AbraFrontpic": [ + 27, + 29677 + ], + "AbraPokedexEntry": [ + 104, + 23224 + ], + "AerodactylBackpic": [ + 27, + 21780 + ], + "AerodactylFrontpic": [ + 31, + 24999 + ], + "AerodactylPokedexEntry": [ + 106, + 17845 + ], + "AerodactylPuzzleLZ": [ + 56, + 25096 + ], + "AipomBackpic": [ + 27, + 23447 + ], + "AipomFrontpic": [ + 28, + 22415 + ], + "AipomPokedexEntry": [ + 106, + 23157 + ], + "AlakazamBackpic": [ + 21, + 25753 + ], + "AlakazamFrontpic": [ + 18, + 24458 + ], + "AlakazamPokedexEntry": [ + 105, + 16384 + ], + "AlphabeticalPokedexOrder": [ + 16, + 19557 + ], + "AmpharosBackpic": [ + 29, + 27083 + ], + "AmpharosFrontpic": [ + 32, + 31107 + ], + "AmpharosPokedexEntry": [ + 106, + 22136 + ], + "AnimObjGFX": [ + 51, + 31803 + ], + "AnimateFlowerTile": [ + 63, + 17288 + ], + "AnimateFlowerTile.FlowerTileFrames": [ + 63, + 17319 + ], + "AnimateHallOfFame.String_NewHallOfFamer": [ + 33, + 25876 + ], + "AnimateLavaBubbleTile1": [ + 63, + 17383 + ], + "AnimateLavaBubbleTile2": [ + 63, + 17414 + ], + "AnimateTowerPillarTile": [ + 63, + 17504 + ], + "AnimateWaterPalette": [ + 63, + 17650 + ], + "AnimateWaterTile": [ + 63, + 17198 + ], + "AnimateWaterTile.WaterTileFrames": [ + 63, + 17224 + ], + "AnimateWhirlpoolTile": [ + 63, + 17555 + ], + "ArbokBackpic": [ + 29, + 27921 + ], + "ArbokFrontpic": [ + 18, + 30587 + ], + "ArbokPokedexEntry": [ + 104, + 18908 + ], + "ArcanineBackpic": [ + 24, + 21507 + ], + "ArcanineFrontpic": [ + 18, + 23185 + ], + "ArcaninePokedexEntry": [ + 104, + 22788 + ], + "AriadosBackpic": [ + 26, + 30528 + ], + "AriadosFrontpic": [ + 22, + 32003 + ], + "AriadosPokedexEntry": [ + 106, + 20690 + ], + "ArticunoBackpic": [ + 30, + 17968 + ], + "ArticunoFrontpic": [ + 18, + 21900 + ], + "ArticunoPokedexEntry": [ + 106, + 18071 + ], + "AzumarillBackpic": [ + 30, + 17179 + ], + "AzumarillFrontpic": [ + 21, + 16861 + ], + "AzumarillPokedexEntry": [ + 106, + 22471 + ], + "BadgeGFX": [ + 9, + 25135 + ], + "BargainShopData": [ + 5, + 24282 + ], + "BaseData": [ + 20, + 23307 + ], + "BattleAnimFrameData": [ + 51, + 26531 + ], + "BattleAnimOAMData": [ + 51, + 28147 + ], + "BattleAnimObjects": [ + 51, + 19109 + ], + "BattleAnimations": [ + 50, + 20490 + ], + "BattleObjectPals": [ + 2, + 23561 + ], + "BattleWinSlideInEnemyTrainerFrontpic": [ + 15, + 27158 + ], + "BayleefBackpic": [ + 23, + 26734 + ], + "BayleefFrontpic": [ + 21, + 29863 + ], + "BayleefPokedexEntry": [ + 106, + 19029 + ], + "BeedrillBackpic": [ + 24, + 19540 + ], + "BeedrillFrontpic": [ + 18, + 27573 + ], + "BeedrillPokedexEntry": [ + 104, + 17937 + ], + "BellossomBackpic": [ + 26, + 27056 + ], + "BellossomFrontpic": [ + 25, + 19031 + ], + "BellossomPokedexEntry": [ + 106, + 22252 + ], + "BellsproutBackpic": [ + 29, + 22535 + ], + "BellsproutFrontpic": [ + 28, + 27661 + ], + "BellsproutPokedexEntry": [ + 105, + 16840 + ], + "BlastoiseBackpic": [ + 25, + 31647 + ], + "BlastoiseFrontpic": [ + 31, + 17124 + ], + "BlastoisePokedexEntry": [ + 104, + 17262 + ], + "BlisseyBackpic": [ + 29, + 31762 + ], + "BlisseyFrontpic": [ + 32, + 27218 + ], + "BlisseyPokedexEntry": [ + 107, + 21782 + ], + "BoltEmote": [ + 5, + 17737 + ], + "BugCatchingContestantEventFlagTable": [ + 4, + 32186 + ], + "BulbasaurBackpic": [ + 29, + 21089 + ], + "BulbasaurFrontpic": [ + 30, + 18230 + ], + "BulbasaurPokedexEntry": [ + 104, + 16384 + ], + "ButterfreeBackpic": [ + 21, + 23446 + ], + "ButterfreeFrontpic": [ + 21, + 17338 + ], + "ButterfreePokedexEntry": [ + 104, + 17595 + ], + "CalPic": [ + 26, + 27406 + ], + "CardStatusGFX": [ + 9, + 22287 + ], + "CaterpieBackpic": [ + 29, + 24822 + ], + "CaterpieFrontpic": [ + 30, + 32071 + ], + "CaterpiePokedexEntry": [ + 104, + 17376 + ], + "CelebiBackpic": [ + 23, + 16804 + ], + "CelebiFrontpic": [ + 30, + 25230 + ], + "CelebiPokedexEntry": [ + 107, + 22796 + ], + "ChanseyBackpic": [ + 28, + 28271 + ], + "ChanseyFrontpic": [ + 24, + 16384 + ], + "ChanseyPokedexEntry": [ + 105, + 21731 + ], + "CharizardBackpic": [ + 26, + 17106 + ], + "CharizardFrontpic": [ + 31, + 20578 + ], + "CharizardPokedexEntry": [ + 104, + 16928 + ], + "CharmanderBackpic": [ + 28, + 30389 + ], + "CharmanderFrontpic": [ + 28, + 27966 + ], + "CharmanderPokedexEntry": [ + 104, + 16704 + ], + "CharmeleonBackpic": [ + 24, + 25026 + ], + "CharmeleonFrontpic": [ + 23, + 29167 + ], + "CharmeleonPokedexEntry": [ + 104, + 16815 + ], + "ChikoritaBackpic": [ + 28, + 26130 + ], + "ChikoritaFrontpic": [ + 29, + 27363 + ], + "ChikoritaPokedexEntry": [ + 106, + 18929 + ], + "ChinchouBackpic": [ + 28, + 26744 + ], + "ChinchouFrontpic": [ + 26, + 18186 + ], + "ChinchouPokedexEntry": [ + 106, + 20919 + ], + "ChrisBackpic": [ + 15, + 31179 + ], + "ChrisPicAndTrainerCardGFX": [ + 9, + 21631 + ], + "ChrisSpriteGFX": [ + 48, + 16384 + ], + "ClefableBackpic": [ + 22, + 26062 + ], + "ClefableFrontpic": [ + 24, + 23857 + ], + "ClefablePokedexEntry": [ + 104, + 20237 + ], + "ClefairyBackpic": [ + 26, + 25655 + ], + "ClefairyFrontpic": [ + 29, + 25390 + ], + "ClefairyPokedexEntry": [ + 104, + 20130 + ], + "CleffaBackpic": [ + 30, + 20306 + ], + "CleffaFrontpic": [ + 30, + 28496 + ], + "CleffaPokedexEntry": [ + 106, + 21255 + ], + "ClockTilemapRLE": [ + 36, + 21914 + ], + "CloysterBackpic": [ + 21, + 26671 + ], + "CloysterFrontpic": [ + 31, + 17706 + ], + "CloysterPokedexEntry": [ + 105, + 19288 + ], + "ContestMons": [ + 37, + 31672 + ], + "CopyBackpic": [ + 15, + 31090 + ], + "CopyrightGFX": [ + 57, + 16384 + ], + "CorsolaBackpic": [ + 29, + 21668 + ], + "CorsolaFrontpic": [ + 25, + 28355 + ], + "CorsolaPokedexEntry": [ + 107, + 19610 + ], + "CreditsBellossomGFX": [ + 33, + 27958 + ], + "CreditsBorderGFX": [ + 33, + 27814 + ], + "CreditsElekidGFX": [ + 33, + 29494 + ], + "CreditsPalettes": [ + 33, + 27676 + ], + "CreditsSentretGFX": [ + 33, + 30262 + ], + "CreditsTogepiGFX": [ + 33, + 28726 + ], + "Cries": [ + 58, + 20882 + ], + "CrobatBackpic": [ + 29, + 31225 + ], + "CrobatFrontpic": [ + 23, + 24276 + ], + "CrobatPokedexEntry": [ + 106, + 20809 + ], + "CroconawBackpic": [ + 23, + 27547 + ], + "CroconawFrontpic": [ + 21, + 29409 + ], + "CroconawPokedexEntry": [ + 106, + 19666 + ], + "CuboneBackpic": [ + 24, + 19146 + ], + "CuboneFrontpic": [ + 28, + 19577 + ], + "CubonePokedexEntry": [ + 105, + 20725 + ], + "CyndaquilBackpic": [ + 23, + 25508 + ], + "CyndaquilFrontpic": [ + 27, + 29354 + ], + "CyndaquilPokedexEntry": [ + 106, + 19241 + ], + "DecorationDesc_GiantOrnament.BigDollScript": [ + 9, + 29110 + ], + "DecorationDesc_NullPoster": [ + 9, + 29067 + ], + "DecorationDesc_OrnamentOrConsole.OrnamentConsoleScript": [ + 9, + 29096 + ], + "DecorationDesc_PosterPointers": [ + 9, + 29015 + ], + "DelibirdBackpic": [ + 26, + 28452 + ], + "DelibirdFrontpic": [ + 24, + 27351 + ], + "DelibirdPokedexEntry": [ + 107, + 19939 + ], + "DewgongBackpic": [ + 30, + 25706 + ], + "DewgongFrontpic": [ + 32, + 17100 + ], + "DewgongPokedexEntry": [ + 105, + 18852 + ], + "DiglettBackpic": [ + 21, + 32573 + ], + "DiglettFrontpic": [ + 30, + 31289 + ], + "DiglettPokedexEntry": [ + 104, + 21789 + ], + "DiplomaGFX": [ + 56, + 16645 + ], + "DiplomaPage1Tilemap": [ + 56, + 17739 + ], + "DiplomaPalettes": [ + 2, + 31366 + ], + "DittoBackpic": [ + 30, + 20050 + ], + "DittoFrontpic": [ + 30, + 30037 + ], + "DittoPokedexEntry": [ + 106, + 16736 + ], + "DodrioBackpic": [ + 21, + 28499 + ], + "DodrioFrontpic": [ + 31, + 22818 + ], + "DodrioPokedexEntry": [ + 105, + 18620 + ], + "DoduoBackpic": [ + 23, + 28359 + ], + "DoduoFrontpic": [ + 29, + 26802 + ], + "DoduoPokedexEntry": [ + 105, + 18509 + ], + "DoneTileAnimation": [ + 63, + 17058 + ], + "DonphanBackpic": [ + 30, + 26647 + ], + "DonphanFrontpic": [ + 31, + 30287 + ], + "DonphanPokedexEntry": [ + 107, + 20701 + ], + "DragonairBackpic": [ + 28, + 25209 + ], + "DragonairFrontpic": [ + 31, + 32369 + ], + "DragonairPokedexEntry": [ + 106, + 18488 + ], + "DragoniteBackpic": [ + 29, + 27642 + ], + "DragoniteFrontpic": [ + 18, + 29387 + ], + "DragonitePokedexEntry": [ + 106, + 18586 + ], + "DratiniBackpic": [ + 30, + 21558 + ], + "DratiniFrontpic": [ + 29, + 30410 + ], + "DratiniPokedexEntry": [ + 106, + 18384 + ], + "DrawPocketName.tilemap": [ + 4, + 19964 + ], + "DrowzeeBackpic": [ + 30, + 20558 + ], + "DrowzeeFrontpic": [ + 23, + 16384 + ], + "DrowzeePokedexEntry": [ + 105, + 19839 + ], + "Drumkits": [ + 58, + 20050 + ], + "DudeBackpic": [ + 15, + 31579 + ], + "DugtrioBackpic": [ + 27, + 21446 + ], + "DugtrioFrontpic": [ + 23, + 27953 + ], + "DugtrioPokedexEntry": [ + 104, + 21899 + ], + "DunsparceBackpic": [ + 29, + 22823 + ], + "DunsparceFrontpic": [ + 27, + 23114 + ], + "DunsparcePokedexEntry": [ + 107, + 17828 + ], + "EeveeBackpic": [ + 24, + 31957 + ], + "EeveeFrontpic": [ + 28, + 19894 + ], + "EeveePokedexEntry": [ + 106, + 16849 + ], + "EggHatchGFX": [ + 5, + 30487 + ], + "EggMovePointers": [ + 8, + 31230 + ], + "EggPic": [ + 20, + 31363 + ], + "EkansBackpic": [ + 26, + 21764 + ], + "EkansFrontpic": [ + 29, + 20218 + ], + "EkansPokedexEntry": [ + 104, + 18799 + ], + "ElectabuzzBackpic": [ + 24, + 27738 + ], + "ElectabuzzFrontpic": [ + 32, + 26729 + ], + "ElectabuzzPokedexEntry": [ + 105, + 23053 + ], + "ElectrodeBackpic": [ + 30, + 22800 + ], + "ElectrodeFrontpic": [ + 30, + 25468 + ], + "ElectrodePokedexEntry": [ + 105, + 20407 + ], + "ElekidBackpic": [ + 22, + 31161 + ], + "ElekidFrontpic": [ + 23, + 30375 + ], + "ElekidPokedexEntry": [ + 107, + 21466 + ], + "ElevatorFloorNames": [ + 4, + 31045 + ], + "EnemyHPBarBorderGFX": [ + 62, + 19378 + ], + "EnteiBackpic": [ + 29, + 28478 + ], + "EnteiFrontpic": [ + 18, + 28786 + ], + "EnteiPokedexEntry": [ + 107, + 22002 + ], + "EnvironmentColorsPointers": [ + 2, + 30414 + ], + "EspeonBackpic": [ + 27, + 22448 + ], + "EspeonFrontpic": [ + 23, + 18899 + ], + "EspeonPokedexEntry": [ + 107, + 16720 + ], + "EvosAttacksPointers": [ + 16, + 26557 + ], + "ExeggcuteBackpic": [ + 26, + 19264 + ], + "ExeggcuteFrontpic": [ + 32, + 24250 + ], + "ExeggcutePokedexEntry": [ + 105, + 20523 + ], + "ExeggutorBackpic": [ + 21, + 19243 + ], + "ExeggutorFrontpic": [ + 32, + 19163 + ], + "ExeggutorPokedexEntry": [ + 105, + 20615 + ], + "ExpBarGFX": [ + 62, + 19458 + ], + "ExpBarPalette": [ + 2, + 27961 + ], + "FarfetchDBackpic": [ + 23, + 24687 + ], + "FarfetchDFrontpic": [ + 22, + 31582 + ], + "FarfetchDPokedexEntry": [ + 105, + 18395 + ], + "FearowBackpic": [ + 30, + 21309 + ], + "FearowFrontpic": [ + 31, + 27132 + ], + "FearowPokedexEntry": [ + 104, + 18694 + ], + "FeraligatrBackpic": [ + 22, + 26490 + ], + "FeraligatrFrontpic": [ + 18, + 23824 + ], + "FeraligatrPokedexEntry": [ + 106, + 19777 + ], + "FishEmote": [ + 5, + 17865 + ], + "FishGroups": [ + 36, + 27127 + ], + "FlaaffyBackpic": [ + 25, + 24656 + ], + "FlaaffyFrontpic": [ + 24, + 20721 + ], + "FlaaffyPokedexEntry": [ + 106, + 22031 + ], + "FlareonBackpic": [ + 24, + 20328 + ], + "FlareonFrontpic": [ + 32, + 32072 + ], + "FlareonPokedexEntry": [ + 106, + 17185 + ], + "FlickeringCaveEntrancePalette": [ + 63, + 17709 + ], + "Font": [ + 62, + 17138 + ], + "FontBattleExtra": [ + 62, + 18162 + ], + "FontExtra": [ + 62, + 16626 + ], + "FontsExtra_SolidBlackAndUpArrowGFX": [ + 62, + 21246 + ], + "Footprints": [ + 62, + 21262 + ], + "ForretressBackpic": [ + 46, + 17057 + ], + "ForretressFrontpic": [ + 21, + 22520 + ], + "ForretressPokedexEntry": [ + 107, + 17709 + ], + "Frames": [ + 62, + 18674 + ], + "FurretBackpic": [ + 26, + 19622 + ], + "FurretFrontpic": [ + 26, + 22476 + ], + "FurretPokedexEntry": [ + 106, + 20010 + ], + "GameFreakLogoGFX": [ + 57, + 18889 + ], + "GameFreakLogoStarsGFX": [ + 57, + 19113 + ], + "GastlyBackpic": [ + 30, + 21059 + ], + "GastlyFrontpic": [ + 22, + 23036 + ], + "GastlyPokedexEntry": [ + 105, + 19395 + ], + "GengarBackpic": [ + 27, + 25104 + ], + "GengarFrontpic": [ + 25, + 29457 + ], + "GengarPokedexEntry": [ + 105, + 19610 + ], + "GeodudeBackpic": [ + 25, + 29090 + ], + "GeodudeFrontpic": [ + 30, + 28944 + ], + "GeodudePokedexEntry": [ + 105, + 17409 + ], + "GetBattleMonBackpic": [ + 15, + 29251 + ], + "GetEggFrontpic": [ + 5, + 30156 + ], + "GetEnemyMonFrontpic": [ + 15, + 29314 + ], + "GetFrontpic": [ + 20, + 22420 + ], + "GetMonBackpic": [ + 20, + 22531 + ], + "GetMonFrontpic": [ + 20, + 22400 + ], + "GetTrainerBackpic": [ + 15, + 31064 + ], + "GirafarigBackpic": [ + 25, + 18275 + ], + "GirafarigFrontpic": [ + 21, + 16384 + ], + "GirafarigPokedexEntry": [ + 107, + 17489 + ], + "GligarBackpic": [ + 24, + 24637 + ], + "GligarFrontpic": [ + 22, + 21722 + ], + "GligarPokedexEntry": [ + 107, + 17940 + ], + "GloomBackpic": [ + 24, + 19934 + ], + "GloomFrontpic": [ + 26, + 23541 + ], + "GloomPokedexEntry": [ + 104, + 21131 + ], + "GolbatBackpic": [ + 30, + 32248 + ], + "GolbatFrontpic": [ + 21, + 27129 + ], + "GolbatPokedexEntry": [ + 104, + 20912 + ], + "GoldeenBackpic": [ + 24, + 21114 + ], + "GoldeenFrontpic": [ + 23, + 20149 + ], + "GoldeenPokedexEntry": [ + 105, + 22269 + ], + "GolduckBackpic": [ + 26, + 32242 + ], + "GolduckFrontpic": [ + 31, + 28713 + ], + "GolduckPokedexEntry": [ + 104, + 22327 + ], + "GolemBackpic": [ + 30, + 29604 + ], + "GolemFrontpic": [ + 21, + 18292 + ], + "GolemPokedexEntry": [ + 105, + 17623 + ], + "GranbullBackpic": [ + 28, + 23351 + ], + "GranbullFrontpic": [ + 22, + 26917 + ], + "GranbullPokedexEntry": [ + 107, + 18286 + ], + "GrassRustleGFX": [ + 5, + 18009 + ], + "GravelerBackpic": [ + 27, + 22781 + ], + "GravelerFrontpic": [ + 21, + 27586 + ], + "GravelerPokedexEntry": [ + 105, + 17514 + ], + "GrimerBackpic": [ + 24, + 26964 + ], + "GrimerFrontpic": [ + 27, + 17064 + ], + "GrimerPokedexEntry": [ + 105, + 18962 + ], + "GrowlitheBackpic": [ + 25, + 21661 + ], + "GrowlitheFrontpic": [ + 28, + 20211 + ], + "GrowlithePokedexEntry": [ + 104, + 22681 + ], + "GrowthRates": [ + 20, + 22019 + ], + "GyaradosBackpic": [ + 22, + 23472 + ], + "GyaradosFrontpic": [ + 18, + 25713 + ], + "GyaradosPokedexEntry": [ + 106, + 16503 + ], + "HOF_SlideBackpic": [ + 33, + 26135 + ], + "HOF_SlideFrontpic": [ + 33, + 26149 + ], + "HPBarPals": [ + 2, + 27949 + ], + "HPExpBarBorderGFX": [ + 62, + 19410 + ], + "HappyEmote": [ + 5, + 17545 + ], + "HaunterBackpic": [ + 28, + 17025 + ], + "HaunterFrontpic": [ + 24, + 25414 + ], + "HaunterPokedexEntry": [ + 105, + 19495 + ], + "HealMachineAnim.HealMachineGFX": [ + 4, + 26581 + ], + "HealMachineAnim.palettes": [ + 4, + 26664 + ], + "HeartEmote": [ + 5, + 17673 + ], + "HeldItemIcons": [ + 35, + 26817 + ], + "HeracrossBackpic": [ + 24, + 28509 + ], + "HeracrossFrontpic": [ + 24, + 21900 + ], + "HeracrossPokedexEntry": [ + 107, + 18739 + ], + "HitmonchanBackpic": [ + 25, + 30555 + ], + "HitmonchanFrontpic": [ + 28, + 31290 + ], + "HitmonchanPokedexEntry": [ + 105, + 21066 + ], + "HitmonleeBackpic": [ + 29, + 28200 + ], + "HitmonleeFrontpic": [ + 32, + 28196 + ], + "HitmonleePokedexEntry": [ + 105, + 20957 + ], + "HitmontopBackpic": [ + 21, + 26213 + ], + "HitmontopFrontpic": [ + 32, + 26240 + ], + "HitmontopPokedexEntry": [ + 107, + 21252 + ], + "HoOhBackpic": [ + 27, + 19098 + ], + "HoOhFrontpic": [ + 18, + 18592 + ], + "HoOhPokedexEntry": [ + 107, + 22682 + ], + "HoOhPuzzleLZ": [ + 56, + 24760 + ], + "HoothootBackpic": [ + 27, + 28706 + ], + "HoothootFrontpic": [ + 28, + 25823 + ], + "HoothootPokedexEntry": [ + 106, + 20127 + ], + "HoppipBackpic": [ + 28, + 22727 + ], + "HoppipFrontpic": [ + 29, + 29860 + ], + "HoppipPokedexEntry": [ + 106, + 22817 + ], + "HorseaBackpic": [ + 30, + 18753 + ], + "HorseaFrontpic": [ + 30, + 30670 + ], + "HorseaPokedexEntry": [ + 105, + 22054 + ], + "HoundoomBackpic": [ + 25, + 28723 + ], + "HoundoomFrontpic": [ + 32, + 16582 + ], + "HoundoomPokedexEntry": [ + 107, + 20372 + ], + "HoundourBackpic": [ + 30, + 22553 + ], + "HoundourFrontpic": [ + 27, + 24774 + ], + "HoundourPokedexEntry": [ + 107, + 20272 + ], + "HypnoBackpic": [ + 23, + 30776 + ], + "HypnoFrontpic": [ + 32, + 21212 + ], + "HypnoPokedexEntry": [ + 105, + 19958 + ], + "IconPointers": [ + 35, + 27222 + ], + "Icons": [ + 35, + 27300 + ], + "IgglybuffBackpic": [ + 30, + 16914 + ], + "IgglybuffFrontpic": [ + 30, + 16384 + ], + "IgglybuffPokedexEntry": [ + 106, + 21359 + ], + "InitializeEventsScript": [ + 64, + 17256 + ], + "Intro_FireGFX1": [ + 57, + 26448 + ], + "Intro_FireGFX2": [ + 57, + 27792 + ], + "Intro_FireGFX3": [ + 57, + 28688 + ], + "Intro_GetMonFrontpic": [ + 20, + 22655 + ], + "Intro_GrassGFX1": [ + 57, + 24224 + ], + "Intro_GrassGFX2": [ + 57, + 25072 + ], + "Intro_GrassMeta": [ + 57, + 24960 + ], + "Intro_GrassTilemap": [ + 57, + 24704 + ], + "Intro_LoadMagikarpPalettes.MagikarpBGPal": [ + 2, + 20774 + ], + "Intro_LoadMagikarpPalettes.MagikarpOBPal": [ + 2, + 20782 + ], + "Intro_RotatePalettesLeftFrontpic": [ + 1, + 25006 + ], + "Intro_WaterGFX1": [ + 57, + 21296 + ], + "Intro_WaterGFX2": [ + 57, + 23328 + ], + "Intro_WaterMeta": [ + 57, + 23056 + ], + "Intro_WaterTilemap": [ + 57, + 22544 + ], + "Intro_WipeInFrontpic": [ + 1, + 25030 + ], + "ItemAttributes": [ + 1, + 26726 + ], + "ItemDescriptions": [ + 110, + 16384 + ], + "ItemNames": [ + 108, + 16384 + ], + "IvysaurBackpic": [ + 26, + 30873 + ], + "IvysaurFrontpic": [ + 27, + 17743 + ], + "IvysaurPokedexEntry": [ + 104, + 16488 + ], + "JigglypuffBackpic": [ + 30, + 23046 + ], + "JigglypuffFrontpic": [ + 30, + 21807 + ], + "JigglypuffPokedexEntry": [ + 104, + 20574 + ], + "JohtoGrassWildMons": [ + 10, + 27445 + ], + "JohtoMap": [ + 36, + 24402 + ], + "JohtoWaterWildMons": [ + 10, + 30313 + ], + "JolteonBackpic": [ + 28, + 21159 + ], + "JolteonFrontpic": [ + 22, + 27344 + ], + "JolteonPokedexEntry": [ + 106, + 17077 + ], + "JumpluffBackpic": [ + 25, + 23910 + ], + "JumpluffFrontpic": [ + 21, + 22056 + ], + "JumpluffPokedexEntry": [ + 106, + 23039 + ], + "JynxBackpic": [ + 26, + 32583 + ], + "JynxFrontpic": [ + 22, + 18178 + ], + "JynxPokedexEntry": [ + 105, + 22939 + ], + "KabutoBackpic": [ + 27, + 30323 + ], + "KabutoFrontpic": [ + 30, + 26413 + ], + "KabutoPokedexEntry": [ + 106, + 17629 + ], + "KabutoPuzzleLZ": [ + 56, + 25528 + ], + "KabutopsBackpic": [ + 24, + 18752 + ], + "KabutopsFrontpic": [ + 21, + 21123 + ], + "KabutopsPokedexEntry": [ + 106, + 17739 + ], + "KadabraBackpic": [ + 26, + 18905 + ], + "KadabraFrontpic": [ + 21, + 25293 + ], + "KadabraPokedexEntry": [ + 104, + 23330 + ], + "KakunaBackpic": [ + 29, + 30135 + ], + "KakunaFrontpic": [ + 30, + 29386 + ], + "KakunaPokedexEntry": [ + 104, + 17824 + ], + "KangaskhanBackpic": [ + 22, + 19518 + ], + "KangaskhanFrontpic": [ + 18, + 29988 + ], + "KangaskhanPokedexEntry": [ + 105, + 21949 + ], + "KantoGrassWildMons": [ + 10, + 30656 + ], + "KantoMap": [ + 36, + 24763 + ], + "KantoWaterWildMons": [ + 10, + 32067 + ], + "KingdraBackpic": [ + 26, + 24600 + ], + "KingdraFrontpic": [ + 21, + 28954 + ], + "KingdraPokedexEntry": [ + 107, + 20477 + ], + "KinglerBackpic": [ + 27, + 24112 + ], + "KinglerFrontpic": [ + 32, + 24752 + ], + "KinglerPokedexEntry": [ + 105, + 20191 + ], + "KoffingBackpic": [ + 25, + 17519 + ], + "KoffingFrontpic": [ + 21, + 18768 + ], + "KoffingPokedexEntry": [ + 105, + 21288 + ], + "KrabbyBackpic": [ + 23, + 18062 + ], + "KrabbyFrontpic": [ + 28, + 30087 + ], + "KrabbyPokedexEntry": [ + 105, + 20072 + ], + "Landmarks": [ + 36, + 25474 + ], + "LanturnBackpic": [ + 29, + 26240 + ], + "LanturnFrontpic": [ + 24, + 25802 + ], + "LanturnPokedexEntry": [ + 106, + 21026 + ], + "LaprasBackpic": [ + 28, + 24593 + ], + "LaprasFrontpic": [ + 21, + 23909 + ], + "LaprasPokedexEntry": [ + 106, + 16621 + ], + "LarvitarBackpic": [ + 29, + 16384 + ], + "LarvitarFrontpic": [ + 29, + 20799 + ], + "LarvitarPokedexEntry": [ + 107, + 22224 + ], + "LavaBubbleTileFrames": [ + 63, + 17440 + ], + "LeaderGFX": [ + 9, + 22383 + ], + "LedianBackpic": [ + 24, + 28894 + ], + "LedianFrontpic": [ + 24, + 28124 + ], + "LedianPokedexEntry": [ + 106, + 20459 + ], + "LedybaBackpic": [ + 23, + 23454 + ], + "LedybaFrontpic": [ + 27, + 27730 + ], + "LedybaPokedexEntry": [ + 106, + 20341 + ], + "LickitungBackpic": [ + 27, + 32257 + ], + "LickitungFrontpic": [ + 32, + 28683 + ], + "LickitungPokedexEntry": [ + 105, + 21184 + ], + "LoadOrientedFrontpic": [ + 20, + 22948 + ], + "LoadQuestionMarkPic.QuestionMarkLZ": [ + 112, + 19520 + ], + "LugiaBackpic": [ + 28, + 29483 + ], + "LugiaFrontpic": [ + 31, + 24462 + ], + "LugiaPokedexEntry": [ + 107, + 22571 + ], + "MachampBackpic": [ + 32, + 31590 + ], + "MachampFrontpic": [ + 18, + 17890 + ], + "MachampPokedexEntry": [ + 105, + 16724 + ], + "MachokeBackpic": [ + 25, + 22786 + ], + "MachokeFrontpic": [ + 31, + 18288 + ], + "MachokePokedexEntry": [ + 105, + 16603 + ], + "MachopBackpic": [ + 27, + 20777 + ], + "MachopFrontpic": [ + 28, + 21788 + ], + "MachopPokedexEntry": [ + 105, + 16484 + ], + "MagbyBackpic": [ + 28, + 24901 + ], + "MagbyFrontpic": [ + 27, + 18760 + ], + "MagbyPokedexEntry": [ + 107, + 21566 + ], + "MagcargoBackpic": [ + 24, + 26190 + ], + "MagcargoFrontpic": [ + 21, + 20185 + ], + "MagcargoPokedexEntry": [ + 107, + 19300 + ], + "MagikarpBackpic": [ + 26, + 30183 + ], + "MagikarpFrontpic": [ + 22, + 28196 + ], + "MagikarpPokedexEntry": [ + 106, + 16384 + ], + "MagmarBackpic": [ + 23, + 25098 + ], + "MagmarFrontpic": [ + 32, + 25747 + ], + "MagmarPokedexEntry": [ + 105, + 23168 + ], + "MagnemiteBackpic": [ + 46, + 16727 + ], + "MagnemiteFrontpic": [ + 46, + 18434 + ], + "MagnemitePokedexEntry": [ + 105, + 18181 + ], + "MagnetTrainBGTiles": [ + 35, + 20492 + ], + "MagnetTrainTilemap": [ + 35, + 20772 + ], + "MagnetonBackpic": [ + 22, + 29046 + ], + "MagnetonFrontpic": [ + 21, + 24833 + ], + "MagnetonPokedexEntry": [ + 105, + 18286 + ], + "MankeyBackpic": [ + 23, + 29571 + ], + "MankeyFrontpic": [ + 27, + 27403 + ], + "MankeyPokedexEntry": [ + 104, + 22437 + ], + "MantineBackpic": [ + 30, + 29821 + ], + "MantineFrontpic": [ + 32, + 23241 + ], + "MantinePokedexEntry": [ + 107, + 20048 + ], + "MapGroupPointers": [ + 37, + 16621 + ], + "MapGroupRoofs": [ + 7, + 16417 + ], + "MapObjectPals": [ + 2, + 30894 + ], + "MareepBackpic": [ + 26, + 23894 + ], + "MareepFrontpic": [ + 28, + 28878 + ], + "MareepPokedexEntry": [ + 106, + 21922 + ], + "MarillBackpic": [ + 30, + 22056 + ], + "MarillFrontpic": [ + 29, + 23111 + ], + "MarillPokedexEntry": [ + 106, + 22364 + ], + "MarowakBackpic": [ + 25, + 27619 + ], + "MarowakFrontpic": [ + 25, + 16384 + ], + "MarowakPokedexEntry": [ + 105, + 20838 + ], + "Marts": [ + 5, + 25342 + ], + "MeganiumBackpic": [ + 27, + 31291 + ], + "MeganiumFrontpic": [ + 18, + 31756 + ], + "MeganiumPokedexEntry": [ + 106, + 19136 + ], + "MeowthBackpic": [ + 27, + 31935 + ], + "MeowthFrontpic": [ + 27, + 26748 + ], + "MeowthPokedexEntry": [ + 104, + 22004 + ], + "MetapodBackpic": [ + 30, + 23291 + ], + "MetapodFrontpic": [ + 30, + 31692 + ], + "MetapodPokedexEntry": [ + 104, + 17485 + ], + "MewBackpic": [ + 27, + 20107 + ], + "MewFrontpic": [ + 28, + 27050 + ], + "MewPokedexEntry": [ + 106, + 18815 + ], + "MewtwoBackpic": [ + 26, + 26356 + ], + "MewtwoFrontpic": [ + 31, + 23917 + ], + "MewtwoPokedexEntry": [ + 106, + 18700 + ], + "MiltankBackpic": [ + 25, + 21286 + ], + "MiltankFrontpic": [ + 23, + 19316 + ], + "MiltankPokedexEntry": [ + 107, + 21678 + ], + "MisdreavusBackpic": [ + 25, + 32010 + ], + "MisdreavusFrontpic": [ + 25, + 30920 + ], + "MisdreavusPokedexEntry": [ + 107, + 17160 + ], + "MoltresBackpic": [ + 23, + 19733 + ], + "MoltresFrontpic": [ + 18, + 21253 + ], + "MoltresPokedexEntry": [ + 106, + 18288 + ], + "MonMenuIcons": [ + 35, + 26971 + ], + "MoveDescriptions": [ + 109, + 16384 + ], + "MoveNames": [ + 108, + 21876 + ], + "Moves": [ + 16, + 23294 + ], + "MrMimeBackpic": [ + 29, + 23683 + ], + "MrMimeFrontpic": [ + 24, + 18358 + ], + "MrMimePokedexEntry": [ + 105, + 22727 + ], + "MukBackpic": [ + 26, + 28799 + ], + "MukFrontpic": [ + 31, + 25535 + ], + "MukPokedexEntry": [ + 105, + 19068 + ], + "MurkrowBackpic": [ + 22, + 32424 + ], + "MurkrowFrontpic": [ + 22, + 17731 + ], + "MurkrowPokedexEntry": [ + 107, + 16938 + ], + "Music": [ + 58, + 20590 + ], + "Music_AfterTheRivalFight": [ + 61, + 20775 + ], + "Music_AzaleaTown": [ + 61, + 22504 + ], + "Music_Bicycle": [ + 59, + 24857 + ], + "Music_BugCatchingContest": [ + 61, + 31766 + ], + "Music_BurnedTower": [ + 61, + 27289 + ], + "Music_CeladonCity": [ + 61, + 17243 + ], + "Music_ChampionBattle": [ + 58, + 26986 + ], + "Music_CherrygroveCity": [ + 61, + 23299 + ], + "Music_ContestResults": [ + 59, + 31550 + ], + "Music_Credits": [ + 7, + 30796 + ], + "Music_DancingHall": [ + 59, + 31164 + ], + "Music_DarkCave": [ + 58, + 25191 + ], + "Music_DragonsDen": [ + 59, + 27880 + ], + "Music_EcruteakCity": [ + 59, + 29392 + ], + "Music_ElmsLab": [ + 58, + 24553 + ], + "Music_Evolution": [ + 60, + 18145 + ], + "Music_GameCorner": [ + 59, + 23917 + ], + "Music_GoldSilverOpening": [ + 61, + 28757 + ], + "Music_GoldSilverOpening2": [ + 61, + 29448 + ], + "Music_GoldenrodCity": [ + 58, + 29692 + ], + "Music_Gym": [ + 61, + 18606 + ], + "Music_GymLeaderVictory": [ + 61, + 17964 + ], + "Music_HallOfFame": [ + 60, + 17855 + ], + "Music_HealPokemon": [ + 60, + 18071 + ], + "Music_IndigoPlateau": [ + 59, + 26706 + ], + "Music_JohtoGymBattle": [ + 58, + 25782 + ], + "Music_JohtoTrainerBattle": [ + 61, + 24726 + ], + "Music_JohtoWildBattle": [ + 61, + 24005 + ], + "Music_JohtoWildBattleNight": [ + 61, + 24668 + ], + "Music_KantoGymBattle": [ + 59, + 18208 + ], + "Music_KantoTrainerBattle": [ + 59, + 18938 + ], + "Music_KantoWildBattle": [ + 59, + 20589 + ], + "Music_LakeOfRage": [ + 59, + 26309 + ], + "Music_LakeOfRageRocketRadio": [ + 59, + 30129 + ], + "Music_LavenderTown": [ + 59, + 30511 + ], + "Music_Lighthouse": [ + 59, + 25961 + ], + "Music_LookBeauty": [ + 59, + 28222 + ], + "Music_LookHiker": [ + 61, + 29713 + ], + "Music_LookKimonoGirl": [ + 61, + 31160 + ], + "Music_LookLass": [ + 59, + 22427 + ], + "Music_LookOfficer": [ + 59, + 22573 + ], + "Music_LookPokemaniac": [ + 58, + 32213 + ], + "Music_LookRival": [ + 61, + 20345 + ], + "Music_LookRocket": [ + 61, + 29858 + ], + "Music_LookSage": [ + 59, + 25438 + ], + "Music_LookYoungster": [ + 61, + 26307 + ], + "Music_MagnetTrain": [ + 59, + 30192 + ], + "Music_MainMenu": [ + 61, + 30973 + ], + "Music_Mom": [ + 61, + 27634 + ], + "Music_MtMoon": [ + 59, + 23337 + ], + "Music_MtMoonSquare": [ + 61, + 18453 + ], + "Music_NationalPark": [ + 61, + 21736 + ], + "Music_NewBarkTown": [ + 58, + 29308 + ], + "Music_Nothing": [ + 58, + 20869 + ], + "Music_PalletTown": [ + 61, + 19212 + ], + "Music_PokeFluteChannel": [ + 61, + 31507 + ], + "Music_PokemonCenter": [ + 59, + 21958 + ], + "Music_PokemonChannel": [ + 59, + 25695 + ], + "Music_PokemonLullaby": [ + 61, + 28025 + ], + "Music_PokemonMarch": [ + 61, + 28195 + ], + "Music_PostCredits": [ + 51, + 31971 + ], + "Music_Printer": [ + 60, + 18429 + ], + "Music_ProfOak": [ + 61, + 19946 + ], + "Music_ProfOaksPokemonTalk": [ + 61, + 19615 + ], + "Music_RivalBattle": [ + 58, + 22264 + ], + "Music_RocketBattle": [ + 58, + 23320 + ], + "Music_RocketHideout": [ + 59, + 27509 + ], + "Music_RocketTheme": [ + 61, + 30317 + ], + "Music_Route1": [ + 59, + 16384 + ], + "Music_Route12": [ + 59, + 17670 + ], + "Music_Route2": [ + 59, + 22749 + ], + "Music_Route26": [ + 59, + 28594 + ], + "Music_Route29": [ + 60, + 17286 + ], + "Music_Route3": [ + 59, + 17098 + ], + "Music_Route30": [ + 59, + 31745 + ], + "Music_Route36": [ + 58, + 21650 + ], + "Music_Route37": [ + 59, + 26955 + ], + "Music_RuinsOfAlphInterior": [ + 58, + 32146 + ], + "Music_RuinsOfAlphRadio": [ + 59, + 28107 + ], + "Music_SSAqua": [ + 58, + 27970 + ], + "Music_ShowMeAround": [ + 59, + 23512 + ], + "Music_SproutTower": [ + 61, + 26996 + ], + "Music_SuccessfulCapture": [ + 61, + 17922 + ], + "Music_Surf": [ + 61, + 20874 + ], + "Music_TinTower": [ + 61, + 26641 + ], + "Music_TitleScreen": [ + 58, + 30641 + ], + "Music_TrainerVictory": [ + 58, + 32415 + ], + "Music_UnionCave": [ + 61, + 23648 + ], + "Music_VermilionCity": [ + 58, + 30239 + ], + "Music_VictoryRoad": [ + 61, + 27762 + ], + "Music_VioletCity": [ + 60, + 16384 + ], + "Music_ViridianCity": [ + 61, + 16384 + ], + "Music_WildPokemonVictory": [ + 61, + 17688 + ], + "NPCTradeCableText": [ + 63, + 19744 + ], + "NPCTrades": [ + 63, + 19492 + ], + "NamingScreenGFX_Border": [ + 4, + 24708 + ], + "NamingScreenGFX_Cursor": [ + 4, + 24724 + ], + "NamingScreenGFX_MiddleLine": [ + 4, + 25138 + ], + "NamingScreenGFX_UnderLine": [ + 4, + 25146 + ], + "NatuBackpic": [ + 30, + 31491 + ], + "NatuFrontpic": [ + 30, + 31086 + ], + "NatuPokedexEntry": [ + 106, + 21692 + ], + "NewPokedexEntry": [ + 62, + 31105 + ], + "NewPokedexOrder": [ + 16, + 19808 + ], + "NidokingBackpic": [ + 32, + 29169 + ], + "NidokingFrontpic": [ + 18, + 19949 + ], + "NidokingPokedexEntry": [ + 104, + 20022 + ], + "NidoqueenBackpic": [ + 24, + 26577 + ], + "NidoqueenFrontpic": [ + 31, + 18862 + ], + "NidoqueenPokedexEntry": [ + 104, + 19683 + ], + "NidoranFBackpic": [ + 27, + 22114 + ], + "NidoranFFrontpic": [ + 29, + 25674 + ], + "NidoranFPokedexEntry": [ + 104, + 19457 + ], + "NidoranMBackpic": [ + 23, + 23865 + ], + "NidoranMFrontpic": [ + 28, + 28575 + ], + "NidoranMPokedexEntry": [ + 104, + 19795 + ], + "NidorinaBackpic": [ + 25, + 23161 + ], + "NidorinaFrontpic": [ + 22, + 20843 + ], + "NidorinaPokedexEntry": [ + 104, + 19579 + ], + "NidorinoBackpic": [ + 25, + 25400 + ], + "NidorinoFrontpic": [ + 23, + 27141 + ], + "NidorinoPokedexEntry": [ + 104, + 19906 + ], + "NinetalesBackpic": [ + 23, + 26327 + ], + "NinetalesFrontpic": [ + 18, + 19289 + ], + "NinetalesPokedexEntry": [ + 104, + 20465 + ], + "NoctowlBackpic": [ + 27, + 30646 + ], + "NoctowlFrontpic": [ + 21, + 28043 + ], + "NoctowlPokedexEntry": [ + 106, + 20240 + ], + "OctilleryBackpic": [ + 30, + 17443 + ], + "OctilleryFrontpic": [ + 24, + 30812 + ], + "OctilleryPokedexEntry": [ + 107, + 19826 + ], + "OddishBackpic": [ + 30, + 23780 + ], + "OddishFrontpic": [ + 29, + 32564 + ], + "OddishPokedexEntry": [ + 104, + 21025 + ], + "OmanyteBackpic": [ + 24, + 22293 + ], + "OmanyteFrontpic": [ + 29, + 19633 + ], + "OmanytePokedexEntry": [ + 106, + 17404 + ], + "OmanytePuzzleLZ": [ + 56, + 25768 + ], + "OmastarBackpic": [ + 22, + 25633 + ], + "OmastarFrontpic": [ + 22, + 18625 + ], + "OmastarPokedexEntry": [ + 106, + 17518 + ], + "OnixBackpic": [ + 25, + 16763 + ], + "OnixFrontpic": [ + 32, + 19676 + ], + "OnixPokedexEntry": [ + 105, + 19724 + ], + "OverworldSprites": [ + 5, + 18398 + ], + "PackGFX": [ + 4, + 21553 + ], + "PackMenuGFX": [ + 4, + 20273 + ], + "PadFrontpic": [ + 20, + 22854 + ], + "PalPacket_Pack": [ + 2, + 24741 + ], + "ParasBackpic": [ + 29, + 17277 + ], + "ParasFrontpic": [ + 28, + 32485 + ], + "ParasPokedexEntry": [ + 104, + 21347 + ], + "ParasectBackpic": [ + 28, + 21474 + ], + "ParasectFrontpic": [ + 21, + 17815 + ], + "ParasectPokedexEntry": [ + 104, + 21453 + ], + "PartyMenuOBPals": [ + 2, + 31430 + ], + "PersianBackpic": [ + 24, + 30047 + ], + "PersianFrontpic": [ + 31, + 29764 + ], + "PersianPokedexEntry": [ + 104, + 22117 + ], + "PhanpyBackpic": [ + 29, + 29585 + ], + "PhanpyFrontpic": [ + 30, + 23536 + ], + "PhanpyPokedexEntry": [ + 107, + 20586 + ], + "PhoneContacts": [ + 36, + 17466 + ], + "PhoneOutOfAreaScript": [ + 36, + 17958 + ], + "PhoneScript_JustTalkToThem": [ + 36, + 17967 + ], + "PhoneTilemapRLE": [ + 36, + 21833 + ], + "PichuBackpic": [ + 29, + 22246 + ], + "PichuFrontpic": [ + 30, + 22305 + ], + "PichuPokedexEntry": [ + 106, + 21136 + ], + "PidgeotBackpic": [ + 27, + 18421 + ], + "PidgeotFrontpic": [ + 32, + 30139 + ], + "PidgeotPokedexEntry": [ + 104, + 18269 + ], + "PidgeottoBackpic": [ + 29, + 23968 + ], + "PidgeottoFrontpic": [ + 22, + 27770 + ], + "PidgeottoPokedexEntry": [ + 104, + 18153 + ], + "PidgeyBackpic": [ + 24, + 23076 + ], + "PidgeyFrontpic": [ + 30, + 27807 + ], + "PidgeyPokedexEntry": [ + 104, + 18035 + ], + "PikachuBackpic": [ + 26, + 23187 + ], + "PikachuFrontpic": [ + 29, + 25106 + ], + "PikachuPokedexEntry": [ + 104, + 19021 + ], + "PiloswineBackpic": [ + 30, + 30460 + ], + "PiloswineFrontpic": [ + 24, + 29279 + ], + "PiloswinePokedexEntry": [ + 107, + 19499 + ], + "PinecoBackpic": [ + 28, + 18304 + ], + "PinecoFrontpic": [ + 28, + 30690 + ], + "PinecoPokedexEntry": [ + 107, + 17601 + ], + "PinsirBackpic": [ + 25, + 25771 + ], + "PinsirFrontpic": [ + 32, + 20700 + ], + "PinsirPokedexEntry": [ + 105, + 23276 + ], + "PlaceUnownPrinterFrontpic": [ + 5, + 28636 + ], + "PokedexCursorPalette": [ + 2, + 21841 + ], + "PokedexDataPointerTable": [ + 17, + 17248 + ], + "PokedexLZ": [ + 16, + 21777 + ], + "PokedexQuestionMarkPalette": [ + 2, + 21849 + ], + "PokedexSlowpokeLZ": [ + 16, + 22195 + ], + "PokegearGFX": [ + 112, + 20035 + ], + "PokegearPals": [ + 2, + 31598 + ], + "PokegearPhoneIconGFX": [ + 62, + 20502 + ], + "PokegearSpritesGFX": [ + 36, + 21660 + ], + "PokemonCries": [ + 60, + 26439 + ], + "PokemonNames": [ + 108, + 19316 + ], + "PokemonPalettes": [ + 2, + 27965 + ], + "PokemonPicPointers": [ + 18, + 16384 + ], + "PokemonProfPic": [ + 26, + 26006 + ], + "PolitoedBackpic": [ + 29, + 17573 + ], + "PolitoedFrontpic": [ + 21, + 19715 + ], + "PolitoedPokedexEntry": [ + 106, + 22708 + ], + "PoliwagBackpic": [ + 30, + 24751 + ], + "PoliwagFrontpic": [ + 30, + 20809 + ], + "PoliwagPokedexEntry": [ + 104, + 22901 + ], + "PoliwhirlBackpic": [ + 27, + 19771 + ], + "PoliwhirlFrontpic": [ + 25, + 20911 + ], + "PoliwhirlPokedexEntry": [ + 104, + 23014 + ], + "PoliwrathBackpic": [ + 25, + 23536 + ], + "PoliwrathFrontpic": [ + 21, + 22983 + ], + "PoliwrathPokedexEntry": [ + 104, + 23117 + ], + "PonytaBackpic": [ + 24, + 32337 + ], + "PonytaFrontpic": [ + 22, + 20403 + ], + "PonytaPokedexEntry": [ + 105, + 17735 + ], + "Porygon2Backpic": [ + 28, + 16705 + ], + "Porygon2Frontpic": [ + 28, + 22102 + ], + "Porygon2PokedexEntry": [ + 107, + 20806 + ], + "PorygonBackpic": [ + 29, + 16980 + ], + "PorygonFrontpic": [ + 26, + 29146 + ], + "PorygonPokedexEntry": [ + 106, + 17290 + ], + "PredefPals": [ + 2, + 25189 + ], + "PrepMonFrontpic": [ + 0, + 14778 + ], + "PrimeapeBackpic": [ + 26, + 18546 + ], + "PrimeapeFrontpic": [ + 31, + 31330 + ], + "PrimeapePokedexEntry": [ + 104, + 22560 + ], + "PsyduckBackpic": [ + 26, + 22120 + ], + "PsyduckFrontpic": [ + 27, + 25434 + ], + "PsyduckPokedexEntry": [ + 104, + 22217 + ], + "PupitarBackpic": [ + 25, + 17141 + ], + "PupitarFrontpic": [ + 26, + 16745 + ], + "PupitarPokedexEntry": [ + 107, + 22333 + ], + "PuzzlePieceBorderData.TileBordersGFX": [ + 56, + 24368 + ], + "QuagsireBackpic": [ + 28, + 20843 + ], + "QuagsireFrontpic": [ + 27, + 28056 + ], + "QuagsirePokedexEntry": [ + 107, + 16597 + ], + "QuestionEmote": [ + 5, + 17481 + ], + "QuilavaBackpic": [ + 23, + 31177 + ], + "QuilavaFrontpic": [ + 22, + 25203 + ], + "QuilavaPokedexEntry": [ + 106, + 19350 + ], + "QwilfishBackpic": [ + 24, + 23467 + ], + "QwilfishFrontpic": [ + 29, + 31494 + ], + "QwilfishPokedexEntry": [ + 107, + 18398 + ], + "RadioTilemapRLE": [ + 36, + 21708 + ], + "RaichuBackpic": [ + 25, + 22411 + ], + "RaichuFrontpic": [ + 32, + 17617 + ], + "RaichuPokedexEntry": [ + 104, + 19137 + ], + "RaikouBackpic": [ + 30, + 17706 + ], + "RaikouFrontpic": [ + 18, + 26955 + ], + "RaikouPokedexEntry": [ + 107, + 21901 + ], + "RapidashBackpic": [ + 23, + 28763 + ], + "RapidashFrontpic": [ + 31, + 21710 + ], + "RapidashPokedexEntry": [ + 105, + 17853 + ], + "RaticateBackpic": [ + 26, + 20695 + ], + "RaticateFrontpic": [ + 22, + 28621 + ], + "RaticatePokedexEntry": [ + 104, + 18471 + ], + "RattataBackpic": [ + 27, + 25763 + ], + "RattataFrontpic": [ + 28, + 32187 + ], + "RattataPokedexEntry": [ + 104, + 18367 + ], + "ReadTileToAnimBuffer": [ + 63, + 17597 + ], + "RemoraidBackpic": [ + 26, + 24247 + ], + "RemoraidFrontpic": [ + 29, + 19340 + ], + "RemoraidPokedexEntry": [ + 107, + 19716 + ], + "RhydonBackpic": [ + 23, + 20980 + ], + "RhydonFrontpic": [ + 18, + 20606 + ], + "RhydonPokedexEntry": [ + 105, + 21621 + ], + "RhyhornBackpic": [ + 27, + 19435 + ], + "RhyhornFrontpic": [ + 31, + 27661 + ], + "RhyhornPokedexEntry": [ + 105, + 21516 + ], + "RoamMaps": [ + 10, + 26971 + ], + "RockMonMaps": [ + 46, + 25677 + ], + "RoofPals": [ + 2, + 31150 + ], + "Roofs": [ + 7, + 16444 + ], + "RotateUnownFrontpic": [ + 56, + 18459 + ], + "SFX": [ + 58, + 21086 + ], + "SadEmote": [ + 5, + 17609 + ], + "SandshrewBackpic": [ + 23, + 31577 + ], + "SandshrewFrontpic": [ + 29, + 23397 + ], + "SandshrewPokedexEntry": [ + 104, + 19241 + ], + "SandslashBackpic": [ + 23, + 22218 + ], + "SandslashFrontpic": [ + 22, + 22161 + ], + "SandslashPokedexEntry": [ + 104, + 19350 + ], + "ScizorBackpic": [ + 26, + 16384 + ], + "ScizorFrontpic": [ + 31, + 20007 + ], + "ScizorPokedexEntry": [ + 107, + 18512 + ], + "ScrollTileDown": [ + 63, + 17169 + ], + "ScrollTileLeft": [ + 63, + 17102 + ], + "ScrollTileRight": [ + 63, + 17122 + ], + "ScrollTileRightLeft": [ + 63, + 17072 + ], + "ScrollTileUp": [ + 63, + 17142 + ], + "ScytherBackpic": [ + 25, + 29823 + ], + "ScytherFrontpic": [ + 31, + 31850 + ], + "ScytherPokedexEntry": [ + 105, + 22825 + ], + "SeadraBackpic": [ + 23, + 23042 + ], + "SeadraFrontpic": [ + 24, + 17174 + ], + "SeadraPokedexEntry": [ + 105, + 22160 + ], + "SeakingBackpic": [ + 26, + 21052 + ], + "SeakingFrontpic": [ + 18, + 31172 + ], + "SeakingPokedexEntry": [ + 105, + 22383 + ], + "SeelBackpic": [ + 28, + 19260 + ], + "SeelFrontpic": [ + 22, + 21283 + ], + "SeelPokedexEntry": [ + 105, + 18734 + ], + "SentretBackpic": [ + 30, + 18492 + ], + "SentretFrontpic": [ + 29, + 20509 + ], + "SentretPokedexEntry": [ + 106, + 19896 + ], + "ShellderBackpic": [ + 24, + 16779 + ], + "ShellderFrontpic": [ + 27, + 17404 + ], + "ShellderPokedexEntry": [ + 105, + 19188 + ], + "ShockEmote": [ + 5, + 17417 + ], + "Shrink1Pic": [ + 62, + 30142 + ], + "Shrink2Pic": [ + 62, + 30286 + ], + "ShuckleBackpic": [ + 30, + 25942 + ], + "ShuckleFrontpic": [ + 30, + 19275 + ], + "ShucklePokedexEntry": [ + 107, + 18623 + ], + "SkarmoryBackpic": [ + 30, + 24509 + ], + "SkarmoryFrontpic": [ + 31, + 21147 + ], + "SkarmoryPokedexEntry": [ + 107, + 20164 + ], + "SkiploomBackpic": [ + 28, + 29181 + ], + "SkiploomFrontpic": [ + 29, + 18459 + ], + "SkiploomPokedexEntry": [ + 106, + 22930 + ], + "SleepEmote": [ + 5, + 17801 + ], + "SlowbroBackpic": [ + 26, + 31900 + ], + "SlowbroFrontpic": [ + 31, + 23368 + ], + "SlowbroPokedexEntry": [ + 105, + 18070 + ], + "SlowkingBackpic": [ + 22, + 30739 + ], + "SlowkingFrontpic": [ + 31, + 26600 + ], + "SlowkingPokedexEntry": [ + 107, + 17057 + ], + "SlowpokeBackpic": [ + 30, + 26178 + ], + "SlowpokeFrontpic": [ + 29, + 18164 + ], + "SlowpokePokedexEntry": [ + 105, + 17964 + ], + "SlugmaBackpic": [ + 28, + 18942 + ], + "SlugmaFrontpic": [ + 29, + 30683 + ], + "SlugmaPokedexEntry": [ + 107, + 19191 + ], + "SmeargleBackpic": [ + 26, + 17826 + ], + "SmeargleFrontpic": [ + 22, + 19961 + ], + "SmearglePokedexEntry": [ + 107, + 21033 + ], + "SmoochumBackpic": [ + 29, + 21379 + ], + "SmoochumFrontpic": [ + 29, + 18753 + ], + "SmoochumPokedexEntry": [ + 107, + 21364 + ], + "SneaselBackpic": [ + 27, + 16724 + ], + "SneaselFrontpic": [ + 22, + 24772 + ], + "SneaselPokedexEntry": [ + 107, + 18850 + ], + "SnorlaxBackpic": [ + 30, + 32600 + ], + "SnorlaxFrontpic": [ + 22, + 22599 + ], + "SnorlaxPokedexEntry": [ + 106, + 17954 + ], + "SnubbullBackpic": [ + 27, + 24443 + ], + "SnubbullFrontpic": [ + 25, + 32372 + ], + "SnubbullPokedexEntry": [ + 107, + 18172 + ], + "SpawnPoints": [ + 5, + 21273 + ], + "SpearowBackpic": [ + 28, + 27356 + ], + "SpearowFrontpic": [ + 30, + 16649 + ], + "SpearowPokedexEntry": [ + 104, + 18584 + ], + "SpecialPhoneCallList": [ + 36, + 17910 + ], + "SpinarakBackpic": [ + 30, + 24024 + ], + "SpinarakFrontpic": [ + 32, + 32551 + ], + "SpinarakPokedexEntry": [ + 106, + 20577 + ], + "SpriteMons": [ + 5, + 18025 + ], + "SquirtleBackpic": [ + 30, + 24991 + ], + "SquirtleFrontpic": [ + 30, + 26880 + ], + "SquirtlePokedexEntry": [ + 104, + 17046 + ], + "StandingTileFrame": [ + 63, + 17550 + ], + "StandingTileFrame8": [ + 63, + 17062 + ], + "StantlerBackpic": [ + 26, + 17466 + ], + "StantlerFrontpic": [ + 21, + 20654 + ], + "StantlerPokedexEntry": [ + 107, + 20921 + ], + "StarmieBackpic": [ + 29, + 32297 + ], + "StarmieFrontpic": [ + 26, + 31216 + ], + "StarmiePokedexEntry": [ + 105, + 22613 + ], + "StaryuBackpic": [ + 29, + 24253 + ], + "StaryuFrontpic": [ + 28, + 25516 + ], + "StaryuPokedexEntry": [ + 105, + 22501 + ], + "StatsScreen_PlaceFrontpic": [ + 20, + 20675 + ], + "StdScripts": [ + 64, + 16384 + ], + "SteelixBackpic": [ + 24, + 29663 + ], + "SteelixFrontpic": [ + 18, + 22545 + ], + "SteelixPokedexEntry": [ + 107, + 18056 + ], + "StubbedGetFrontpic": [ + 112, + 19066 + ], + "SudowoodoBackpic": [ + 25, + 27987 + ], + "SudowoodoFrontpic": [ + 24, + 24247 + ], + "SudowoodoPokedexEntry": [ + 106, + 22586 + ], + "SuicuneBackpic": [ + 22, + 29894 + ], + "SuicuneFrontpic": [ + 31, + 16540 + ], + "SuicunePokedexEntry": [ + 107, + 22111 + ], + "SunfloraBackpic": [ + 23, + 20565 + ], + "SunfloraFrontpic": [ + 21, + 31221 + ], + "SunfloraPokedexEntry": [ + 106, + 23389 + ], + "SunkernBackpic": [ + 29, + 16682 + ], + "SunkernFrontpic": [ + 30, + 24267 + ], + "SunkernPokedexEntry": [ + 106, + 23272 + ], + "SwarmGrassWildMons": [ + 10, + 32284 + ], + "SwarmWaterWildMons": [ + 10, + 32473 + ], + "SwinubBackpic": [ + 30, + 28722 + ], + "SwinubFrontpic": [ + 30, + 30878 + ], + "SwinubPokedexEntry": [ + 107, + 19400 + ], + "TMHMMoves": [ + 4, + 23142 + ], + "TangelaBackpic": [ + 30, + 29165 + ], + "TangelaFrontpic": [ + 22, + 30317 + ], + "TangelaPokedexEntry": [ + 105, + 21842 + ], + "TaurosBackpic": [ + 30, + 27576 + ], + "TaurosFrontpic": [ + 32, + 18650 + ], + "TaurosPokedexEntry": [ + 105, + 23380 + ], + "TeddiursaBackpic": [ + 29, + 29309 + ], + "TeddiursaFrontpic": [ + 30, + 19014 + ], + "TeddiursaPokedexEntry": [ + 107, + 18963 + ], + "TentacoolBackpic": [ + 29, + 17869 + ], + "TentacoolFrontpic": [ + 29, + 24538 + ], + "TentacoolPokedexEntry": [ + 105, + 17183 + ], + "TentacruelBackpic": [ + 25, + 22036 + ], + "TentacruelFrontpic": [ + 32, + 22229 + ], + "TentacruelPokedexEntry": [ + 105, + 17300 + ], + "Text_BreedHuh": [ + 101, + 18257 + ], + "TheEndGFX": [ + 50, + 31933 + ], + "TilesetBGPalette": [ + 2, + 30558 + ], + "Tilesets": [ + 5, + 22206 + ], + "TimeFishGroups": [ + 36, + 27614 + ], + "TitleScreenGFX1": [ + 38, + 16384 + ], + "TitleScreenGFX2": [ + 38, + 17560 + ], + "TitleScreenGFX3": [ + 57, + 16864 + ], + "TitleScreenGFX4": [ + 57, + 16928 + ], + "TitleScreenTilemap": [ + 38, + 17962 + ], + "TogepiBackpic": [ + 29, + 21957 + ], + "TogepiFrontpic": [ + 30, + 30249 + ], + "TogepiPokedexEntry": [ + 106, + 21466 + ], + "TogeticBackpic": [ + 25, + 20160 + ], + "TogeticFrontpic": [ + 23, + 21806 + ], + "TogeticPokedexEntry": [ + 106, + 21576 + ], + "TotodileBackpic": [ + 27, + 23780 + ], + "TotodileFrontpic": [ + 28, + 23973 + ], + "TotodilePokedexEntry": [ + 106, + 19561 + ], + "TownMapGFX": [ + 62, + 19602 + ], + "TownMapPals.PalMap": [ + 36, + 24236 + ], + "TradeAnim_GetFrontpic": [ + 10, + 21301 + ], + "TradeAnim_ShowFrontpic": [ + 10, + 21351 + ], + "TradeAnim_ShowGetmonFrontpic": [ + 10, + 21348 + ], + "TradeAnim_ShowGivemonFrontpic": [ + 10, + 21343 + ], + "TradeArrowLeftGFX": [ + 10, + 22391 + ], + "TradeArrowRightGFX": [ + 10, + 22375 + ], + "TradeBallGFX": [ + 10, + 22823 + ], + "TradeBubbleGFX": [ + 10, + 22439 + ], + "TradeCableGFX": [ + 10, + 22407 + ], + "TradeGameBoyLZ": [ + 10, + 22503 + ], + "TradeGameBoyTilemap": [ + 10, + 22291 + ], + "TradeLinkTubeTilemap": [ + 10, + 22339 + ], + "TradePoofGFX": [ + 10, + 22919 + ], + "TradeTexts": [ + 63, + 19714 + ], + "TradedForText": [ + 63, + 19749 + ], + "TrainerCard_JohtoBadgesOAM": [ + 9, + 21541 + ], + "TrainerClassAttributes": [ + 14, + 21858 + ], + "TrainerClassNames": [ + 108, + 18773 + ], + "TrainerEncounterMusic": [ + 58, + 20519 + ], + "TrainerGroups": [ + 14, + 22846 + ], + "TrainerPalettes": [ + 2, + 30013 + ], + "TrainerPicPointers": [ + 32, + 16384 + ], + "Trainers": [ + 14, + 22978 + ], + "TreeMonMaps": [ + 46, + 25574 + ], + "TreeMons": [ + 46, + 25712 + ], + "TypeMatchups": [ + 13, + 19713 + ], + "TypeNames": [ + 20, + 18862 + ], + "TyphlosionBackpic": [ + 23, + 29974 + ], + "TyphlosionFrontpic": [ + 32, + 22735 + ], + "TyphlosionPokedexEntry": [ + 106, + 19452 + ], + "TyranitarBackpic": [ + 23, + 21393 + ], + "TyranitarFrontpic": [ + 31, + 19435 + ], + "TyranitarPokedexEntry": [ + 107, + 22451 + ], + "TyrogueBackpic": [ + 26, + 29838 + ], + "TyrogueFrontpic": [ + 28, + 31889 + ], + "TyroguePokedexEntry": [ + 107, + 21139 + ], + "UmbreonBackpic": [ + 25, + 26882 + ], + "UmbreonFrontpic": [ + 25, + 18653 + ], + "UmbreonPokedexEntry": [ + 107, + 16820 + ], + "UnownABackpic": [ + 46, + 20410 + ], + "UnownAFrontpic": [ + 46, + 20656 + ], + "UnownBBackpic": [ + 46, + 19259 + ], + "UnownBFrontpic": [ + 46, + 18857 + ], + "UnownCBackpic": [ + 30, + 31891 + ], + "UnownCFrontpic": [ + 27, + 32578 + ], + "UnownDBackpic": [ + 46, + 17990 + ], + "UnownDFrontpic": [ + 46, + 18140 + ], + "UnownEBackpic": [ + 46, + 18717 + ], + "UnownEFrontpic": [ + 46, + 18576 + ], + "UnownFBackpic": [ + 46, + 17687 + ], + "UnownFFrontpic": [ + 46, + 19127 + ], + "UnownFont": [ + 62, + 29454 + ], + "UnownGBackpic": [ + 46, + 16893 + ], + "UnownGFrontpic": [ + 46, + 16384 + ], + "UnownHBackpic": [ + 30, + 32424 + ], + "UnownHFrontpic": [ + 46, + 16556 + ], + "UnownIBackpic": [ + 46, + 22743 + ], + "UnownIFrontpic": [ + 46, + 22837 + ], + "UnownJBackpic": [ + 46, + 19390 + ], + "UnownJFrontpic": [ + 46, + 19908 + ], + "UnownKBackpic": [ + 46, + 21140 + ], + "UnownKFrontpic": [ + 46, + 20160 + ], + "UnownLBackpic": [ + 46, + 21597 + ], + "UnownLFrontpic": [ + 46, + 21705 + ], + "UnownMBackpic": [ + 46, + 20778 + ], + "UnownMFrontpic": [ + 46, + 21486 + ], + "UnownNBackpic": [ + 46, + 20533 + ], + "UnownNFrontpic": [ + 46, + 20285 + ], + "UnownOBackpic": [ + 46, + 19651 + ], + "UnownOFrontpic": [ + 46, + 21020 + ], + "UnownPBackpic": [ + 46, + 22649 + ], + "UnownPFrontpic": [ + 46, + 22444 + ], + "UnownPicPointers": [ + 31, + 16384 + ], + "UnownPokedexEntry": [ + 107, + 17277 + ], + "UnownPuzzleCursorGFX": [ + 56, + 24530 + ], + "UnownPuzzleStartCancelLZ": [ + 56, + 24594 + ], + "UnownQBackpic": [ + 46, + 22132 + ], + "UnownQFrontpic": [ + 46, + 22341 + ], + "UnownRBackpic": [ + 46, + 22928 + ], + "UnownRFrontpic": [ + 46, + 23018 + ], + "UnownSBackpic": [ + 46, + 18288 + ], + "UnownSFrontpic": [ + 46, + 17217 + ], + "UnownTBackpic": [ + 46, + 21257 + ], + "UnownTFrontpic": [ + 46, + 20899 + ], + "UnownUBackpic": [ + 46, + 21919 + ], + "UnownUFrontpic": [ + 46, + 22237 + ], + "UnownVBackpic": [ + 46, + 17531 + ], + "UnownVFrontpic": [ + 46, + 17375 + ], + "UnownWBackpic": [ + 46, + 20034 + ], + "UnownWFrontpic": [ + 46, + 19521 + ], + "UnownXBackpic": [ + 46, + 21373 + ], + "UnownXFrontpic": [ + 46, + 22026 + ], + "UnownYBackpic": [ + 46, + 22547 + ], + "UnownYFrontpic": [ + 46, + 21812 + ], + "UnownZBackpic": [ + 46, + 19781 + ], + "UnownZFrontpic": [ + 46, + 18992 + ], + "UrsaringBackpic": [ + 29, + 28756 + ], + "UrsaringFrontpic": [ + 32, + 23746 + ], + "UrsaringPokedexEntry": [ + 107, + 19077 + ], + "VaporeonBackpic": [ + 29, + 19047 + ], + "VaporeonFrontpic": [ + 21, + 30316 + ], + "VaporeonPokedexEntry": [ + 106, + 16968 + ], + "VenomothBackpic": [ + 21, + 32123 + ], + "VenomothFrontpic": [ + 32, + 20188 + ], + "VenomothPokedexEntry": [ + 104, + 21676 + ], + "VenonatBackpic": [ + 26, + 25304 + ], + "VenonatFrontpic": [ + 28, + 17985 + ], + "VenonatPokedexEntry": [ + 104, + 21557 + ], + "VenusaurBackpic": [ + 23, + 18481 + ], + "VenusaurFrontpic": [ + 18, + 26336 + ], + "VenusaurPokedexEntry": [ + 104, + 16597 + ], + "VictreebelBackpic": [ + 27, + 30969 + ], + "VictreebelFrontpic": [ + 32, + 21722 + ], + "VictreebelPokedexEntry": [ + 105, + 17074 + ], + "VileplumeBackpic": [ + 25, + 30189 + ], + "VileplumeFrontpic": [ + 23, + 25918 + ], + "VileplumePokedexEntry": [ + 104, + 21239 + ], + "VoltorbBackpic": [ + 30, + 28267 + ], + "VoltorbFrontpic": [ + 46, + 17840 + ], + "VoltorbPokedexEntry": [ + 105, + 20308 + ], + "VulpixBackpic": [ + 26, + 20338 + ], + "VulpixFrontpic": [ + 23, + 17224 + ], + "VulpixPokedexEntry": [ + 104, + 20349 + ], + "WaitTileAnimation": [ + 63, + 17061 + ], + "WartortleBackpic": [ + 26, + 19980 + ], + "WartortleFrontpic": [ + 22, + 29471 + ], + "WartortlePokedexEntry": [ + 104, + 17164 + ], + "WaveSamples": [ + 58, + 19890 + ], + "WeedleBackpic": [ + 30, + 19794 + ], + "WeedleFrontpic": [ + 30, + 27345 + ], + "WeedlePokedexEntry": [ + 104, + 17707 + ], + "WeepinbellBackpic": [ + 27, + 21112 + ], + "WeepinbellFrontpic": [ + 26, + 22832 + ], + "WeepinbellPokedexEntry": [ + 105, + 16954 + ], + "WeezingBackpic": [ + 27, + 16384 + ], + "WeezingFrontpic": [ + 18, + 25088 + ], + "WeezingPokedexEntry": [ + 105, + 21402 + ], + "WigglytuffBackpic": [ + 30, + 19536 + ], + "WigglytuffFrontpic": [ + 24, + 31576 + ], + "WigglytuffPokedexEntry": [ + 104, + 20688 + ], + "WobbuffetBackpic": [ + 29, + 30955 + ], + "WobbuffetFrontpic": [ + 25, + 25028 + ], + "WobbuffetPokedexEntry": [ + 107, + 17376 + ], + "WooperBackpic": [ + 30, + 27113 + ], + "WooperFrontpic": [ + 30, + 28037 + ], + "WooperPokedexEntry": [ + 107, + 16496 + ], + "WriteTileFromAnimBuffer": [ + 63, + 17585 + ], + "WrongNumber.script": [ + 36, + 16960 + ], + "XatuBackpic": [ + 28, + 17345 + ], + "XatuFrontpic": [ + 22, + 24340 + ], + "XatuPokedexEntry": [ + 106, + 21814 + ], + "YanmaBackpic": [ + 22, + 23907 + ], + "YanmaFrontpic": [ + 25, + 26142 + ], + "YanmaPokedexEntry": [ + 107, + 16384 + ], + "ZapdosBackpic": [ + 28, + 26437 + ], + "ZapdosFrontpic": [ + 18, + 28185 + ], + "ZapdosPokedexEntry": [ + 106, + 18182 + ], + "ZubatBackpic": [ + 24, + 17569 + ], + "ZubatFrontpic": [ + 29, + 32030 + ], + "ZubatPokedexEntry": [ + 104, + 20809 + ], + "_AreWeGeniusesText": [ + 100, + 22601 + ], + "_BackAlreadyText": [ + 100, + 22807 + ], + "_BargainShopComeAgainText": [ + 101, + 24234 + ], + "_BargainShopFinalPriceText": [ + 101, + 24084 + ], + "_BargainShopIntroText": [ + 101, + 23979 + ], + "_BargainShopNoFundsText": [ + 101, + 24204 + ], + "_BargainShopPackFullText": [ + 101, + 24122 + ], + "_BargainShopSoldOutText": [ + 101, + 24155 + ], + "_BargainShopThanksText": [ + 101, + 24113 + ], + "_BidsFarewellToMonText": [ + 100, + 18201 + ], + "_BreedAppearsToCareForText": [ + 101, + 18477 + ], + "_BreedAskNicknameText": [ + 101, + 18296 + ], + "_BreedBrimmingWithEnergyText": [ + 101, + 18420 + ], + "_BreedClearboxText": [ + 101, + 18265 + ], + "_BreedEggHatchText": [ + 101, + 18267 + ], + "_BreedFriendlyText": [ + 101, + 18508 + ], + "_BreedNoInterestText": [ + 101, + 18447 + ], + "_BreedShowsInterestText": [ + 101, + 18534 + ], + "_CGB_GSIntro.ShellderLaprasBGPalette": [ + 2, + 22241 + ], + "_CGB_GSIntro.ShellderLaprasOBPals": [ + 2, + 22249 + ], + "_CGB_PackPals.PackPals": [ + 2, + 22895 + ], + "_CantAcceptEggText": [ + 100, + 22429 + ], + "_ComeAgainText": [ + 100, + 22990 + ], + "_ComeBackLaterText": [ + 100, + 22576 + ], + "_DayCareLadyIntroEggText": [ + 100, + 22176 + ], + "_DayCareLadyIntroText": [ + 100, + 22122 + ], + "_DayCareManIntroEggText": [ + 100, + 21949 + ], + "_DayCareManIntroText": [ + 100, + 21902 + ], + "_DaycareDummyText": [ + 100, + 21900 + ], + "_EmptyMailboxText": [ + 101, + 18563 + ], + "_ForYourMonSendsText": [ + 100, + 18255 + ], + "_FoundAnEggText": [ + 100, + 23013 + ], + "_GotBackMonText": [ + 100, + 22788 + ], + "_HallOfFamePC.HOFMaster": [ + 33, + 26331 + ], + "_HallOfFamePC.TimeFamer": [ + 33, + 26347 + ], + "_HaveNoRoomText": [ + 100, + 22919 + ], + "_HerbShopLadyIntroText": [ + 101, + 23692 + ], + "_HerbalLadyComeAgainText": [ + 101, + 23950 + ], + "_HerbalLadyFinalPriceText": [ + 101, + 23826 + ], + "_HerbalLadyHowManyText": [ + 101, + 23815 + ], + "_HerbalLadyNoMoneyText": [ + 101, + 23916 + ], + "_HerbalLadyPackFullText": [ + 101, + 23886 + ], + "_HerbalLadyThanksText": [ + 101, + 23858 + ], + "_IllKeepItThanksText": [ + 100, + 23222 + ], + "_IllRaiseYourMonText": [ + 100, + 22549 + ], + "_LastHealthyMonText": [ + 100, + 22500 + ], + "_LeftWithDayCareLadyText": [ + 101, + 18323 + ], + "_LeftWithDayCareManText": [ + 101, + 18372 + ], + "_MailAlreadyHoldingItemText": [ + 101, + 18673 + ], + "_MailAskSendToPCText": [ + 101, + 21857 + ], + "_MailClearedPutAwayText": [ + 101, + 18585 + ], + "_MailDetachedText": [ + 101, + 21794 + ], + "_MailEggText": [ + 101, + 18705 + ], + "_MailLoseMessageText": [ + 101, + 21757 + ], + "_MailMessageLostText": [ + 101, + 18636 + ], + "_MailMovedFromBoxText": [ + 101, + 18733 + ], + "_MailNoSpaceText": [ + 101, + 21821 + ], + "_MailPackFullText": [ + 101, + 18617 + ], + "_MailSentToPCText": [ + 101, + 21919 + ], + "_MailboxFullText": [ + 101, + 21892 + ], + "_MartAskMoreText": [ + 101, + 24689 + ], + "_MartBoughtText": [ + 101, + 24722 + ], + "_MartCantBuyText": [ + 101, + 24635 + ], + "_MartComeAgainText": [ + 101, + 24669 + ], + "_MartFinalPriceText": [ + 101, + 23660 + ], + "_MartHowManyText": [ + 101, + 23649 + ], + "_MartNoMoneyText": [ + 101, + 24574 + ], + "_MartPackFullText": [ + 101, + 24603 + ], + "_MartSellHowManyText": [ + 101, + 24471 + ], + "_MartSellPriceText": [ + 101, + 24482 + ], + "_MartThanksText": [ + 101, + 24548 + ], + "_MartWelcomeText": [ + 101, + 24518 + ], + "_MonNameBidsFarewellText": [ + 100, + 18223 + ], + "_MonWasSentToText": [ + 100, + 18175 + ], + "_NewPokedexEntry": [ + 16, + 23170 + ], + "_NoRoomForEggText": [ + 100, + 23255 + ], + "_NotEnoughMoneyText": [ + 100, + 22945 + ], + "_NotYetText": [ + 100, + 23003 + ], + "_NothingToSellText": [ + 101, + 24438 + ], + "_OTSendsText": [ + 100, + 18274 + ], + "_OakText1": [ + 101, + 22052 + ], + "_OakText2": [ + 101, + 22163 + ], + "_OakText3": [ + 101, + 22225 + ], + "_OakText4": [ + 101, + 22227 + ], + "_OakText5": [ + 101, + 22335 + ], + "_OakText6": [ + 101, + 22455 + ], + "_OakText7": [ + 101, + 22493 + ], + "_OhFineThenText": [ + 100, + 22974 + ], + "_OnlyOneMonText": [ + 100, + 22396 + ], + "_PCMonHoldingMailText": [ + 101, + 18835 + ], + "_PerfectHeresYourMonText": [ + 100, + 22761 + ], + "_PharmacyComeAgainText": [ + 101, + 24410 + ], + "_PharmacyFinalPriceText": [ + 101, + 24301 + ], + "_PharmacyHowManyText": [ + 101, + 24290 + ], + "_PharmacyIntroText": [ + 101, + 24259 + ], + "_PharmacyNoMoneyText": [ + 101, + 24380 + ], + "_PharmacyPackFullText": [ + 101, + 24349 + ], + "_PharmacyThanksText": [ + 101, + 24335 + ], + "_PokemonRemoveMailText": [ + 101, + 21578 + ], + "_PrepMonFrontpic": [ + 0, + 14783 + ], + "_ReceivedEggText": [ + 100, + 23178 + ], + "_RemoveMailText": [ + 100, + 22463 + ], + "_TakeGoodCareOfEggText": [ + 100, + 23199 + ], + "_TakeGoodCareOfMonText": [ + 100, + 18229 + ], + "_WhatShouldIRaiseText": [ + 100, + 22366 + ], + "_YourMonHasGrownText": [ + 100, + 22651 + ], + "wBaseUnusedBackpic": [ + 1, + 53556 + ], + "wBaseUnusedFrontpic": [ + 1, + 53554 + ] + }, + "tilesets": { + "TILESET_CAVE": {}, + "TILESET_CHAMPIONS_ROOM": {}, + "TILESET_DARK_CAVE": {}, + "TILESET_ELITE_FOUR_ROOM": {}, + "TILESET_FACILITY": {}, + "TILESET_FOREST": {}, + "TILESET_GAME_CORNER": {}, + "TILESET_GATE": {}, + "TILESET_HOUSE": {}, + "TILESET_ICE_PATH": {}, + "TILESET_JOHTO": {}, + "TILESET_JOHTO_MODERN": {}, + "TILESET_KANTO": {}, + "TILESET_LAB": {}, + "TILESET_LIGHTHOUSE": {}, + "TILESET_MANSION": {}, + "TILESET_MART": {}, + "TILESET_PARK": {}, + "TILESET_PLAYERS_HOUSE": {}, + "TILESET_PLAYERS_ROOM": {}, + "TILESET_POKECENTER": {}, + "TILESET_PORT": {}, + "TILESET_RADIO_TOWER": {}, + "TILESET_RUINS_OF_ALPH": {}, + "TILESET_TOWER": {}, + "TILESET_TRADITIONAL_HOUSE": {}, + "TILESET_TRAIN_STATION": {}, + "TILESET_UNDERGROUND": {} + } +}