Compare commits
592 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dbd8abe14c | |||
| 04490c9b9a | |||
| 49d094b14d | |||
| cd7985e4ac | |||
| 61edf3470f | |||
| cd02cd6f33 | |||
| 127e3da909 | |||
| 8fa702e715 | |||
| 3aaaf9936e | |||
| af33c6e810 | |||
| e8eccfd4df | |||
| 241c3345bd | |||
| 20e0692486 | |||
| 0136429d3e | |||
| 01aab1d763 | |||
| 3d2d53362d | |||
| 03838ee1d4 | |||
| 2fabc03841 | |||
| ae6cac89e1 | |||
| 41f02ecfbc | |||
| cef286b170 | |||
| ab94b96a79 | |||
| cc43bd77d2 | |||
| d4dc72d0f4 | |||
| 6d841526b1 | |||
| 8e48bb4e32 | |||
| 2c8c800584 | |||
| 4db97164bb | |||
| 882763cfe1 | |||
| 798f3c25c0 | |||
| 02024fef58 | |||
| a4c51eccea | |||
| a9767e5df1 | |||
| e59175fc89 | |||
| 4e20f4585f | |||
| 1c37867e52 | |||
| 67491e2dac | |||
| 2d85c9595d | |||
| b8138ef850 | |||
| e3fc8380cf | |||
| b9afaaae60 | |||
| 4d03a38067 | |||
| 79ed37699e | |||
| abf0f9e98a | |||
| 3955721bfc | |||
| c3855cebec | |||
| 41e6507665 | |||
| 5482ac590d | |||
| 28440eb936 | |||
| f606840216 | |||
| 6a990bf79f | |||
| ab5fed60fe | |||
| e3a215e081 | |||
| 7fa758602a | |||
| bb979911c8 | |||
| f225f8a6e7 | |||
| ee891fb8fa | |||
| 9d73ff4110 | |||
| 0b7ecf1599 | |||
| aa45f05c63 | |||
| 9234f12ed0 | |||
| 26e0bd227e | |||
| 64ce66a619 | |||
| 9302bbcbf3 | |||
| 1855f838ef | |||
| 11af06a752 | |||
| 05668591ef | |||
| e74beec32d | |||
| 3efb2c9a6c | |||
| d3af63e013 | |||
| ca6bee64b9 | |||
| b52d183dba | |||
| 23e1877a62 | |||
| d8736fe33e | |||
| a74365a4fd | |||
| 1c2163bb5f | |||
| 12c2677dc2 | |||
| ae1b59ce7f | |||
| 02c4de897d | |||
| 3c6242717e | |||
| 3c71afb9fa | |||
| e79107c644 | |||
| 0afce96c8d | |||
| d881997acd | |||
| 8906133b93 | |||
| b9e3c4a689 | |||
| 81f18e244d | |||
| ff91481018 | |||
| fdb3184c71 | |||
| aa3b2a18ec | |||
| d498016a47 | |||
| 492e0344b5 | |||
| a710c64909 | |||
| 943ba5dcbf | |||
| 371edd1a02 | |||
| 9fc6ba5b01 | |||
| 983bea61aa | |||
| ab74f7adf7 | |||
| bdd0bf45bd | |||
| 4b950fc23d | |||
| af183d95ad | |||
| 1d4d5d2337 | |||
| 653b72d86a | |||
| 12b6ef6350 | |||
| c979685a1f | |||
| 5b1d0261ff | |||
| 5bffc89a2f | |||
| 2ce612d5b1 | |||
| 6999e5aecf | |||
| 44a7910c69 | |||
| 85c6fde443 | |||
| 67b6dcc293 | |||
| 24d9f279ec | |||
| 83f93c27a1 | |||
| d756b7cd43 | |||
| 1e6f504a84 | |||
| abb4f3f9f0 | |||
| 5b0209a7ce | |||
| c1e0685e97 | |||
| 3c57b28c6d | |||
| cab62ff7b3 | |||
| 5b6dfed359 | |||
| 91a85a065b | |||
| 32e2c51365 | |||
| 1992fdf398 | |||
| 59ac27971a | |||
| cd0ace2a39 | |||
| 274c728e77 | |||
| 8547127f54 | |||
| 45d799aa98 | |||
| 660c90626d | |||
| d9dc42895f | |||
| 02fd21bcdb | |||
| af00d6ad14 | |||
| 0d4a518a08 | |||
| 05b43ca258 | |||
| 9d6ea845d7 | |||
| bbca4e8e39 | |||
| 49954ec4ad | |||
| 6e94625f2a | |||
| 0399ad040f | |||
| 726ed1102e | |||
| 5bc7d203a5 | |||
| ce204aaf16 | |||
| ed8a89c5ce | |||
| 66e7d9432e | |||
| d59a9522ee | |||
| f1661b62b3 | |||
| a98562f46b | |||
| 365bd24740 | |||
| d50ea4d067 | |||
| 52c96437fa | |||
| 2a7c8ec81b | |||
| bd6c606303 | |||
| 112120e8fe | |||
| 00f6e3a7b4 | |||
| 9cc72f951b | |||
| 0f484682d8 | |||
| 60cf07fb0a | |||
| 493caee715 | |||
| aa6217e581 | |||
| a640f5504f | |||
| e01672b97d | |||
| 9e3d487a08 | |||
| 506cd2c5c5 | |||
| 6cb0ac5666 | |||
| b7d0ff2e16 | |||
| 984cefdc7b | |||
| 6c892cb7c1 | |||
| 52d1ddc5af | |||
| 75cd28435c | |||
| 9aa8b4e371 | |||
| e94d8e32ef | |||
| 3fae3088ca | |||
| cbb08f76ae | |||
| dd503706b0 | |||
| eeb181bcc0 | |||
| 287534cf26 | |||
| d08fce5fd5 | |||
| 0ae29744c8 | |||
| 84e1a5177b | |||
| c7e72149f1 | |||
| ecfca19f11 | |||
| 90a53e509a | |||
| 15fc97a996 | |||
| 35ed326dda | |||
| f4a1dd9e16 | |||
| 6f75a64c46 | |||
| dc04a4e848 | |||
| cd37aa175c | |||
| cb6cfb5556 | |||
| 439e97aee6 | |||
| 5528525888 | |||
| a49ba66c36 | |||
| 9de4db8531 | |||
| dbc48f377a | |||
| 1d77d42fe2 | |||
| 683fee8028 | |||
| a83d18fc51 | |||
| b9e8b00af0 | |||
| e2820e02c5 | |||
| f92364a002 | |||
| 9696c8d3e2 | |||
| 18b2bcd0a7 | |||
| f56970350a | |||
| 863f371e68 | |||
| 1bfc89fdd4 | |||
| 24c5114745 | |||
| 7def560726 | |||
| 6cbd0de77d | |||
| 9a8101df6a | |||
| 78744a6853 | |||
| efce1bb116 | |||
| 104c95a942 | |||
| f6392e8932 | |||
| 88e34ef65c | |||
| 8f0117a145 | |||
| 1f878c3098 | |||
| ae5276b2d1 | |||
| 4dd4c5c463 | |||
| cafa6f3d61 | |||
| f0ed2efe07 | |||
| bbf48c7e9e | |||
| 99d54b82b6 | |||
| 49776ca1eb | |||
| 272305f3a4 | |||
| 89f023c7dd | |||
| afc5a52978 | |||
| 12a04f4188 | |||
| 0fe7a96781 | |||
| a06a49bfed | |||
| 86b90fd4b8 | |||
| d18824cfae | |||
| 12b4c93279 | |||
| af47e19e1a | |||
| 4e666303d0 | |||
| 0fa8206321 | |||
| 78ac154998 | |||
| bbcaac7b71 | |||
| 719ba49a85 | |||
| 54ae20c69b | |||
| f864837cbe | |||
| 21191b8e80 | |||
| 6426e913b6 | |||
| 3d3e42c6a3 | |||
| 6fe106f55c | |||
| ba8ac3d143 | |||
| 1963a5453e | |||
| 59a383736c | |||
| 1820f411ae | |||
| 8327942a9c | |||
| aec7f6001a | |||
| d377518632 | |||
| ad9b03fe56 | |||
| 3b490bc42f | |||
| dacd9abe73 | |||
| f74e21782b | |||
| 8fbe819493 | |||
| 626080d228 | |||
| f56e82de81 | |||
| 7d3950658a | |||
| 3c1246ee06 | |||
| d69965e3b1 | |||
| 30edac03a6 | |||
| dd4aface93 | |||
| b1350fdecd | |||
| f2d9b74490 | |||
| 84d09d0894 | |||
| e61f231316 | |||
| 33998aab5d | |||
| 9ce7379946 | |||
| b692474e24 | |||
| 6cfc85ca83 | |||
| 7b060c19f4 | |||
| 604b9338f9 | |||
| 3a6557ffe2 | |||
| fe139ca7f4 | |||
| 5c617d3e32 | |||
| 8d11c04d43 | |||
| 92f73d8bed | |||
| 8da51aa7ba | |||
| 78f87111de | |||
| e3fcdd0776 | |||
| 6ea59f1a79 | |||
| 51fb4db82e | |||
| 7f78de8718 | |||
| 76aab74bf1 | |||
| a2bf08c6ff | |||
| b831076839 | |||
| 45575a1fd8 | |||
| 56d92246cb | |||
| 4a00f2335c | |||
| e9cb830d71 | |||
| c2666d2b43 | |||
| 9ceec3070d | |||
| f89977a8e7 | |||
| 8d512b95cb | |||
| 0914387c16 | |||
| 105672c132 | |||
| 0dd6ecc219 | |||
| 9ef5c8dead | |||
| 12ff4dba2e | |||
| ff9992dff8 | |||
| 98c08e4b22 | |||
| 0bd3b26acc | |||
| 59b55a8f37 | |||
| 67e6b1fb04 | |||
| 17243a7d8b | |||
| 16b6b98ede | |||
| e4ce1063a1 | |||
| f099593136 | |||
| f944507520 | |||
| 6824949247 | |||
| 4f54255518 | |||
| cfe482e1fc | |||
| 0e5ea26e36 | |||
| d5886e69aa | |||
| 32fa215243 | |||
| dd5d8afd82 | |||
| f19f4e9341 | |||
| 3aec2ca89b | |||
| 0f7261dd92 | |||
| 0886573d34 | |||
| 5c041ea857 | |||
| 30cd9afc92 | |||
| cceb74a025 | |||
| 42540076c1 | |||
| a528868656 | |||
| c82af28fd4 | |||
| d72e5db0a5 | |||
| 9907cfb0a0 | |||
| a3cd5a132e | |||
| 0971b47bec | |||
| f2ea250364 | |||
| d86ac70d9e | |||
| be8023a994 | |||
| a0ab949399 | |||
| 30ed68ae22 | |||
| f2aa376304 | |||
| 1b48862923 | |||
| 25417ffac8 | |||
| 4dfbc5a828 | |||
| f0f5bc7551 | |||
| f002db2929 | |||
| c5791574f5 | |||
| 8c1fbfb429 | |||
| 8e5501a23b | |||
| 50fd0e9c7d | |||
| 17843c2f7a | |||
| 54c8d2706b | |||
| 44bfb1b93f | |||
| 7a78992881 | |||
| 6ed41c3b87 | |||
| 7414c176d9 | |||
| d7581dbece | |||
| eeb8c89d3d | |||
| 2591fde764 | |||
| a8f3a3155c | |||
| 2a927651c5 | |||
| 62ef647811 | |||
| fe4491a87b | |||
| 105bf65e02 | |||
| 8120f113af | |||
| 7e0c64cb78 | |||
| 4afb54c54f | |||
| 74f6b68034 | |||
| 5d1e7ff3c1 | |||
| 3c628140f7 | |||
| 01f68d4038 | |||
| 8bf99c3318 | |||
| f0829ed54f | |||
| 21276e20d1 | |||
| 9fb9648e4c | |||
| af7689a1bc | |||
| 59dba325b9 | |||
| d13064ee64 | |||
| 669c9f4d8b | |||
| 228306f883 | |||
| 0a2ef854e6 | |||
| 61c471343a | |||
| 175bff4b29 | |||
| 95eb1c3a35 | |||
| cf50976d65 | |||
| 3fcee7f951 | |||
| 17a4c83970 | |||
| 9712fb1e9b | |||
| cbf622f4aa | |||
| 7c1cdea2eb | |||
| 2bfa491b6a | |||
| 41fbfc9ba6 | |||
| 1fa7c0eb43 | |||
| 839b238e74 | |||
| ec22d57cd1 | |||
| 23ef283272 | |||
| 327579f9b6 | |||
| e5f9d2903d | |||
| 1f3aa4e111 | |||
| 3f88d1c49a | |||
| 5348ba1c65 | |||
| 2f35fd44a1 | |||
| 470fe70f07 | |||
| 399bf557f3 | |||
| fc2ca6cc26 | |||
| d9d42ec956 | |||
| 5e41c74682 | |||
| 02ad846dfa | |||
| 4e7eda65ed | |||
| 3fabe4f591 | |||
| 81234ef2ab | |||
| 4bd28390ea | |||
| fa886899fc | |||
| a81126a03a | |||
| 164c555bb4 | |||
| e5926893a9 | |||
| dbcaf705c2 | |||
| f6809be81d | |||
| 3ddf70888e | |||
| fe7dcf33ec | |||
| 733450bf86 | |||
| 1dae9622e1 | |||
| da0fa5c9ad | |||
| e576dea676 | |||
| 4cac51a831 | |||
| fef844e220 | |||
| 0f45bb5792 | |||
| c195c5da7d | |||
| cb26367f46 | |||
| 31dc89c0d7 | |||
| c95c3003f7 | |||
| 35b3fa6d9c | |||
| e217d62f42 | |||
| e62801caaf | |||
| e6008bb299 | |||
| 9a49646d8a | |||
| 9934e4d765 | |||
| b6a397460e | |||
| 2b75c07571 | |||
| ebbc55c4d0 | |||
| 4e6ee3629f | |||
| 46bd0f6709 | |||
| e6c1ed8753 | |||
| 2b92562538 | |||
| 078bb1ade6 | |||
| f205464aa4 | |||
| b820d3917c | |||
| fea81f03fc | |||
| 75aa71a886 | |||
| 398eefc380 | |||
| b549e319c2 | |||
| 1a57b67e14 | |||
| 378246728b | |||
| 41e326f376 | |||
| f9adeca5ca | |||
| 838f87c224 | |||
| 5b519165fc | |||
| 0bb0518093 | |||
| 41f549b271 | |||
| 6b012a31ce | |||
| 1a2b23a066 | |||
| f109530c5f | |||
| 4cb132199c | |||
| fe16b3259c | |||
| 2e46954fe0 | |||
| aedc63c40d | |||
| 00d13d3c35 | |||
| 1b8b3ad538 | |||
| 5f75cfd691 | |||
| 5f89def2ce | |||
| 7c26eb9a24 | |||
| d951fe8fc5 | |||
| ac01135ca8 | |||
| 5e89e35e02 | |||
| 7e7afeaf82 | |||
| c6fa6d294b | |||
| fc2d17fc78 | |||
| 6fb5aa08ff | |||
| 9f072285a6 | |||
| c58ebe4b9b | |||
| 0dd187fe30 | |||
| ea78792c03 | |||
| ba8aa7b263 | |||
| 243cb6a477 | |||
| c987fefede | |||
| 9954225701 | |||
| 2a038f7112 | |||
| 6bb2e078c0 | |||
| 323c59e54f | |||
| d26d63ed38 | |||
| 2009df3dd1 | |||
| 548e75a15e | |||
| 339c85b590 | |||
| 0f0420314b | |||
| a08b5e65f7 | |||
| 99b89c4e5e | |||
| 20586e0c48 | |||
| b54d0c605a | |||
| 2f75af902c | |||
| 9147a6413e | |||
| 9a467d4bf1 | |||
| e40f9337b6 | |||
| 1f0712d2d5 | |||
| 345bc9519a | |||
| 0b30bb2eff | |||
| f0e88aa581 | |||
| 317893bd61 | |||
| bd181271d9 | |||
| 4df09010a8 | |||
| 1a5b2b96dc | |||
| 43d89813fc | |||
| 5bc76ce6fe | |||
| f0f3e9634b | |||
| 1fe35335a5 | |||
| ca1beaefe7 | |||
| 24cf1b3317 | |||
| 88e2ec2042 | |||
| be60c7c1d5 | |||
| 9be21380c7 | |||
| 22fd196045 | |||
| d5293dffb8 | |||
| 9326b07583 | |||
| 90437a5d8d | |||
| c4ec5dc1c5 | |||
| 3b19826f1e | |||
| 95e4715bc3 | |||
| 81edc97d86 | |||
| 92c8bac714 | |||
| 1df04aa18d | |||
| 8654444d1a | |||
| 772c39edb8 | |||
| 74e7474349 | |||
| 3aefe41ea1 | |||
| 2223c31e93 | |||
| 568f0fa9eb | |||
| e91cf0f0c2 | |||
| 8a1f583d88 | |||
| 7518319c80 | |||
| 304df44742 | |||
| e92bab6f4f | |||
| 43755c5457 | |||
| d4455c508f | |||
| 7d01242cd1 | |||
| 9d77189cec | |||
| c8adbdcc64 | |||
| ac6dfe7134 | |||
| b1ad7c7254 | |||
| 218b9249a2 | |||
| 6fb5602cb0 | |||
| b942d37944 | |||
| b7ee191b6c | |||
| 4ea24a0a6f | |||
| 6ad8c8d73d | |||
| 8982952fa4 | |||
| b135878824 | |||
| e9463d39aa | |||
| dcee7ca8e4 | |||
| 1653aa0a5d | |||
| 2699c9a2f9 | |||
| efd81d8e34 | |||
| 925b224eda | |||
| 7504753ea8 | |||
| da60f40dfc | |||
| 560ebc5997 | |||
| 361d4b81df | |||
| 83cf1fdb00 | |||
| 228a0baf81 | |||
| df7cea4387 | |||
| 13b55a797e | |||
| 1ec33b1374 | |||
| c06991e03d | |||
| 2fcd7abbfd | |||
| 5ca17cfc99 | |||
| 05f1e3852c | |||
| 3b22a45a23 | |||
| f172d3129f | |||
| a7a84b6fab | |||
| d4afa09e03 | |||
| 50947eec2a | |||
| 2624bb0210 | |||
| 6e9685f302 | |||
| 898bf0c71e | |||
| 5a7add8eaa | |||
| 66334dfacd | |||
| a33f3b1ceb | |||
| 6e724cedf7 | |||
| e949c78639 | |||
| 9bcfdb1f0d | |||
| fccb122c59 | |||
| 33899c237e | |||
| 0fad222569 | |||
| ddcce5b95a | |||
| 6fd9741fab | |||
| caa65182dc |
@@ -22,12 +22,14 @@ body:
|
||||
id: game
|
||||
attributes:
|
||||
label: Which game were you playing
|
||||
description: Pick every version you saw the bug in.
|
||||
description: Pick every version you saw the bug in. Use N/A if it isn't game-specific.
|
||||
multiple: true
|
||||
options:
|
||||
- Red
|
||||
- Blue
|
||||
- Yellow
|
||||
- Gold
|
||||
- N/A
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -30,13 +30,14 @@ body:
|
||||
id: game
|
||||
attributes:
|
||||
label: Which game is this about
|
||||
description: Pick every version it applies to.
|
||||
description: Pick every version it applies to. Use N/A if it isn't game-specific.
|
||||
multiple: true
|
||||
options:
|
||||
- Red
|
||||
- Blue
|
||||
- Yellow
|
||||
- Not version-specific
|
||||
- Gold
|
||||
- N/A
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -27,13 +27,14 @@ body:
|
||||
id: game
|
||||
attributes:
|
||||
label: Which game is this for
|
||||
description: Pick every version the mod should cover.
|
||||
description: Pick every version the mod should cover. Use N/A if it isn't game-specific.
|
||||
multiple: true
|
||||
options:
|
||||
- Red
|
||||
- Blue
|
||||
- Yellow
|
||||
- Not version-specific
|
||||
- Gold
|
||||
- N/A
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
|
After Width: | Height: | Size: 626 KiB |
|
After Width: | Height: | Size: 582 KiB |
|
After Width: | Height: | Size: 472 KiB |
|
After Width: | Height: | Size: 730 KiB |
@@ -10,8 +10,6 @@ name: ci
|
||||
# The T3 content tier asserts Pokemon Red facts; scripts/test.sh detects
|
||||
# data/generated/ is absent and skips it rather than failing.
|
||||
#
|
||||
# Runs alongside release.yml, which is untouched by this file.
|
||||
|
||||
on:
|
||||
push:
|
||||
# Integration branch + release branch. PRs already run via pull_request
|
||||
@@ -24,12 +22,360 @@ concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
ios-changes:
|
||||
name: detect iOS changes
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
changed: ${{ steps.paths.outputs.changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: paths
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
|
||||
HEAD_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -Eq '^(mobile/ios/|scripts/build_ios\.sh$)'; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
ios-build:
|
||||
name: iOS build
|
||||
needs: ios-changes
|
||||
if: needs.ios-changes.outputs.changed == 'true'
|
||||
runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }}
|
||||
outputs:
|
||||
ipa_url: ${{ steps.upload-ipa.outputs.artifact-url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: import signing certificate
|
||||
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||
run: |
|
||||
keychain_path="$RUNNER_TEMP/gen1recomp-ci-signing.keychain-db"
|
||||
ci_dir="${POKEMON_CI_DIR:-$HOME/.config/pokemon-ci}"
|
||||
p12="$ci_dir/signing.p12"
|
||||
passfile="$ci_dir/signing.pass"
|
||||
[ -f "$p12" ] && [ -f "$passfile" ] || exit 1
|
||||
p12pw="$(cat "$passfile")"
|
||||
kcpw="$(openssl rand -base64 24)"
|
||||
echo "::add-mask::$kcpw"
|
||||
security delete-keychain "$keychain_path" 2>/dev/null || true
|
||||
security create-keychain -p "$kcpw" "$keychain_path"
|
||||
security set-keychain-settings "$keychain_path"
|
||||
security unlock-keychain -p "$kcpw" "$keychain_path"
|
||||
security import "$p12" -P "$p12pw" -k "$keychain_path" -T /usr/bin/codesign -T /usr/bin/security
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$kcpw" "$keychain_path" >/dev/null
|
||||
existing="$(security list-keychains -d user | sed -e 's/^[[:space:]]*//' -e 's/"//g')"
|
||||
security list-keychains -d user -s "$keychain_path" $existing
|
||||
- name: install xcbeautify
|
||||
run: brew list xcbeautify >/dev/null 2>&1 || brew install xcbeautify
|
||||
- name: build iOS release
|
||||
env:
|
||||
CANONICAL_REPOSITORY: ${{ github.repository == 'bryanthaboi/gen1recomp' }}
|
||||
run: |
|
||||
if [ "$CANONICAL_REPOSITORY" = true ]; then
|
||||
scripts/build_ios.sh --fetch --device --release
|
||||
else
|
||||
scripts/build_ios.sh --fetch --release
|
||||
fi
|
||||
- name: upload iOS release artifact
|
||||
id: upload-ipa
|
||||
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gen1recomp-ios-ipa
|
||||
path: dist/ios/gen1recomp.ipa
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
- name: clean up signing keychain
|
||||
if: ${{ always() && github.repository == 'bryanthaboi/gen1recomp' }}
|
||||
run: security delete-keychain "$RUNNER_TEMP/gen1recomp-ci-signing.keychain-db" 2>/dev/null || true
|
||||
|
||||
switch-changes:
|
||||
name: detect Switch changes
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
changed: ${{ steps.paths.outputs.changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: paths
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
|
||||
HEAD_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -Eq '^(scripts/build_switch\.sh$|scripts/switch/|docs/switch-.*\.md$|tests/switch_ci_workflows_test\.lua$|tests/switch_transfer_docs_test\.lua$|\.github/workflows/(ci|release|switch-artifact-comment)\.yml$|src/core/(NxAssetOverlay|Platform|GameVersion)\.lua$|src/import/CacheFs\.lua$|tests/engine/(assets_version_fallback|nx_generated_guard|nx_yellow_boot|switch_diagnostics)_test\.lua$|tests/engine/platform_nx)'; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
switch-selftest:
|
||||
name: Switch offline selftest
|
||||
needs: switch-changes
|
||||
if: needs.switch-changes.outputs.changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: install luajit
|
||||
run: sudo apt-get update && sudo apt-get install -y luajit
|
||||
- name: Switch offline selftest
|
||||
run: bash scripts/switch/selftest_build_switch.sh
|
||||
- name: verify_payload self-test
|
||||
run: bash scripts/switch/verify_payload.sh --self-test
|
||||
- name: Switch CI workflow content gate
|
||||
run: luajit tests/switch_ci_workflows_test.lua
|
||||
- name: Switch transfer docs content gate
|
||||
run: luajit tests/switch_transfer_docs_test.lua
|
||||
# NX runtime regressions gate this job via switch-changes; run the NX
|
||||
# engine suites here too so a PR touching them gets feedback on the
|
||||
# fork-safe ubuntu runner before the self-hosted Mac build.
|
||||
- name: NX engine suites (headless)
|
||||
run: |
|
||||
luajit tests/engine/assets_version_fallback_test.lua
|
||||
luajit tests/engine/nx_generated_guard_test.lua
|
||||
luajit tests/engine/nx_yellow_boot_test.lua
|
||||
|
||||
switch-build:
|
||||
name: Switch fused build
|
||||
needs: [switch-changes, switch-selftest]
|
||||
if: |
|
||||
always()
|
||||
&& needs.switch-changes.outputs.changed == 'true'
|
||||
&& needs.switch-selftest.result == 'success'
|
||||
&& github.repository == 'bryanthaboi/gen1recomp'
|
||||
&& (github.event_name != 'pull_request'
|
||||
|| github.event.pull_request.head.repo.full_name == github.repository)
|
||||
runs-on: ["self-hosted", "macOS"]
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Build Switch fused NRO
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VER="$(printf '%s' "$GITHUB_SHA" | cut -c1-7)"
|
||||
scripts/build_switch.sh --fetch --fused --version "$VER"
|
||||
echo "SWITCH_VER=$VER" >> "$GITHUB_ENV"
|
||||
- name: upload Switch NRO artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gen1recomp-switch-nro
|
||||
path: |
|
||||
dist/switch/gen1recomp-${{ env.SWITCH_VER }}-switch.nro
|
||||
dist/switch/gen1recomp-${{ env.SWITCH_VER }}-switch.nro.sha256
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
xbox-uwp-changes:
|
||||
name: detect Xbox UWP changes
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
changed: ${{ steps.paths.outputs.changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: paths
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
|
||||
HEAD_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -Eq '^(ports/uwp/|scripts/build_xbox_uwp\.sh$|scripts/xbox-uwp/|scripts/pack_love\.sh$|\.github/workflows/(ci|release)\.yml$|src/core/Platform\.lua$|src/import/(CacheFs|LauncherView|RomImporter)\.lua$|src/update/Check\.lua$|tests/engine/(platform_nx|uwp_baseroms|uwp_native_picker)_test\.lua$|tests/rom_importer_double_pick_test\.lua$)'; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
xbox-uwp-selftest:
|
||||
name: Xbox UWP offline selftest
|
||||
needs: xbox-uwp-changes
|
||||
if: needs.xbox-uwp-changes.outputs.changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Xbox UWP offline selftest
|
||||
run: bash scripts/xbox-uwp/selftest_build_xbox_uwp.sh
|
||||
- name: Build shared payload
|
||||
run: |
|
||||
scripts/pack_love.sh \
|
||||
--output .bazinga/work/ci-game.love \
|
||||
--listing .bazinga/work/ci-love-listing.txt \
|
||||
--version 0.0.0
|
||||
- name: Upload shared payload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gen1recomp-xbox-uwp-payload
|
||||
path: .bazinga/work/ci-game.love
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
xbox-uwp-build:
|
||||
name: Xbox UWP build
|
||||
needs: [xbox-uwp-changes, xbox-uwp-selftest]
|
||||
if: |
|
||||
always()
|
||||
&& needs.xbox-uwp-changes.outputs.changed == 'true'
|
||||
&& needs.xbox-uwp-selftest.result == 'success'
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Download shared payload
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: gen1recomp-xbox-uwp-payload
|
||||
path: .bazinga/work
|
||||
- name: Build Xbox UWP package
|
||||
shell: bash
|
||||
run: |
|
||||
bash scripts/build_xbox_uwp.sh \
|
||||
--release \
|
||||
--version 0.0.0 \
|
||||
--game-love .bazinga/work/ci-game.love
|
||||
- name: Upload Xbox UWP package
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gen1recomp-xbox-uwp
|
||||
path: |
|
||||
dist/xbox-uwp/gen1recomp-0.0.0-xbox-uwp.zip
|
||||
dist/xbox-uwp/gen1recomp-0.0.0-xbox-uwp.zip.sha256
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
linux-arm64-changes:
|
||||
name: detect Linux arm64 changes
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
changed: ${{ steps.paths.outputs.changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: paths
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
|
||||
HEAD_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -Eq '^(scripts/build_linux_arm64\.sh$|scripts/linux-arm64/|scripts/pack_love\.sh$|docs/linux-arm64-build\.md$|\.github/workflows/(ci|release)\.yml$)'; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
linux-arm64-selftest:
|
||||
name: Linux arm64 offline selftest
|
||||
needs: linux-arm64-changes
|
||||
if: needs.linux-arm64-changes.outputs.changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
# Deliberately on x86_64: everything this gate checks (pins, the
|
||||
# host-arch guard, the dependency exclude list, the AppRun fusion
|
||||
# contract) is answerable without a container or an aarch64 machine,
|
||||
# so the slow native job below only ever starts on a sane tree.
|
||||
- name: Linux arm64 offline selftest
|
||||
run: bash scripts/linux-arm64/selftest_build_linux_arm64.sh
|
||||
|
||||
linux-arm64-build:
|
||||
name: Linux arm64 AppImage build
|
||||
needs: [linux-arm64-changes, linux-arm64-selftest]
|
||||
if: |
|
||||
always()
|
||||
&& needs.linux-arm64-changes.outputs.changed == 'true'
|
||||
&& needs.linux-arm64-selftest.result == 'success'
|
||||
# No fork restriction, unlike switch-build: this needs no secrets and no
|
||||
# self-hosted hardware, just GitHub's free arm64 runner for public repos,
|
||||
# so contributors get the same coverage on their own PRs.
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Build the aarch64 AppImage
|
||||
run: |
|
||||
set -euo pipefail
|
||||
scripts/build_linux_arm64.sh --version 0.0.0
|
||||
- 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; }
|
||||
- name: Upload the AppImage
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gen1recomp-linux-arm64
|
||||
path: |
|
||||
dist/linux-arm64/gen1recomp-0.0.0-linux-arm64.AppImage
|
||||
dist/linux-arm64/gen1recomp-0.0.0-linux-arm64.AppImage.sha256
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
headless:
|
||||
name: headless suites (no ROM)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
# LuaJIT, not lua5.4: LOVE 11.x embeds LuaJIT 2.1 and the engine is
|
||||
# written to Lua 5.1 semantics, so CI must run the interpreter the
|
||||
@@ -37,6 +383,9 @@ jobs:
|
||||
- name: install luajit
|
||||
run: sudo apt-get update && sudo apt-get install -y luajit
|
||||
|
||||
- name: install Pillow
|
||||
run: python3 -m pip install --upgrade pillow
|
||||
|
||||
- name: interpreter version
|
||||
run: luajit -v
|
||||
|
||||
@@ -47,7 +396,7 @@ jobs:
|
||||
name: fixture dataset integrity
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
- run: sudo apt-get update && sudo apt-get install -y luajit
|
||||
- run: python3 -m pip install --upgrade pillow
|
||||
|
||||
@@ -89,7 +438,7 @@ jobs:
|
||||
name: screenshot differ (capture not yet wired)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
- run: python3 -m pip install --upgrade pillow
|
||||
|
||||
# 21-testing-and-ci §"Testing & acceptance criteria": compare_shots
|
||||
@@ -137,7 +486,7 @@ jobs:
|
||||
name: mod lint (no ROM-derived content)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
# the MK305 dump check key-diffs shipped tables through luajit, and
|
||||
# modkit treats a missing interpreter as a fatal MK100 -- without
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: iOS artifact comment
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [ci]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: artifact
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
HEAD_REPOSITORY: ${{ github.event.workflow_run.head_repository.full_name }}
|
||||
run: |
|
||||
artifact_id="$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts" --jq '.artifacts[] | select(.name == "gen1recomp-ios-ipa") | .id')"
|
||||
[ -n "$artifact_id" ] || exit 0
|
||||
head_owner="${HEAD_REPOSITORY%%/*}"
|
||||
pr_number="$(gh api "repos/$GITHUB_REPOSITORY/pulls?state=open&head=$head_owner:$HEAD_BRANCH" --jq '.[0].number // empty')"
|
||||
[ -n "$pr_number" ] || exit 0
|
||||
echo "artifact_url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts/$artifact_id" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
|
||||
# Upsert via comment-tag only — do not delete-all bot comments (clobbers Switch).
|
||||
- name: Get build info
|
||||
id: build-info
|
||||
env:
|
||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
run: |
|
||||
commit_hash="$(printf '%s' "$HEAD_SHA" | cut -c1-7)"
|
||||
build_time="$(date "+%Y-%m-%d %H:%M:%S")"
|
||||
echo "hash=$commit_hash" >> "$GITHUB_OUTPUT"
|
||||
echo "time=$build_time" >> "$GITHUB_OUTPUT"
|
||||
- name: comment iOS artifact
|
||||
if: steps.artifact.outputs.pr_number != ''
|
||||
uses: thollander/actions-comment-pull-request@v3
|
||||
with:
|
||||
message: |
|
||||
[gen1recomp.ipa](${{ steps.artifact.outputs.artifact_url }})
|
||||
|
||||
**Commit**: [#${{ steps.build-info.outputs.hash }}](https://github.com/${{ github.event.workflow_run.head_repository.full_name }}/commit/${{ github.event.workflow_run.head_sha }})
|
||||
**Build Time**: `${{ steps.build-info.outputs.time }}`
|
||||
|
||||
<sub>This comment was automatically generated. [View workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }})</sub>
|
||||
pr-number: ${{ steps.artifact.outputs.pr_number }}
|
||||
comment-tag: ios-build-result
|
||||
github-token: ${{ github.token }}
|
||||
@@ -1,8 +1,9 @@
|
||||
name: Release
|
||||
|
||||
# Builds the macOS, Windows, and Linux desktop apps, an Android APK, an iOS
|
||||
# IPA, and the Anbernic RG34XXSP (Stock OS 64-bit MOD / PortMaster) port on
|
||||
# the self-hosted Mac runner, and publishes them as a GitHub Release.
|
||||
# IPA, a Nintendo Switch SD-ready zip (experimental), Xbox UWP, and the Anbernic
|
||||
# RG34XXSP (Stock OS 64-bit MOD / PortMaster) port, then publishes them as a
|
||||
# GitHub Release.
|
||||
#
|
||||
# Versioning:
|
||||
# - First ever release is 0.1.0.
|
||||
@@ -24,6 +25,7 @@ on:
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- '**.md'
|
||||
- 'mobile/ios/app-repo.json'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
@@ -41,16 +43,17 @@ concurrency:
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: [self-hosted, macOS]
|
||||
|
||||
version:
|
||||
name: determine release version
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.ver.outputs.version }}
|
||||
tag: ${{ steps.ver.outputs.tag }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Determine version
|
||||
id: ver
|
||||
env:
|
||||
@@ -58,7 +61,6 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
semver_re='^[0-9]+\.[0-9]+\.[0-9]+$'
|
||||
|
||||
# 1) Explicit override from a manual run.
|
||||
@@ -88,7 +90,6 @@ jobs:
|
||||
| grep -E "$semver_re" \
|
||||
| sort -t. -k1,1n -k2,2n -k3,3n \
|
||||
| tail -1 || true)"
|
||||
|
||||
if [ -z "$latest" ]; then
|
||||
version="0.1.0"
|
||||
echo "No existing release tag; starting at $version"
|
||||
@@ -116,11 +117,159 @@ jobs:
|
||||
echo "::error::Release $tag already exists. Pick a different version."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
|
||||
love-payload:
|
||||
name: build release game.love
|
||||
needs: version
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Build shared payload
|
||||
run: |
|
||||
scripts/pack_love.sh \
|
||||
--output dist/payload/game.love \
|
||||
--listing dist/payload/love-listing.txt \
|
||||
--version "${{ needs.version.outputs.version }}"
|
||||
- name: Upload shared payload
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gen1recomp-release-love
|
||||
path: dist/payload/game.love
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
linux-arm64:
|
||||
name: build Linux arm64 AppImage
|
||||
needs: [version, love-payload]
|
||||
# GitHub's free arm64 runner for public repos. It has to be arm64: the
|
||||
# AppImage compiles LÖVE natively inside a Debian bullseye arm64
|
||||
# container, and the qemu-emulated alternative takes hours.
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Download shared payload
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: gen1recomp-release-love
|
||||
path: .bazinga/work
|
||||
- name: Build Linux arm64 AppImage
|
||||
run: |
|
||||
set -euo pipefail
|
||||
scripts/build_linux_arm64.sh \
|
||||
--version "${{ needs.version.outputs.version }}" \
|
||||
--game-love .bazinga/work/game.love
|
||||
- name: Upload Linux arm64 release
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gen1recomp-linux-arm64-release
|
||||
path: |
|
||||
dist/linux-arm64/gen1recomp-${{ needs.version.outputs.version }}-linux-arm64.AppImage
|
||||
dist/linux-arm64/gen1recomp-${{ needs.version.outputs.version }}-linux-arm64.AppImage.sha256
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
xbox-uwp:
|
||||
name: build Xbox UWP release
|
||||
needs: [version, love-payload]
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Download shared payload
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: gen1recomp-release-love
|
||||
path: .bazinga/work
|
||||
- name: Prepare signing certificate
|
||||
shell: pwsh
|
||||
env:
|
||||
CERTIFICATE_BASE64: ${{ secrets.XBOX_UWP_SIGNING_CERTIFICATE }}
|
||||
CERTIFICATE_PASSWORD: ${{ secrets.XBOX_UWP_SIGNING_PASSWORD }}
|
||||
CANONICAL_REPOSITORY: ${{ github.repository == 'bryanthaboi/gen1recomp' }}
|
||||
run: |
|
||||
if ($env:CANONICAL_REPOSITORY -eq 'true' -and
|
||||
[string]::IsNullOrWhiteSpace($env:CERTIFICATE_BASE64)) {
|
||||
throw 'XBOX_UWP_SIGNING_CERTIFICATE is not configured.'
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($env:CERTIFICATE_BASE64)) {
|
||||
"UWP_PUBLISHER=CN=Gen1Recomp" | Out-File $env:GITHUB_ENV -Append
|
||||
exit 0
|
||||
}
|
||||
$pfx = Join-Path $env:RUNNER_TEMP 'gen1recomp-uwp.pfx'
|
||||
[IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($env:CERTIFICATE_BASE64))
|
||||
$flags = [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::EphemeralKeySet
|
||||
$cert = [Security.Cryptography.X509Certificates.X509Certificate2]::new(
|
||||
$pfx, $env:CERTIFICATE_PASSWORD, $flags)
|
||||
$cer = Join-Path $env:RUNNER_TEMP 'gen1recomp-uwp.cer'
|
||||
[IO.File]::WriteAllBytes(
|
||||
$cer,
|
||||
$cert.Export([Security.Cryptography.X509Certificates.X509ContentType]::Cert))
|
||||
Import-Certificate -FilePath $cer -CertStoreLocation Cert:\LocalMachine\TrustedPeople | Out-Null
|
||||
"UWP_PFX=$pfx" | Out-File $env:GITHUB_ENV -Append
|
||||
"UWP_CERT_THUMBPRINT=$($cert.Thumbprint)" | Out-File $env:GITHUB_ENV -Append
|
||||
"UWP_PUBLISHER=$($cert.Subject)" | Out-File $env:GITHUB_ENV -Append
|
||||
- name: Build Xbox UWP package
|
||||
shell: bash
|
||||
run: |
|
||||
bash scripts/build_xbox_uwp.sh \
|
||||
--release \
|
||||
--version "${{ needs.version.outputs.version }}" \
|
||||
--publisher "$UWP_PUBLISHER" \
|
||||
--game-love .bazinga/work/game.love
|
||||
- name: Sign and stage Xbox UWP release
|
||||
shell: pwsh
|
||||
env:
|
||||
CERTIFICATE_PASSWORD: ${{ secrets.XBOX_UWP_SIGNING_PASSWORD }}
|
||||
run: |
|
||||
if (-not $env:UWP_PFX) {
|
||||
exit 0
|
||||
}
|
||||
scripts/xbox-uwp/stage_release.ps1 `
|
||||
-Version '${{ needs.version.outputs.version }}' `
|
||||
-Configuration Release `
|
||||
-BuildInfo .bazinga/work/xbox-uwp-build-info.json `
|
||||
-CertificatePath $env:UWP_PFX `
|
||||
-CertificatePassword $env:CERTIFICATE_PASSWORD
|
||||
- name: Upload Xbox UWP release
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gen1recomp-xbox-uwp-release
|
||||
path: |
|
||||
dist/xbox-uwp/gen1recomp-${{ needs.version.outputs.version }}-xbox-uwp.zip
|
||||
dist/xbox-uwp/gen1recomp-${{ needs.version.outputs.version }}-xbox-uwp.zip.sha256
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
- name: Remove signing certificate
|
||||
if: always()
|
||||
shell: pwsh
|
||||
run: |
|
||||
if ($env:UWP_CERT_THUMBPRINT) {
|
||||
Remove-Item "Cert:\LocalMachine\TrustedPeople\$env:UWP_CERT_THUMBPRINT" -ErrorAction SilentlyContinue
|
||||
}
|
||||
if ($env:UWP_PFX) {
|
||||
Remove-Item $env:UWP_PFX -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
release:
|
||||
needs: [version, xbox-uwp, linux-arm64]
|
||||
runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }}
|
||||
|
||||
steps:
|
||||
# The self-hosted runner lives under the machine owner's home
|
||||
# directory; mask it first so absolute paths in every later step's
|
||||
# output show up as *** in the public workflow logs.
|
||||
- name: Mask runner paths
|
||||
run: echo "::add-mask::$HOME"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Import signing certificate into a temporary keychain
|
||||
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
KEYCHAIN_PATH="$RUNNER_TEMP/pokemon-signing.keychain-db"
|
||||
@@ -163,30 +312,51 @@ jobs:
|
||||
# notarize separately below so it uses secret credentials, not a
|
||||
# login-keychain profile. "all" also builds the Linux AppImage,
|
||||
# which needs no signing/notarization.
|
||||
scripts/build.sh all --version "${{ steps.ver.outputs.version }}" --no-notarize
|
||||
scripts/build.sh all --version "${{ needs.version.outputs.version }}" --no-notarize
|
||||
|
||||
- name: Build Android
|
||||
run: |
|
||||
set -euo pipefail
|
||||
scripts/build_android.sh --version "${{ steps.ver.outputs.version }}"
|
||||
scripts/build_android.sh --version "${{ needs.version.outputs.version }}"
|
||||
|
||||
- name: Build iOS
|
||||
- name: Install xcbeautify
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Device Release IPA; signs with the Apple Development identity on
|
||||
# the runner (auto team detection). Users on other Apple IDs still
|
||||
# re-sign or build via docs/ios-install.md.
|
||||
scripts/build_ios.sh --fetch --device --release \
|
||||
--version "${{ steps.ver.outputs.version }}"
|
||||
brew list xcbeautify >/dev/null 2>&1 || brew install xcbeautify
|
||||
|
||||
- name: Build iOS
|
||||
env:
|
||||
CANONICAL_REPOSITORY: ${{ github.repository == 'bryanthaboi/gen1recomp' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ "$CANONICAL_REPOSITORY" = true ]; then
|
||||
scripts/build_ios.sh --fetch --device --release \
|
||||
--version "${{ needs.version.outputs.version }}"
|
||||
else
|
||||
scripts/build_ios.sh --fetch --release \
|
||||
--version "${{ needs.version.outputs.version }}"
|
||||
fi
|
||||
|
||||
- name: Build Switch
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Hard-fail gate: Switch ships with every release (never soft-fail).
|
||||
# PR CI is path-gated (ubuntu selftest + canonical fused); release
|
||||
# always builds Switch regardless of which files changed.
|
||||
# Needs native switch-tools (nacptool/elf2nro) and/or Docker on the
|
||||
# Mac self-hosted runner; see docs/switch-build.md.
|
||||
scripts/build_switch.sh --fetch --fused \
|
||||
--version "${{ needs.version.outputs.version }}"
|
||||
|
||||
- name: Build Anbernic RG34XXSP port
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Self-contained aarch64 PortMaster-style pack; pulls the LÖVE 11.5
|
||||
# runtime from PortMaster-GUI, so it needs no signing/notarization.
|
||||
./build-rg34xxsp.sh --version "${{ steps.ver.outputs.version }}"
|
||||
./build-rg34xxsp.sh --version "${{ needs.version.outputs.version }}"
|
||||
|
||||
- name: Notarize & staple macOS app
|
||||
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ci_dir="${POKEMON_CI_DIR:-$HOME/.config/pokemon-ci}"
|
||||
@@ -220,17 +390,41 @@ jobs:
|
||||
ditto -c -k --sequesterRsrc --keepParent "$app" "$zip"
|
||||
echo "Notarized + stapled ✓"
|
||||
|
||||
- name: Download Xbox UWP release
|
||||
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: gen1recomp-xbox-uwp-release
|
||||
path: dist/xbox-uwp
|
||||
|
||||
- name: Download Linux arm64 release
|
||||
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: gen1recomp-linux-arm64-release
|
||||
path: dist/linux-arm64
|
||||
|
||||
- name: Stage release assets
|
||||
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||
id: assets
|
||||
run: |
|
||||
set -euo pipefail
|
||||
v="${{ steps.ver.outputs.version }}"
|
||||
v="${{ needs.version.outputs.version }}"
|
||||
outdir="dist/release"
|
||||
rm -rf "$outdir"
|
||||
mkdir -p "$outdir"
|
||||
cp "dist/mac/gen1recomp-macos.zip" "$outdir/gen1recomp-${v}-macos.zip"
|
||||
cp "dist/win/gen1recomp-win64.zip" "$outdir/gen1recomp-${v}-windows.zip"
|
||||
cp "dist/linux/gen1recomp-linux.zip" "$outdir/gen1recomp-${v}-linux.zip"
|
||||
|
||||
# arm64 desktop Linux (Raspberry Pi, Armbian, arm64 VMs). Built on
|
||||
# its own runner because LÖVE publishes no aarch64 binary and the
|
||||
# AppImage has to be compiled natively; ships as a runnable
|
||||
# AppImage rather than a zip so `chmod +x && ./it` just works.
|
||||
arm64_appimage="dist/linux-arm64/gen1recomp-${v}-linux-arm64.AppImage"
|
||||
[ -f "$arm64_appimage" ] || { echo "::error::$arm64_appimage not found (expected from the linux-arm64 job)"; exit 1; }
|
||||
cp "$arm64_appimage" "$outdir/gen1recomp-${v}-linux-arm64.AppImage"
|
||||
chmod +x "$outdir/gen1recomp-${v}-linux-arm64.AppImage"
|
||||
apk="$(find dist/android/debug -name '*.apk' | head -1)"
|
||||
[ -n "$apk" ] || { echo "::error::no Android APK found under dist/android/debug"; exit 1; }
|
||||
cp "$apk" "$outdir/gen1recomp-${v}-android.apk"
|
||||
@@ -239,6 +433,16 @@ jobs:
|
||||
[ -f "$ipa" ] || { echo "::error::$ipa not found (expected from scripts/build_ios.sh --device)"; exit 1; }
|
||||
cp "$ipa" "$outdir/gen1recomp-${v}-ios.ipa"
|
||||
|
||||
swzip="dist/switch/gen1recomp-${v}-switch.zip"
|
||||
[ -f "$swzip" ] || { echo "::error::$swzip not found (expected from scripts/build_switch.sh --fused → pack_sd_zip.sh)"; exit 1; }
|
||||
cp "$swzip" "$outdir/gen1recomp-${v}-switch.zip"
|
||||
# Local fused .nro stays under dist/switch/ for PR CI / debug; release
|
||||
# publishes the SD-ready zip only.
|
||||
|
||||
uwp="dist/xbox-uwp/gen1recomp-${v}-xbox-uwp.zip"
|
||||
[ -f "$uwp" ] || { echo "::error::$uwp not found (expected from the Xbox UWP job)"; exit 1; }
|
||||
cp "$uwp" "$outdir/gen1recomp-${v}-xbox-uwp.zip"
|
||||
|
||||
# Anbernic handheld port (suffix names the CFW it targets, so a
|
||||
# future RG35XX/other-CFW pack can ship alongside it).
|
||||
rg34="dist/rg34xxsp/gen1recomp-rg34xxsp-stockos64-mod.zip"
|
||||
@@ -260,12 +464,13 @@ jobs:
|
||||
cat "$outdir/sha256sums.txt"
|
||||
|
||||
- name: Publish GitHub Release
|
||||
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
v="${{ steps.ver.outputs.version }}"
|
||||
tag="${{ steps.ver.outputs.tag }}"
|
||||
v="${{ needs.version.outputs.version }}"
|
||||
tag="${{ needs.version.outputs.tag }}"
|
||||
|
||||
# Issues this release closes. Three sources, deduped by number:
|
||||
# 1. GitHub's own "closing issues" links on every PR whose
|
||||
@@ -349,22 +554,94 @@ jobs:
|
||||
fi
|
||||
printf 'Release notes:\n%s\n' "$notes"
|
||||
|
||||
release_files=(
|
||||
"dist/release/gen1recomp-${v}-macos.zip"
|
||||
"dist/release/gen1recomp-${v}-windows.zip"
|
||||
"dist/release/gen1recomp-${v}-linux.zip"
|
||||
"dist/release/gen1recomp-${v}-linux-arm64.AppImage"
|
||||
"dist/release/gen1recomp-${v}-android.apk"
|
||||
"dist/release/gen1recomp-${v}-ios.ipa"
|
||||
"dist/release/gen1recomp-${v}-switch.zip"
|
||||
"dist/release/gen1recomp-${v}-xbox-uwp.zip"
|
||||
"dist/release/gen1recomp-${v}-rg34xxsp-stockos64-mod.zip"
|
||||
"dist/release/gen1recomp-${v}.love"
|
||||
"dist/release/sha256sums.txt"
|
||||
)
|
||||
|
||||
gh release create "$tag" \
|
||||
--target "$GITHUB_SHA" \
|
||||
--title "$v" \
|
||||
--notes "$notes" \
|
||||
"dist/release/gen1recomp-${v}-macos.zip" \
|
||||
"dist/release/gen1recomp-${v}-windows.zip" \
|
||||
"dist/release/gen1recomp-${v}-linux.zip" \
|
||||
"dist/release/gen1recomp-${v}-android.apk" \
|
||||
"dist/release/gen1recomp-${v}-ios.ipa" \
|
||||
"dist/release/gen1recomp-${v}-rg34xxsp-stockos64-mod.zip" \
|
||||
"dist/release/gen1recomp-${v}.love" \
|
||||
"dist/release/sha256sums.txt"
|
||||
"${release_files[@]}"
|
||||
|
||||
echo "Published release $tag"
|
||||
|
||||
- name: Update iOS app repository
|
||||
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
v="${{ needs.version.outputs.version }}"
|
||||
ipa="dist/release/gen1recomp-${v}-ios.ipa"
|
||||
app_repo="mobile/ios/app-repo.json"
|
||||
[ -f "$ipa" ] || { echo "::error::$ipa not found"; exit 1; }
|
||||
[ -f "$app_repo" ] || { echo "::error::$app_repo not found"; exit 1; }
|
||||
|
||||
date="$(date -u +"%Y-%m-%d")"
|
||||
size="$(wc -c < "$ipa" | tr -d '[:space:]')"
|
||||
download_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/v${v}/gen1recomp-${v}-ios.ipa"
|
||||
localized_description="Gen1Recomp - A native Lua / LÖVE2D recreation of Gen 1 Poke"
|
||||
release_notes="$(GH_TOKEN="${{ github.token }}" gh release view "v${v}" --json body --jq '.body // ""' 2>/dev/null || true)"
|
||||
if [ -n "$release_notes" ]; then
|
||||
localized_description="$release_notes"
|
||||
fi
|
||||
entry="$(jq -n \
|
||||
--arg version "$v" \
|
||||
--arg date "$date" \
|
||||
--arg download_url "$download_url" \
|
||||
--arg localized_description "$localized_description" \
|
||||
--argjson size "$size" \
|
||||
'{version: $version, date: $date, size: $size, downloadURL: $download_url, localizedDescription: $localized_description}')"
|
||||
|
||||
if jq -e --arg version "$v" \
|
||||
'any(.apps[] | select(.bundleIdentifier == "com.theboisclub.gen1recomp").versions[]?; .version == $version)' \
|
||||
"$app_repo" >/dev/null; then
|
||||
jq --arg version "$v" --argjson entry "$entry" \
|
||||
'(.apps[] | select(.bundleIdentifier == "com.theboisclub.gen1recomp").versions) |= map(if .version == $version then $entry else . end)' \
|
||||
"$app_repo" > "$app_repo.tmp"
|
||||
else
|
||||
jq --argjson entry "$entry" \
|
||||
'(.apps[] | select(.bundleIdentifier == "com.theboisclub.gen1recomp").versions) |= [$entry] + .' \
|
||||
"$app_repo" > "$app_repo.tmp"
|
||||
fi
|
||||
mv "$app_repo.tmp" "$app_repo"
|
||||
|
||||
# main is PR-only for everyone except deploy keys (the "main protection"
|
||||
# ruleset's bypass actor), so this push must authenticate with the
|
||||
# RELEASE_DEPLOY_KEY deploy key over SSH; the workflow's GITHUB_TOKEN
|
||||
# would be rejected by the branch protection.
|
||||
- name: Commit iOS app repository
|
||||
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||
env:
|
||||
DEPLOY_KEY: ${{ secrets.RELEASE_DEPLOY_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git add mobile/ios/app-repo.json
|
||||
if git diff --cached --quiet; then
|
||||
echo "app-repo.json unchanged; nothing to push"
|
||||
exit 0
|
||||
fi
|
||||
key="$RUNNER_TEMP/release-deploy-key"
|
||||
printf '%s\n' "$DEPLOY_KEY" > "$key"
|
||||
chmod 600 "$key"
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git commit -m "chore(ios): update app-repo.json [skip ci]"
|
||||
git -c core.sshCommand="ssh -i $key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \
|
||||
push "git@github.com:${GITHUB_REPOSITORY}.git" "HEAD:${GITHUB_REF_NAME}"
|
||||
rm -f "$key"
|
||||
|
||||
- name: Clean up signing keychain
|
||||
if: always()
|
||||
if: ${{ always() && github.repository == 'bryanthaboi/gen1recomp' }}
|
||||
run: |
|
||||
security delete-keychain "$RUNNER_TEMP/pokemon-signing.keychain-db" 2>/dev/null || true
|
||||
rm -f "$RUNNER_TEMP/release-deploy-key"
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Switch artifact comment
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [ci]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: artifact
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
HEAD_REPOSITORY: ${{ github.event.workflow_run.head_repository.full_name }}
|
||||
run: |
|
||||
artifact_id="$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts" --jq '.artifacts[] | select(.name == "gen1recomp-switch-nro") | .id')"
|
||||
[ -n "$artifact_id" ] || exit 0
|
||||
head_owner="${HEAD_REPOSITORY%%/*}"
|
||||
pr_number="$(gh api "repos/$GITHUB_REPOSITORY/pulls?state=open&head=$head_owner:$HEAD_BRANCH" --jq '.[0].number // empty')"
|
||||
[ -n "$pr_number" ] || exit 0
|
||||
echo "artifact_url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts/$artifact_id" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
|
||||
# Upsert via comment-tag only — do not delete-all bot comments (clobbers iOS).
|
||||
- name: Get build info
|
||||
id: build-info
|
||||
env:
|
||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
run: |
|
||||
commit_hash="$(printf '%s' "$HEAD_SHA" | cut -c1-7)"
|
||||
build_time="$(date "+%Y-%m-%d %H:%M:%S")"
|
||||
echo "hash=$commit_hash" >> "$GITHUB_OUTPUT"
|
||||
echo "time=$build_time" >> "$GITHUB_OUTPUT"
|
||||
- name: comment Switch artifact
|
||||
if: steps.artifact.outputs.pr_number != ''
|
||||
uses: thollander/actions-comment-pull-request@v3
|
||||
with:
|
||||
message: |
|
||||
[gen1recomp-switch.nro](${{ steps.artifact.outputs.artifact_url }})
|
||||
|
||||
**Commit**: [#${{ steps.build-info.outputs.hash }}](https://github.com/${{ github.event.workflow_run.head_repository.full_name }}/commit/${{ github.event.workflow_run.head_sha }})
|
||||
**Build Time**: `${{ steps.build-info.outputs.time }}`
|
||||
|
||||
<sub>This comment was automatically generated. [View workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }})</sub>
|
||||
pr-number: ${{ steps.artifact.outputs.pr_number }}
|
||||
comment-tag: switch-build-result
|
||||
github-token: ${{ github.token }}
|
||||
@@ -31,9 +31,29 @@ mobile/ios/love-src/
|
||||
mobile/ios/cache/
|
||||
mobile/ios/build/
|
||||
|
||||
# Final packaged build artifacts (mac/win/web/android/ios) — see scripts/build.sh
|
||||
# love-nx vendor binaries (fetch per docs/switch-build.md; also covered by .*)
|
||||
.bazinga/love-nx/
|
||||
|
||||
# Final packaged build artifacts (mac/win/web/android/ios/switch) — see scripts/build.sh
|
||||
/dist/
|
||||
|
||||
# Switch OTA launcher build outputs
|
||||
ports/switch/ota-launcher/build/
|
||||
ports/switch/ota-launcher/build-host/
|
||||
ports/switch/ota-launcher/*.nro
|
||||
ports/switch/ota-launcher/*.nacp
|
||||
ports/switch/ota-launcher/*.elf
|
||||
ports/switch/ota-launcher/*.map
|
||||
ports/switch/ota-launcher/romfs/logo.rgba
|
||||
ports/switch/ota-launcher/romfs/logo.png
|
||||
ports/switch/ota-launcher/romfs/cacert.pem
|
||||
ports/switch/ota-launcher/romfs/ota-bootstrap.nro
|
||||
ports/switch/ota-bootstrap/build/
|
||||
ports/switch/ota-bootstrap/*.nro
|
||||
ports/switch/ota-bootstrap/*.nacp
|
||||
ports/switch/ota-bootstrap/*.elf
|
||||
ports/switch/ota-bootstrap/*.map
|
||||
|
||||
# Legacy manual convenience-copy location (superseded by /dist/android/)
|
||||
mobile/dist/
|
||||
|
||||
@@ -43,3 +63,15 @@ mobile/dist/
|
||||
|
||||
# per-machine iOS bundle-id pin (see scripts/build_ios.sh)
|
||||
mobile/ios/bundle_id.local
|
||||
|
||||
# Xbox UWP build output
|
||||
/ports/uwp/build/
|
||||
/ports/uwp/third_party/*/source/
|
||||
/ports/uwp/third_party/angle/depot_tools/
|
||||
|
||||
# Native TLS dialer build output (dotnet publish)
|
||||
/native/tls_dial/bin/
|
||||
/native/tls_dial/obj/
|
||||
/dist/native/
|
||||
/dist/win/
|
||||
/.bazinga/
|
||||
|
||||
@@ -118,6 +118,7 @@ not a hard error, so the list can grow without breaking old mods.
|
||||
| `QUEST` | New story, NPCs, dialogue, cutscenes | content |
|
||||
| `MECHANIC` | New or changed battle/field mechanics via hooks/effects | overhaul |
|
||||
| `GRAPHICS` | Sprite / tileset / palette / font changes | content |
|
||||
| `LANGUAGE` | A translation: `text`, `strings` and the glyphs it needs | content |
|
||||
| `AUDIO` | Music, sfx, cries | content |
|
||||
| `UI` | New or modified screens, menus, overlays | content / overhaul |
|
||||
| `TOOL` | Dev/QoL utilities, overlays, inter-mod libraries | content |
|
||||
@@ -128,7 +129,120 @@ not a hard error, so the list can grow without breaking old mods.
|
||||
keeps validating with the value it has shipped since before the taxonomy
|
||||
existed.
|
||||
|
||||
### 4. `mod.card`
|
||||
A translation may also set `"language": true` in the manifest. That is the
|
||||
one claim online play acts on: an install running nothing but verified
|
||||
translations may take an ONLINE MATCH or a TOURNAMENT instead of being
|
||||
asked to restart vanilla. The claim is checked, not taken -- the mod
|
||||
qualifies only if every record it writes lands in `text`, `strings` or
|
||||
`font`, it wraps no hook, subscribes to no event and requests no
|
||||
permission. Anything else and it is an ordinary content mod that happens to
|
||||
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:
|
||||
|
||||
```json
|
||||
"games": ["gen1", "gen2"]
|
||||
```
|
||||
|
||||
Each entry is a version id (`"red"`, `"blue"`, `"yellow"`, `"gold"`), 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,
|
||||
`mods/<id>/`, shared by every game -- targeting is declared, never filed.
|
||||
|
||||
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.
|
||||
|
||||
Every token is enforced, per game: the loader gates on the same
|
||||
`ModTargets.supports` answer both mod surfaces draw, so `"games": ["blue"]`
|
||||
really does not load on Red and the skip line is the launcher's line, `For
|
||||
Blue, not Red`, and `"games": ["gold"]` alone does not load on Red either. A
|
||||
manifest with neither key still covers every Gen 1 game, so nothing written
|
||||
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 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
|
||||
Gen 1 mod, and it is the one to start from.
|
||||
|
||||
Two consequences worth knowing before you claim Gen 2.
|
||||
|
||||
**Dependencies are contagious.** A mod whose hard dependency does not run here
|
||||
is left out too, with the dependency's own wording (`depends on X, which does
|
||||
not run here (For Blue, not Red)`). It is reported as a skip, not as a failure,
|
||||
and neither mod lands on the boot error list, but the mod does not run, so
|
||||
every hard dependency has to cover the same games.
|
||||
|
||||
**The player can override you.** The claim is yours, and a mod written before
|
||||
the key existed can never carry one, so the manager's detail pane offers
|
||||
`TRY HERE ANYWAY` for any mod that does not claim the game being played. It
|
||||
persists per game in `options.modsGen2[id][version]` and takes effect on the
|
||||
next boot; forcing a mod onto Red does not force it onto Gold. A forced mod
|
||||
loads normally and keeps a note saying its author never verified it here.
|
||||
|
||||
**Prefer the API on Gold, but the Gen 1 names still work.** Gen 2 is a
|
||||
parallel module tree behind `src/core/Game2.lua`. In new code take the live
|
||||
game from `mod.game` (or the `game.ready` payload, or any `ui.*` hook's first
|
||||
argument) and the world from `mod.world`; both resolve per generation, and
|
||||
neither needs `engine_internals`.
|
||||
|
||||
For the mods written before Gold existed, a require made from a mod's own file
|
||||
is answered on a Gold boot by an adapter presenting the Gen 1 API over Gen 2
|
||||
internals. Fifteen names are served -- `src.core.Game`,
|
||||
`src.world.OverworldController`, `src.world.Map`, `src.world.NPC`,
|
||||
`src.world.Collision`, `src.world.WorldAPI`, `src.world.PikachuFollower`,
|
||||
`src.world.FieldDefaults`, `src.pokemon.Boxes`, `src.script.ScriptRunner`,
|
||||
`src.ui.PartyMenu`, `src.ui.StartMenu`, `src.ui.OptionsMenu`, `src.ui.BoxMenu`
|
||||
and `src.battle.BattleState`. `src/mods/Gen2Compat.lua` is the full table and
|
||||
publishes what it covers through `Gen2Compat.coverage(name)`, whose members are
|
||||
`backed`, `warned` or `absent`. A name with no adapter (`src.script.Commands`,
|
||||
`src.ui.OptionRows`) is reported against the mod that required it, and a member
|
||||
an adapter cannot back is absent or logs once rather than answering wrongly.
|
||||
|
||||
Things no adapter can fix, all mod-side: a hardcoded version allow-list
|
||||
(`GameVersion.get() == "red" or ...`) excludes you from Gold by construction;
|
||||
Gold's builtin screen ids carry a `Gen2` prefix, so a string match on
|
||||
`"BoxMenu"` matches nothing there; a write to a field on a live Gen 2 menu
|
||||
instance is inert; and `map.warpAt` is a table on Gen 1 and a method on Gold,
|
||||
so indexing it raises. Each has a route that works on both generations, in
|
||||
`docs/preparing-your-mod-for-gen2.md`.
|
||||
|
||||
Check it statically, then load it headless:
|
||||
|
||||
```sh
|
||||
python3 tools/modkit.py gen2check mods/my_mod
|
||||
```
|
||||
|
||||
```lua
|
||||
local run = T.sdk.loadMod("mods/my_mod", { generation = 2 })
|
||||
T.eq(run.mod and run.mod.state, "loaded",
|
||||
"runs on gen 2: " .. tostring(run.mod and run.mod.skipReason))
|
||||
T.eq(#run.errors, 0, "and loads with no boot errors")
|
||||
```
|
||||
|
||||
Assert the state, not only the error count: a gate skip is deliberately not an
|
||||
error, so `#run.errors == 0` passes for a mod that never ran a line.
|
||||
|
||||
`gen2check` answers `will load`, `will load but degrade` or `will not work`,
|
||||
with a `MK4xx` finding per site and an `unresolved:` note, with a file and a
|
||||
line, for every reach a static scan could not follow. Neither substitutes for a
|
||||
real Gold boot.
|
||||
|
||||
### 5. `mod.card`
|
||||
|
||||
The manifest is the *engine's* contract: identity, load order, dependencies,
|
||||
permissions, profile. The card is the *human-facing* one: who made this,
|
||||
@@ -149,7 +263,7 @@ Two fields deserve their own note:
|
||||
distributed mod never carries ROM-derived bytes, not even in its preview
|
||||
images.
|
||||
|
||||
### 5. Tags
|
||||
### 6. Tags
|
||||
|
||||
Lowercase kebab strings, open vocabulary. The showcase generator
|
||||
lowercases and de-dupes. A recommended starting set: `beginner`,
|
||||
@@ -207,12 +321,15 @@ registry or a new schema field lands with its catalog entry in the same PR
|
||||
and the generator runs clean:
|
||||
|
||||
```sh
|
||||
luajit tools/gen_registry_docs.lua # in-repo default
|
||||
luajit tools/gen_registry_docs.lua ../project.wiki # the wiki checkout
|
||||
luajit tools/gen_registry_docs.lua # docs/modding/reference/registries.md
|
||||
luajit tools/gen_registry_docs.lua ../project.wiki # Reference-Registries.md in a wiki checkout
|
||||
```
|
||||
|
||||
The prose reference lives in the GitHub wiki; the generated pages are
|
||||
written into a checkout of it, so they cannot drift from the engine.
|
||||
With no argument it writes inside the repo, which is the copy `python3
|
||||
tools/modkit.py docs` regenerates and `--out` copies from. Pass a directory
|
||||
(or set `POKEPORT_DOCS_DIR`) to write the wiki's flat page name into a wiki
|
||||
checkout instead. The prose reference lives in the GitHub wiki; both copies
|
||||
come off `src/mods/Schemas.lua`, so neither can drift from the engine.
|
||||
|
||||
### 5. Deprecation etiquette
|
||||
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
# Gold port: what is still open after the 2026-08-09 asm parity runs
|
||||
|
||||
Two multi-agent runs diffed the Gold port against the `pokegold` disassembly
|
||||
(`../pokegold`) and fixed what diverged. Round 1 confirmed 51 defects across 14
|
||||
paired clusters; round 2 confirmed 41 more across 13, including one crash. A
|
||||
third round (2026-08-10) closed the mod-API boundary: see "The 2026-08-10
|
||||
mod-API round" below for what that opened and closed.
|
||||
|
||||
Every tier is green at the end of all three: `run_engine` 131/131, `run_tests`
|
||||
ALL TESTS PASSED, `run_modkit` 6/6, `run_save_editor_tests` 650/0,
|
||||
`run_link_tests` ALL PASSED.
|
||||
|
||||
This file records only what is NOT done, so nobody re-derives it. It is a
|
||||
working handoff doc in the shape of `KANTO-CONTINUE.md`, not user documentation.
|
||||
|
||||
## Do this first or none of it is visible
|
||||
|
||||
**Re-import the Gold cache.** Seven fixes live in the extractor and the manifest
|
||||
tool, so they do nothing until the ROM is re-imported. This was verified still
|
||||
outstanding on 2026-08-10: the default identity's
|
||||
`gold/data/generated/menu_gfx.lua` has no `trainerPics` key at all, so a trainer
|
||||
battle still opens on the mon rather than the trainer even though the Lua half
|
||||
is fixed and correct.
|
||||
|
||||
- trainer frontpics (`TrainerPicPointers` walk in `extractMenuGfx`), which is
|
||||
the missing trainer intro image at the start of a trainer battle. The Lua
|
||||
side is now right and re-import is the ONLY thing left: `Trainers.lookup`
|
||||
returns a numeric `class` (36) beside a `classId` (`BUG_CATCHER`), and both
|
||||
`menu_gfx.trainerPics` and `palettes.trainers` are keyed by the constant, so
|
||||
the lookup had to move to `classId`. On a cache that carries the pics the
|
||||
frontpic renders in the class's own palette.
|
||||
- egg assets: `battle/front/egg.png` and `menu/egg_hatch.png`, feeding the
|
||||
hatch cutscene and the summary screen's egg page. A fallback landed in the
|
||||
meantime (`ICON_EGG` frame 0, which resolves to a real 16x16 frame), so the
|
||||
egg page draws SOMETHING on a stale cache; `tools/rom_manifest_gold.json`
|
||||
carries `EggPic` at `[20, 31363]` and that stream decompresses to exactly 400
|
||||
bytes (a 5x5-tile pic), so a fresh import takes the real path.
|
||||
- the Pokegear phone icon. `_LoadFontsExtra` (`engine/gfx/load_font.asm:8-15`)
|
||||
puts `FontsExtra_SolidBlackAndUpArrowGFX` at `$60`/`$61` and
|
||||
`PokegearPhoneIconGFX` at `$62`, ON TOP of `FontExtra`'s BOLD_A/B/C.
|
||||
`src/import/RomExtractorGen2.lua:342-356` reads `FontExtra` straight in and
|
||||
then blits only `Frames` over `$79-$7E`, so `$62` is still a bold "C" -- which
|
||||
is what the new caller box draws where the telephone glyph belongs. Fix is two
|
||||
files: add the two GFX symbols to `tools/rom_manifest_gold.json`, then blit
|
||||
them at `$60`/`$61`/`$62` in the loop that already handles `Frames`.
|
||||
- `PREDEFPAL_UNOWN_PUZZLE = 76`, which is why the Ruins of Alph puzzle rendered
|
||||
grayscale instead of brown
|
||||
- the tilemap pad `0x7f -> 0x4f` in `readTilemapRLE` / `readFlatTilemap`
|
||||
- held-item icons (`HeldItemIcons` in the manifest, `out.heldItem` in
|
||||
`extractIcons`), which is the held-item marker in the party menu
|
||||
- the NPC-trade rows' `item` field, which is what made the Violet City Onix's
|
||||
Bitter Berry print as "83"
|
||||
|
||||
## Open: real unknowns, each needs a cache and a driver run
|
||||
|
||||
1. **Magnet train renders a blank field.** Pushing `World:magnetTrain` directly
|
||||
shows nothing for 90+ frames (see shot `03-magnet-train.png`). The screen's
|
||||
surround is correct now; what was never investigated is whether
|
||||
`drawBackground` needs setup that the direct push skips. This cannot be
|
||||
settled by reading source.
|
||||
|
||||
2. **The NPC that introduces Unown never appears.** Round 1 could not produce a
|
||||
port ref that diverges from the asm, so it is filed `cannot_locate`, not
|
||||
fixed. The cart chain is: solve a chamber puzzle, then
|
||||
`setmapscene RUINS_OF_ALPH_INNER_CHAMBER, SCENE_RUINSOFALPHINNERCHAMBER_STRANGE_PRESENCE`
|
||||
from `maps/RuinsOfAlphKabutoChamber.asm`. Walk that chain live rather than
|
||||
statically: the static read came back clean twice.
|
||||
|
||||
3. **Two pic sites still resolve an Unown off the species.**
|
||||
`src/ui/gen2/PhotoStudio.lua:102` and `src/ui/gen2/EvolutionAnim.lua:460` use
|
||||
`def.spriteFront` without going through `Unown.formSprite`. For EvolutionAnim
|
||||
this is almost certainly dead (Unown does not evolve). For PhotoStudio it is
|
||||
genuinely unclear: the cart's `engine/events/print_photo.asm` does not call
|
||||
`GetUnownLetter` at all, so the port may already match. Check before changing.
|
||||
|
||||
4. **Unown dex registration on obtain paths.** `engine/pokemon/evolve.asm:310`
|
||||
runs `GetUnownLetter` then `callfar UpdateUnownDex` after
|
||||
`SetSeenAndCaughtMon`, guarded by `cp UNOWN`. That is dex bookkeeping, not
|
||||
rendering, and it was never audited across every path that gives the player a
|
||||
mon. `GetUnownLetter` has 20 call sites on the cart; the port covers the pic
|
||||
ones.
|
||||
|
||||
## The 2026-08-10 mod-API round
|
||||
|
||||
Six lanes closed the Gen 1 / Gen 2 mod-API boundary and a batch of reported
|
||||
gameplay bugs. What that round **closed**, so nobody re-opens it:
|
||||
|
||||
- Fifteen seams that had a Gen 2 site but no entry in the parity gate are now
|
||||
listed and asserted: the four `intro.oak_speech.*` events plus the
|
||||
`intro.oak_speech.build` hook, `battle.overlay`,
|
||||
`battle.low_health_alarm`, `battle.catch_exp`, `pokemon.sprite`,
|
||||
`input.step`, `input.pointer`, `render.zones`, `render.compose`,
|
||||
`render.letterbox`, `render.hud`. Five more (`intro.boot.*`) are listed as
|
||||
Gen 2-only. `tests/engine/gate_gen2_mod_api.lua` is 893/893.
|
||||
- Two registries un-gated on Gold after their consumer landed:
|
||||
`battle_sprite_scales` (`src/ui/gen2/BattleState.lua:imageScale`) and
|
||||
`render_pipelines` (`src/core/Game2.lua:load` installs
|
||||
`src/render/Pipelines.lua` on the merged dataset after `mods:load`). Both
|
||||
keep the SHARED Gen 1 target, so one mod record serves both games. 40 of the
|
||||
46 registries are now available on Gold.
|
||||
- `hook:render.zones` came off the `gate_meta_coverage.lua` DEBT ledger, which
|
||||
is now four entries, all M7/M12 link and give-mon seams.
|
||||
|
||||
What that round **left open**, each verified on 2026-08-10:
|
||||
|
||||
1. **`render_pipelines`' `drawWorld` half is inert on Gold.** Gold's overworld
|
||||
draws straight to the window rather than into a canvas the way
|
||||
`src/world/OverworldController.lua` hands one to `Pipelines.drawWorld`, so a
|
||||
drawWorld-only pipeline renders nothing. It is not left switched on
|
||||
pretending: `Game2:load` retires a restored level for one and re-applies
|
||||
Tilt from the option the exclusion just cleared, leaving
|
||||
`options.pipelines` untouched so the mode returns the day Gold grows a world
|
||||
canvas. Related: `Pipelines.rows` is read only from
|
||||
`src/ui/OptionsMenu.lua`, so a pipeline on Gold has a hotkey and no OPTION
|
||||
row.
|
||||
|
||||
2. **`transitions` stays gated and should.** `src/ui/gen2/BattleTransition.lua`
|
||||
keys `STYLES` as a boolean SET of the four cart wipes (`spin`, `speckle`,
|
||||
`zoom`, `sine`), not the `{ frames, draw, sound, flash }` record the
|
||||
registry carries, and there is no styleDef lookup a mod id could reach --
|
||||
a registered style would fail the membership test and fall back to vanilla.
|
||||
Un-gating it before that changes would be the silent no-op the routing table
|
||||
exists to prevent. Same for `rulesets`, `field`, `text_pointers`,
|
||||
`link_fields` and `map_scripts`; `docs/mod-api-gen2-compat.md` carries the
|
||||
per-registry reason.
|
||||
|
||||
3. **`src/core/Game2.lua:joystickremoved` is half of Gen 1's.**
|
||||
`src/core/Game.lua:867` does `self:recoverInput("joystickremoved", joystick)`
|
||||
AND `TouchControls:joystickremoved()`; Game2 does only the pad half, so a
|
||||
controller unplugged mid-hold leaves Gold's Input state stranded and a held
|
||||
direction walks forever. It was a total noop before, so this is not a
|
||||
regression -- it is a seam finished halfway. One line.
|
||||
|
||||
4. **`Game2` noops five joystick callbacks.** `joystickpressed`,
|
||||
`joystickreleased`, `joystickaxis`, `joystickhat` and `joystickadded` are
|
||||
assigned `noop`, so a stick with no SDL game-controller-database entry
|
||||
reaches neither Input nor `GamepadMap.RAW_BUTTON_BINDINGS` on Gold. Gen 1
|
||||
handles all four (`src/core/Game.lua:764-797`). A DualSense is
|
||||
SDL-recognized and takes the gamepad path, so this is not the reported
|
||||
SELECT bug -- it is the raw-stick fallback.
|
||||
|
||||
5. **`Game2:gamepadpressed` has no SELECT-held guard.** Gen 1
|
||||
(`src/core/Game.lua:686-694`) suppresses the shoulder GAME SPEED cycle while
|
||||
SELECT is held, because Select+L is a display chord on NX. Harmless today
|
||||
(Gold has no display chord) but the two paths have diverged.
|
||||
|
||||
6. **`hideCallerBox`'s fallback path skips the screen seams.**
|
||||
`src/script/gen2/CallAsm.lua:217-231` pops through `stack:pop` when the
|
||||
caller box is top (which it always is on a normal call, since `PhoneRing`
|
||||
runs `closetext` before `InitCallReceiveDelay`) but falls back to a bare
|
||||
`table.remove(states, index)` otherwise, which raises neither `exit` nor
|
||||
`screen.popped`. Only a mod screen pushed over the box can reach it, and
|
||||
then a listener that saw `screen.pushed` never sees the pop.
|
||||
|
||||
7. **`src/core/gen2/Roamers.lua:338` says the wrong thing.** Its comment says
|
||||
"the shared `encounter.species` hook still runs downstream and is where a
|
||||
mod changes what appears". It does not: `World:tryWildEncounter` calls
|
||||
`startBattle` on a roamer hit and RETURNS before it reaches
|
||||
`World:rollEncounter`. The doc's partial-coverage list is right and the
|
||||
comment is wrong; correct the comment, not the doc.
|
||||
|
||||
8. **`src/world/gen2/World.lua:4318` still calls the pack's SEL row something
|
||||
"this port has not built".** The pack submenu now offers SEL. Stale comment.
|
||||
|
||||
Three partial coverages that are documented and still true, repeated here
|
||||
because "the hook exists" is not "the hook sees everything":
|
||||
`encounter.roll` / `encounter.species` are not wired into `World:tryHeadbutt`,
|
||||
`World:rockMonEncounter` or the roamer path; `src/ui/gen2/BattleState.lua`
|
||||
builds a flat `opts` for `Catching.attempt` with no `data` in it, so a
|
||||
mod-registered ball is readable through `Catching.recordFor` but is not
|
||||
resolved at the real throw site; and no Gen 2 UI file reads
|
||||
`Battle.statusRecordFor(data, status).hudLabel`, so a mod status shows no label
|
||||
in the battle HUD, the party menu or the summary page.
|
||||
|
||||
## Open: cleanups declined on purpose
|
||||
|
||||
These were skipped with reasons during the wiring pass. They are listed so the
|
||||
reasons survive, not because they are pending work.
|
||||
|
||||
1. **`Vm:showRaw(body, stay)`** was proposed and declined. `showRawHeld` in
|
||||
`Specials.lua` already answers the text lookahead correctly, so this trades
|
||||
one working shape for another.
|
||||
|
||||
2. **`tests/drivers/gold_egg_hatch.lua` summary push** was declined as
|
||||
redundant: `gold_egg_hatch_shots.lua` already photographs both the cutscene
|
||||
and the summary page.
|
||||
|
||||
3. **Two `Battle.lua` divergences** were filed as notes by the file's own owner
|
||||
rather than fixed, because no reported symptom drives either: text printed
|
||||
for an already-statused target, and Future Sight taking STAB, type
|
||||
effectiveness and weather. Both are real divergences from the cart. Fix them
|
||||
when something actually depends on them.
|
||||
|
||||
## Reported, investigated, and NOT defects: do not re-chase these
|
||||
|
||||
Each of these came in as a bug report and came back disproved against the asm.
|
||||
Re-opening one costs another full investigation, so the reasoning is kept here.
|
||||
|
||||
- **SFX pointer table misalignment.** The table is aligned end to end. 188 `dba`
|
||||
rows with no skips or padding, 188 constants, verified through the manifest
|
||||
scraper, the extractor's `*3` stride, and 8 ids spot-checked against the live
|
||||
cache. Two things that look like misalignment are how the cart is built:
|
||||
`SFX_GET_EGG_UNUSED` and `SFX_GET_EGG` both point at `Sfx_GetEgg`, and
|
||||
`Sfx_ReadText` / `Sfx_ReadText2` share one header address. **Do not repoint,
|
||||
re-stride, or add an offset to the SFX, music, or cry tables.**
|
||||
|
||||
- **Bind letting you pick other moves.** This is Gen 1 behavior and does not
|
||||
hold for Gen 2. `BattleCommand_TrapTarget` (`effect_commands.asm:5568-5605`)
|
||||
writes only the TARGET's wrap count and trapping move. It touches nothing on
|
||||
the user, so the user is free to switch moves. The port is correct. The real
|
||||
Gen 2 move locks (Rollout, Thrash, Petal Dance) were genuinely missing and
|
||||
have been added.
|
||||
|
||||
- **The Slowpoke Tail salesman not blocking progression.** He is Route 32's
|
||||
`FISHER4` at (7,70), not an Azalea Town object, and the cart's refusal arm
|
||||
only prints. `_OfferToSellSlowpokeTail` runs `setscene SCENE_ROUTE32_NOOP`
|
||||
first, so the coord event never fires again. There is no pushback to port.
|
||||
|
||||
- **The rival being named BLUE.** Not reproducible. The whole chain is intact:
|
||||
special 36 resolves to `H.NameRival`, `NamingScreen:accept` hands the typed
|
||||
string through, and BLUE is the Gen 1 default that Gold never reaches. A
|
||||
separate real defect on this seam (the rival being pre-named SILVER before the
|
||||
officer asks, and the blank-entry fallback) was found and fixed.
|
||||
|
||||
- **Not Very Effective dealing 0 damage**, **status not shown in the battle
|
||||
UI**, and **the Pokedex missing from the Start Menu** all came back
|
||||
`already_fixed`: the damage floor is `MIN_DAMAGE` added after the cap and
|
||||
before the type multiply, the HUD prints the status tag where the level goes,
|
||||
and the menu row is gated on `save.engineFlags[11]` correctly.
|
||||
|
||||
## Standing risks, not breakage
|
||||
|
||||
- **Four Gold test files are wired into no runner**, so a regression in them
|
||||
will not turn `run_tests.lua` red: `gold_flag_names_test`,
|
||||
`gold_route_validate_test`, `gen2_pokegear_unlock_test`,
|
||||
`gen2_save_export_test`. All four pass standalone. The first two are the
|
||||
pre-run route validators documented in `KANTO-CONTINUE.md`; the last two need
|
||||
a `GOLD_CACHE` and are excluded by an in-file comment. This is deliberate, but
|
||||
it is a coverage hole and worth knowing.
|
||||
|
||||
- **`mods/` must stay clean.** The 2026-08-10 round left temporary probe mods
|
||||
behind mid-run (`mods/tmp_menus`, `mods/zz_verify_seams`); both are gone now
|
||||
and `mods/` holds only `example_mew_starter`, `example_silly_oak`, `examples`
|
||||
and `nuzlocke`. A stray `mods/tmp_*` loads on every Gold boot and changes what
|
||||
a driver measures. `mods/example_silly_oak` carries only a `.modkitignore` and
|
||||
warns "manifest.json does not exist" on every boot; that is pre-existing
|
||||
(27 Jul) and unrelated.
|
||||
|
||||
- **`tests/drivers/gold_opaque_surround.lua` is stale.** It reads
|
||||
`game.stack._items`; `src/core/StateStack.lua` has only `.states`. That branch
|
||||
is covered instead by `gold_center_pc` and the letterbox-per-frame count in
|
||||
`gold_frame_seams`.
|
||||
|
||||
- **`luac` on this machine is Lua 5.5**, so `luac -p` is a weak proxy for the
|
||||
LuaJIT/5.1 semantics this engine targets. Use `luajit -b <file> /dev/null` for
|
||||
a real syntax gate.
|
||||
|
||||
- **`docs/rfcs/0001-surfing-pikachu-sprite.md` and `0002-screen-render-visible.md`
|
||||
are deleted** in the working tree, and both are still referenced by name from
|
||||
live modkit case files that pass. Those deletions predate both parity runs.
|
||||
Worth resolving before a commit.
|
||||
|
||||
## The failure mode that actually bit, worth remembering
|
||||
|
||||
Round 1 reported the Yes/No dialogue fix as landed. It was not. Two of its three
|
||||
lanes wrote their half, and the third left the hook closure at
|
||||
`World.lua:828` declared as `function(body, onDone)`, so the third argument
|
||||
`Vm:resume` passes was silently discarded and the entire `stay` implementation
|
||||
in `World:showText` and `World:askYesNo` was dead code. Lua drops extra
|
||||
arguments without complaint, so nothing failed and every suite stayed green.
|
||||
|
||||
Round 2 caught it only because an investigator probed the boundary empirically
|
||||
instead of reading both sides and assuming they met. When work is split across
|
||||
agents by file, **the seams between the files are where the bugs live**, and a
|
||||
green suite does not prove a seam is connected.
|
||||
@@ -0,0 +1,457 @@
|
||||
# KANTO-CONTINUE: executing the never-run half of Gold (sections 19-32)
|
||||
|
||||
Deferred from the 2026-08-09 fix campaign by request. This doc is self-contained: the
|
||||
audit documents it was distilled from are being retired, so everything a future session
|
||||
needs is here.
|
||||
|
||||
## What this is
|
||||
|
||||
The Johto main line (walkthrough sections 00-18) is proven: the route bot beats Champion
|
||||
Lance with every row passing. Kanto (sections 19-32) is extracted and wired but has
|
||||
NEVER been executed by any bot row, driver, or human. Every entry below reads clean in
|
||||
static review (cache + code + cart source were cross-checked adversarially); the risk is
|
||||
purely runtime. The job: make each beat actually run, fix what breaks, and leave
|
||||
executable evidence (bot rows or assertion drivers) behind.
|
||||
|
||||
## State after the 2026-08-09 campaign (what changed under you)
|
||||
|
||||
- ~80 defects fixed across the port; 91/91 gen2 suites, engine 128/128, T3 all green.
|
||||
- The post-game handoff WORKS now: post-E4 credits end at the title screen, and
|
||||
CONTINUE consumes the post-game spawn (HallOfFame.consumePostGameSpawn): after Lance
|
||||
you continue in NEW_BARK_TOWN, after Red at SILVER_CAVE_OUTSIDE (23,20). Section 19
|
||||
is reachable by normal play for the first time.
|
||||
- The Snorlax wake chain is fully functional (engine-flag bridge unlocks the Pokegear
|
||||
and EXPN card; the tuned radio song persists after closing the gear; SnorlaxAwake
|
||||
fires; proven live by tests/drivers/gold_radio_persist.lua).
|
||||
- Landed systems Kanto beats depend on: phone random/outgoing calls, field item use
|
||||
(Escape Rope/Dig via save.backupWarp), Repel, swarms, Rock Smash encounters,
|
||||
waterfall current tiles, roamer scatter on CONTINUE, trap volatiles + FORCESHINY
|
||||
battle types (beast catching in section 32 is now mechanically possible), badge
|
||||
boosts, held items, specialty balls (Lure Ball needs battleType 'fish', already
|
||||
stamped by updateFishing).
|
||||
- NPC HOUR WINDOWS are now enforced (they were not when the Johto route was authored).
|
||||
Any Kanto row that talks to a time-gated NPC must pin the clock (POKEPORT_GOLD_HOUR /
|
||||
POKEPORT_GOLD_DAY) or handle absence. Only 11 hour-gated objects exist on the whole
|
||||
cart: CELADON_GAME_CORNER 6/7, GOLDENROD_GAME_CORNER 4/5, MOUNT_MOON_GIFT_SHOP 1-4,
|
||||
and the three Moms in PLAYERS_HOUSE_1F; Celadon and Mt Moon are the Kanto exposures.
|
||||
- The game clock now anchors on every new game (cart InitClock semantics), so time of
|
||||
day no longer follows the host wall clock in driver runs; POKEPORT_GOLD_HOUR pins
|
||||
the anchored base too. Runs are reproducible across day/night; a pre-existing flake
|
||||
class (night runs catching HOOTHOOT where day runs catch PIDGEY, shifting grind
|
||||
levels) died with it.
|
||||
- Battles hit harder both ways since the cart MIN_DAMAGE floor landed; grind budgets
|
||||
tuned for the old numbers may need a nudge.
|
||||
|
||||
## Known bot weaknesses to expect (verified pre-existing, not campaign regressions)
|
||||
|
||||
- Pathfinding pocket: travel into ILEX_FOREST / SLOWPOKE_WELL bounces (probe
|
||||
CHERRYGROVE_CITY>ILEX_FOREST reports no route), and route row 16.54 gives up the
|
||||
walk to VICTORY_ROAD_GATE with the identical TELEPORT signature in every logged run.
|
||||
Kanto routing should waypoint doors explicitly per the ops recipes above.
|
||||
- Optional buy rows 04.5d and 18.g4/18.g5 fail whenever the wallet is empty at that
|
||||
point in the run; they are budgeting gaps in the route, not engine bugs.
|
||||
- Grind row 08.g (Route 41 water) has almost no margin; with the clock now anchored
|
||||
its inputs are stable, but a low-level POLIWAG catch can still stretch it.
|
||||
|
||||
## How to run things
|
||||
|
||||
Identity/cache: gold-dev (~/Library/Application Support/LOVE/gold-dev/gold), rebuilt
|
||||
2026-08-09 with swarm/rock/roam tables. Checkpoints gold-ckpt-NN.lua exist per section;
|
||||
gold-ckpt-18-pristine.lua is the post-E4 seed. ROM: ../decprep/Pokemon - Gold
|
||||
Version.gbc. Cart source: ../pokegold (cite it in any hand-ported code).
|
||||
|
||||
Headless love needs a pty on macOS; wrap every run:
|
||||
perl -e 'alarm 900; exec @ARGV' python3 -c "import pty; pty.spawn(['love','.'])"
|
||||
|
||||
Full bot run: POKEPORT_IDENTITY=<id> POKEPORT_GAME=gold POKEPORT_SPEED=200 \
|
||||
POKEPORT_GOLD_CKPT=1 POKEPORT_GOLD_STALL=20000 POKEPORT_GOLD_LOG=/tmp/b.log \
|
||||
POKEPORT_DRIVER=tests/drivers/gold_bot.lua love .
|
||||
Resume a section: POKEPORT_GOLD_RESUME=NN (same identity, prior CKPT=1 run).
|
||||
Travel probe: POKEPORT_GOLD_PROBE="MAP_A>MAP_B" with tests/drivers/gold_travel_probe.lua.
|
||||
Map graph: luajit tools/goldwalk/mapgraph.lua path|map|reach|audit; regenerate
|
||||
tests/drivers/gold/map_regions.lua with `mapgraph.lua graph >` after ANY extractor map
|
||||
change, then re-check route region indices.
|
||||
Route validation (before any run): luajit tests/gold_route_validate_test.lua and
|
||||
luajit tests/gold_flag_names_test.lua.
|
||||
Suites: GOLD_CACHE="$HOME/Library/Application Support/LOVE/gold-dev/gold" luajit
|
||||
tests/gen2_<name>_test.lua; tiers run_engine/run_modkit/run_tests.
|
||||
Reimport after extractor/manifest changes: python3 tools/make_gold_manifest.py, then
|
||||
POKEPORT_IMPORT_TRACE=1 POKEPORT_IDENTITY=gold-dev POKEPORT_IMPORT_ROM="../decprep/\
|
||||
Pokemon - Gold Version.gbc" POKEPORT_IMPORT_ONLY=1 POKEPORT_FORCE_IMPORT=1 \
|
||||
POKEPORT_GAME=gold love .
|
||||
|
||||
## Recommended shape of the work
|
||||
|
||||
1. Extend tests/drivers/gold/route.lua with sections 19-32 (the row format, ops and
|
||||
evidence rules are documented in the file head and bot.lua). Seed runs from
|
||||
gold-ckpt-18-pristine plus the now-working post-credits CONTINUE. `expect` flags are
|
||||
the only oracles; never make a row load-bearing on a soft-failing map.
|
||||
2. Highest-risk multi-map scripted chains first: S.S. Aqua voyage, Machine Part chain,
|
||||
PASS/Magnet Train chain. Each deserves an assertion driver even if bot rows also
|
||||
cover it.
|
||||
3. Time-gated beats (S.S. Aqua sailing days, Indigo rival Mon/Wed, Dragon's Den
|
||||
Tue/Thu, Clefairy Monday night, TM03 night gate) need POKEPORT_GOLD_DAY/HOUR pins.
|
||||
4. Johto stragglers are folded in below (lighthouse descent, TM08 flake, and the
|
||||
optional gift beats); they ride the same route-extension pass.
|
||||
5. Section 32 (beasts) last: it needs long roaming play; trap volatiles and FORCESHINY
|
||||
landed this campaign, so Mean Look + Heavy/Fast Ball plans are now viable.
|
||||
|
||||
## Also deferred (deliberate stubs, do not re-flag)
|
||||
|
||||
Time Capsule trading, Trainer House CAL2/Mystery Gift visitor, Silver-version import,
|
||||
Cable Club link rooms, and Game Boy Printer output are deliberate product-level stubs,
|
||||
not campaign work.
|
||||
|
||||
## The beats, in walkthrough order
|
||||
|
||||
### Runtime coverage ends at the Hall of Fame: no bot row, driver, or human has run any Kanto beat (sections 19-32) or the section 14 Tin Tower/Whirl Islands content
|
||||
|
||||
walkthrough section(s) 19,20,21,22,23,24,27,28,29,30,31,14 | missing
|
||||
|
||||
One root cause: tests/drivers/gold/route.lua (1264 lines) ends at row 18.20 (settle HALL_OF_FAME, expect EVENT_BEAT_ELITE_FOUR); the campaign covers asm-walk sections 00-18 only and none of the 24/36 gold_* drivers reaches any Kanto map, so sections 19-32 have zero execution proof. Extend route.lua into sections 19-32 and/or add targeted drivers. Per section pair: (s19/20) next rows are the Elm ticket (needs the post-credits spawn fix or a fly detour from Indigo), the ship crossing as far as the docking events, the Surge fight, and a Saffron Gym pad-chain assertion. (s21/22) All 24 route/gym trainers resolve in cache with correct parties: Route 9 six (Edna, Sid, Dean, Sidney, Tim, Heidi); Route 25 seven plus Kevin, whose Nugget-then-battle script 50:488d is hand-shaped (a full bag aborts before the battle, cart behavior); Cerulean's three swimmers; Route 10 South's Jim and Robert (Robert's Quagsire holds a BERRY via TRAINERTYPE_ITEM); Route 8's five; Celadon Gym's five including the twins' shared beat flag. Sight-line engagement is Trainers.sees (proven by the Johto bot); prize money base x level x 4 is Prize.lua; Erika's class item extracts as HYPER_POTION (the walkthrough's 'three Full Restores' is a FAQ error, not a port gap); Gold-only wilds (Mankey line Route 9, Growlithe Routes 7/8) come from the ROM version branch; Route 8's PRZCureBerry fruit tree is a fruittree op (Vm.lua:1218) into Apricorns.lua daily reset. One Kanto smoke-run converts the whole block. (s23/24) Nothing touches Routes 11-19, Fuchsia City/gym, Vermilion Snorlax, Diglett's Cave, Route 2, Pewter; the report's three blocker findings (START menu row, Pokegear card flags, radio persistence) are exactly the UI-plumbing breaks data audits miss: a gold_snorlax_wake.lua that grants the gear and EXPN card the way the game does (setflag 4, setflag 0, setflag 3 through the VM, not by poking save fields), tunes the radio, closes the gear, and talks to the Snorlax would currently fail at three separate steps; gold_cycling_road.lua and gold_fuchsia_gym.lua are the other two cheap proofs (gold_menu_shots.lua:72-73 only ever hand-seeded save fields). (s27/28) Drive first: (1) Cinnabar Blue talk -> disappear -> clearevent 1910 -> Viridian Gym populated -> Blue beaten -> ENGINE_EARTHBADGE set -> VAR_BADGES answers 16 (a break anywhere makes the 16th badge unobtainable and the Mt. Silver gate unpassable); cache chain exists: CINNABAR_ISLAND obj 1 SPRITE_BLUE at (9,6) scriptKey 4e:4985 eventFlag 1909; VIRIDIAN_GYM objs 1-2 gated on 1910; (2) the ROUTE_20 MAPCALLBACK_NEWMAP 4e:4cfa setevent 215 / ROUTE_19 MAPCALLBACK_TILES 4e:4f09 changeblock unseal pair (regression walls off Route 19). Driver: surf Pallet -> Route 21 -> Cinnabar -> talk to Blue -> Route 20 -> beat Blaine -> fly to Viridian -> beat Blue. (s29/30) No rows for Kanto OAKS_LAB, ROUTE_22, ROUTE_28, SILVER_CAVE_OUTSIDE, or the Union Cave B2F Lapras revisit (route.lua's only Union Cave rows are 03.29/04.g); risk concentrates on composites never run together: the 16-badge Oak visit with ProfOaksPCBoot text chain, the double Cut approach to the Route 28 west strip, surf-to-talk on the drifting SWIM_WANDER Lapras (Lake of Rage Gyarados proves the shape via gold_lake_probe.lua), and the daily-flag consume-and-respawn cycle across a save reload; a targeted gold_lapras_probe driver (clockDay=5, walk in, assert object present, catch, walk out, re-enter, assert gone) plus route extension converts inspected to proven. (s31) All six Silver Cave maps decode (warps, item balls flags 1689-1693, hidden items, SILVER_CAVE_OUTSIDE flypoint callback, PALETTE_DARK on Room 1, grass/water tables including Room 2 water); Flash lifts PALETTE_DARK via Palettes.isDarkness + World flashUsed; Surf/Waterfall live in FieldMoves with the badge table; gen2 ledges landed via Permissions.ledgeFacings / World:tryLedgeJump (the asm-walk's 'ledge hop missing' row is stale, GOLD-NEXT-RUN records the fix); the flypoint is FieldMoves.lua:386 flag 75; but no driver has entered Silver Cave, used Waterfall on Room 2's two bands, hopped Room 1's Escape Rope ledge, or talked to Red (closest: gold_halloffame_shots, gold_hm07_probe which proved Waterfall only at Tohjo Falls). (s14) The bot covers sections 10,11,12,13,16,17,18 and never section 14 (Ho-Oh is optional); untested: the 4F-9F ladder/hop mazes (depend on the recently added ledge-hop and one-way-wall support), the Ecruteak entrance sage scene retarget from Morty's setmapscene, the 1F sage unmasking off EVENT_TEAM_ROCKET_DISBANDED (set on the Gold arm of the Radio Tower boss script), the roof save/reset loop, and the entire Gold-side Whirl Islands dive; needs one probe driver carrying a RAINBOW_WING save up the tower and one diving to the Lugia chamber.
|
||||
|
||||
*How to prove it:* Extend tests/drivers/gold/route.lua past row 18.20 into sections 19-32, and/or write the named targeted drivers: gold_snorlax_wake.lua, gold_cycling_road.lua, gold_fuchsia_gym.lua, a gold_lapras_probe (clockDay=5 present/absent cycle), a Cinnabar-to-Viridian 16th-badge driver, a Route 20/19 unseal driver, a Silver Cave/Red driver, a Tin Tower RAINBOW_WING probe, and a Whirl Islands Lugia dive probe.
|
||||
|
||||
*Notes:* Merged from the parent heading plus six 'Also filed as' sub-findings (all one cause). Doc's own ratings: parent missing/minor (s19,20, already in ledger); s21,22 unverified/minor; s23,24 unverified/minor; s27,28 missing/major; s29,30 unverified/minor; s31 unverified/minor; s14 unverified/minor. The adversarial re-check ran on the s27/28 sub-claim, tried to refute and could not; its one correction: tests/drivers/gold/flag_names.lua and map_regions.lua DO name the Kanto maps (map_regions even carries CINNABAR/SEAFOAM/VIRIDIAN region graphs, so bot travel there is data-ready) but they are lookup tables, not executed coverage. 'Implemented statically, executed by nobody' matches the project's Roamers/Events:restore failure mode. GOLD-WALK-HANDOFF sections 1 and 4 already document the Johto-only scope.
|
||||
|
||||
|
||||
### Olivine Port pier and Fast Ship boarding gate: the only road into Kanto, fully extracted, never executed
|
||||
|
||||
walkthrough section(s) 19 | unverified
|
||||
|
||||
Coord event at (7,15) (cache 5b:407d) does the full cart sequence: temporary-event bails, first-time bypass on event 48, readvar VAR_WEEKDAY branch, yesorno, checkitem S_S_TICKET (item 68), setevent 1, 7-step applymovement, sjump into gangway script 5b:401e which plays SFX, disappears/appears the sailor, runs special 46 = FadeOutToWhite (specialOrder[46+1], implemented Specials.lua:998), clears the eight eastbound EVENT_BEAT_* rematch flags on repeat trips, sets FAST_SHIP_1F scene 1, and warps to (25,1). Port sprite toggles for the post-Hall-of-Fame sailor (events 1847/1848) are armed by the HOF script. All data extracted, every op has a proven Johto call site; matched op-for-op against pokegold maps/OlivinePort.asm. Needs one live run or driver.
|
||||
|
||||
*How to prove it:* A driver or bot section that reaches OLIVINE_PORT post-HOF with the S_S_TICKET, boards, and asserts the warp to FAST_SHIP_1F (25,1) with scene 1 set. No gold_* driver currently reaches OLIVINE_PORT post-HOF.
|
||||
|
||||
*Notes:* Doc rating: unverified/blocker, new. If this chain fails, the walkthrough stops here (Kanto unreachable).
|
||||
|
||||
|
||||
### S.S. Aqua maiden voyage: trainer gauntlet, lazy sailor, granddaughter, Metal Coat, docking; extracted end to end, never run
|
||||
|
||||
walkthrough section(s) 19 | unverified
|
||||
|
||||
Verified in cache, never executed: FastShip1F enter scene 5b:48a8 sdefer 5b:48ad (SFX_BOAT, earthquake 30, blackoutmod to cabin map, clearevent 49, scene 2 on first trip); grandpa bump coord events at (24,6)/(25,6); maiden-trip trainer objects behind flag 1849 with parties matching data/trainers/parties.asm exactly (NOLAND Sandslash31/Golem33, LYLE Koffing28/Flareon31/Koffing28, COLIN Delibird32 holding BERRY, MEG&PEG, FRITZ Mr.Mime/Magmar/Machoke, JEFF 2x Raticate32, DEBRA Seaking33, STANLY Machop31/Machoke33/Psyduck26, all in trainers.lua); B1F blocking-sailor coord scripts 5b:5e0c/5b:5e21 plus shared talk script 5b:5e37 clearing event 1837 to spawn the lazy sailor; FastShipLazySailorScript 5b:4d68 (playmusic, loadtrainer SAILOR 9, reloadmap, special 27 = HealParty, setmapscene B1F NOOP, readvar VAR_FACING walk-out); bed script 5b:521b (HealParty, fades, ReloadSpritesNoPalettes, RestartMapMusic, dock check on events 49/50/48 so sleeping cannot dock the maiden voyage); granddaughter payoff 5b:55c6 (fade, disappear/moveobject/appear teleport, showemote, sjump 5b:5633 = Metal Coat verbosegiveitem plus EVENT_FAST_SHIP_HAS_ARRIVED/FOUND_GIRL); door guard 5b:48d2 with arrived arm 5b:48ec (scall facing arm, setmapscene VERMILION_PORT scene 1, warp to VERMILION_PORT 7,17). Specials 27/46/47/49/50/60 resolve to HealParty/FadeOutToWhite/FadeOutToBlack/FadeInFromBlack/ReloadSpritesNoPalettes/RestartMapMusic, all implemented. Nothing in the chain touches a stubbed special, callasm, or VAR. Matched against pokegold maps/FastShip*.asm.
|
||||
|
||||
*How to prove it:* A bot section or driver for the full crossing (board, fight or dodge the gauntlet, sleep check, granddaughter Metal Coat, dock at VERMILION_PORT 7,17).
|
||||
|
||||
*Notes:* Doc rating: unverified/blocker, new. If any link breaks, arrival in Kanto is impossible.
|
||||
|
||||
|
||||
### Machine Part quest chain (Power Plant manager, Cerulean Gym grunt scene, Route 24 grunt, part return, TM07) never executed
|
||||
|
||||
walkthrough section(s) 21,22 | unverified
|
||||
|
||||
The spine of sections 21-22: manager talk sets EVENT_MET_MANAGER_AT_POWER_PLANT 202, clears EVENT_CERULEAN_GYM_ROCKET 1901 and EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM 251, arms CERULEAN_GYM scene 1 and the POWER_PLANT guard phone-call scene; grunt runs out of the gym (sets 203, clears 1900/1902, arms ROUTE_25 scene 1); beat the Route 24 grunt (GRUNTM 31, L30 Golbat); return the part for TM07 Zap Cannon plus EVENT_RESTORED_POWER_TO_KANTO 205 and the Saffron station population flip (clears 1906). Statically verified: cache holds full bytecode for PowerPlantManager 54:4dbd with both branches (54:4deb takeitem MACHINE_PART / 54:4e04 TM give guarded by event 223), gym scene script 54:4332 sdefer 54:4336 with all 34 rows, Route 24 grunt script 50:4407; initial_events.lua seeds flags 251/1900/1901/1902/1903/1906/1907 at new game exactly as InitializeEventsScript does (pokegold engine/events/std_scripts.asm:546-550); World.lua:607 applies the seed while EVENT_INITIALIZED_EVENTS is clear. Engine deps all have call sites: World:trySceneScript (World.lua:5369, called at :6893), setmapscene/setscene (Vm.lua:274-284), specials RestartMapMusic/FadeOutToBlack/FadeInFromBlack/ReloadSpritesNoPalettes/FadeOutMusic (Specials.lua:999-1072). Missing only run-time proof: route bot covers sections 00-18, no Kanto driver exists.
|
||||
|
||||
*How to prove it:* Route extension or a driver walking manager -> gym grunt scene -> Route 24 grunt battle -> part return, asserting events 202/203/205 set and 1900/1901/1902/251/1906 cleared and TM07 in bag.
|
||||
|
||||
*Notes:* Doc rating: unverified/blocker, new.
|
||||
|
||||
|
||||
### Route 25 Misty date scene, Cerulean gym population, and Misty fight never executed (and Misty gives no TM, contrary to walkthrough)
|
||||
|
||||
walkthrough section(s) 21 | unverified
|
||||
|
||||
The date cutscene at Route 25 cells (42,6)/(42,7) is what clears EVENT_TRAINERS_IN_CERULEAN_GYM 1903 and populates the gym with Misty, three swimmers, and the guide; without it Misty never appears and the Cascade Badge is unreachable. Cache has both coord events with sceneId=1 (scripts 18305/18369) and full scripts 50:4781/50:47c1 (34-row cutscene: heart emote, boyfriend flees, Misty approach movement, clearevent 1903, setscene 0, special 60=RestartMapMusic), armed by gym grunt script 54:4336 row 27 (setmapscene group=7 map=16 scene=1). Engine deps: World:tryCoordScript (World.lua:5006) filters on the map scene id; mapScenes persist on the save (GOLD-INDEX tier note on World:loadPlayerData). CeruleanGymMistyScript 54:438a is in the cache; MISTY trainer 1 party (L42 Golduck, L42 Quagsire, L44 Lapras, L47 Starmie with full movesets) extracts correctly; baseMoney 25 so the 4700G payout follows from Prize.reward.
|
||||
|
||||
*How to prove it:* Driver: arm the scene via the gym grunt, trigger the date coord event at (42,6)/(42,7), assert 1903 cleared and the gym populated, then fight Misty and assert the Cascade Badge and 4700G.
|
||||
|
||||
*Notes:* Doc rating: unverified/blocker, new. Correction embedded in the doc: Misty gives NO TM on the Gold cart, only the badge plus retroactive swimmer beat flags; the walkthrough and the lead's 'TM from Misty' are wrong about the cart, nothing is missing in the port there.
|
||||
|
||||
|
||||
### PASS quest: Copycat lost item and Fan Club doll chain fully extracted, never run
|
||||
|
||||
walkthrough section(s) 22 | unverified
|
||||
|
||||
Copycat (COPYCATS_HOUSE_2F object 1, variable sprite slot 251) talk after the machine part return sets EVENT_MET_COPYCAT_FOUND_OUT_ABOUT_LOST_ITEM 206; Fan Club Clefairy guy (POKEMON_FAN_CLUB object 3, script 59:437b) then gives LOST_ITEM 130 and disappears the doll (flag 1908, correctly NOT in the initial seed; doc evidence line also cites POKEMON_FAN_CLUB object 5 flag 1908); returning it (Copycat branch 61:528b: takeitem 130, setevent 208, clearevent 1907 to re-show her shelf doll, then PASS 134 via 61:529d) yields the PASS. All bytecode in cache (61:5235/61:5270/61:528b); the mimicry gag's variablesprite slot 11 plus special 93 resolves to LoadUsedSpritesGFX (specialOrder[94], Specials.lua:1033); the Copycat's variable sprite slot is seeded through World.initialSprites/findInitialSprites (World.lua:1530, the fix GOLD-INDEX credits for the Copycat existing at all). Flag 1907 is in the initial seed so the shelf doll is hidden until the return; 1906 is seeded so the Saffron station stays empty until power is restored.
|
||||
|
||||
*How to prove it:* Driver: after machine-part return, talk to Copycat (assert 206), get LOST_ITEM at the Fan Club (assert doll flag 1908 set), return it (assert 208 set, 1907 cleared, PASS 134 in bag).
|
||||
|
||||
*Notes:* Doc rating: unverified/major, new. Correction embedded: the walkthrough's 'Rail Pass then Magnet Train Pass' is one item (PASS) on the cart, not a port gap. Merge-with hint: the s20 entry 'Copycat lost doll quest chain toward the rail PASS' (copycat-doll-pass-s20) describes the same quest from the section-20 audit with slightly different event ids cited (210/201 gate checks there vs 206/208 here).
|
||||
|
||||
|
||||
### Magnet Train ride wired end to end; arrival behavior needs a human eye, never ridden since
|
||||
|
||||
walkthrough section(s) 22 | unverified
|
||||
|
||||
SaffronMagnetTrainStationOfficerScript (cache 61:4bc2/61:4bd0, rows 11-14) checks EVENT_RESTORED_POWER_TO_KANTO 205 then checkitem PASS 134, does setval 1, special id=35 which resolves to MagnetTrain (specialOrder[36]), then warpcheck and newloadmap 249 (MAPSETUP_TRAIN). All implemented: H.MagnetTrain (Specials.lua:2119) reads scriptVar for direction and blocks on World:magnetTrain (World.lua:2393) which pushes Gen2MagnetTrainRide; Vm handles warpcheck (:1135) and newloadmap (:1177); MAPSETUP_TRAIN defined at World.lua:175. Gating events (205 set by the Power Plant manager, PASS from the Copycat) are this section's other findings.
|
||||
|
||||
*How to prove it:* WHATS-NEXT item 8: a human rides both ways and presses a direction on arrival; the conversation must start on the first step because south is the only legal step out of the doorway cell. Nothing has run this since.
|
||||
|
||||
*Notes:* Doc rating: unverified/major, already named in the ledger.
|
||||
|
||||
|
||||
### Silver Wing in Gold: Pewter City gramps script extracted, but Kanto (and the Whirl Islands dive) never run
|
||||
|
||||
walkthrough section(s) 14 | unverified
|
||||
|
||||
For a Gold cart, Lugia needs the SILVER_WING carried in the bag (WhirlIslandLugiaChamberLugiaCallback does checkevent EVENT_FOUGHT_LUGIA then checkitem SILVER_WING), and in Gold the wing comes from the Pewter City gramps (pokegold maps/PewterCity.asm PewterCityGrampsScript: checkver, Silver arm branches away, Gold falls through to verbosegiveitem SILVER_WING + setevent). Verified in cache: PEWTER_CITY object 3 (SPRITE_GRAMPS at 29,17) scriptKey 4d:583e = faceplayer / opentext / checkver / iftrue 4d:585c / checkevent 121 / verbosegiveitem item 71 / setevent 121 / closetext / end, all generic opcodes. The Lugia chamber map, its SPRITE_LUGIA object (scriptKey 47:41a0, eventFlag 1853), and its MAPCALLBACK_OBJECTS callback (47:418c) are in the cache using only implemented machinery. Route bot has no section 14/25 rows (grep 'id = "1[0-9]' shows 10,11,12,13,16,17,18). One mechanical gap on the dive path: the waterfall forced-down item, reported separately in the doc.
|
||||
|
||||
*How to prove it:* Driver or bot rows: reach Pewter in Gold, talk to the gramps (assert SILVER_WING item 71 given, event 121 set), then the full Whirl Islands dive to the Lugia chamber.
|
||||
|
||||
*Notes:* Doc rating: unverified/major, new. The waterfall forced-down item on the dive path is a separate finding elsewhere in the doc, not part of this entry.
|
||||
|
||||
|
||||
### Vermilion Port arrival bookkeeping, Vermilion flypoint, and Kanto music never executed
|
||||
|
||||
walkthrough section(s) 19 | unverified
|
||||
|
||||
Arrival scene (VERMILION_PORT scene 1, sdefer 5b:450e) extracted intact: one step up, appear the gangway sailor, setscene 0, end-of-voyage flags (1841/1840 hide grandpa and granddaughter for good, 1849 retires the maiden-trip trainers, clear 1843 to reveal the Olivine passage Pokefan, set 48 EVENT_FAST_SHIP_FIRST_TIME), and blackoutmod VERMILION_CITY. ENGINE_FLYPOINT_VERMILION comes from MAPCALLBACK_NEWMAP on both VERMILION_PORT and VERMILION_CITY (setflag 57); the port's fly table carries the matching row (FieldMoves.lua:392) and World:runMapCallback has a live call site (World.lua:5659 plus the MAPCALLBACK_OBJECTS call in setMap). Fly deliberately stays region-locked exactly like the cart (FieldMoves.flyPoints, FieldMoves.lua:442: Kanto page only after SPAWN_INDIGO, no cross-region fly, verified against pokegold engine/pokegear/pokegear.asm .KantoFlyMap). Kanto music extracted: audio.lua songs has 92 entries, musicOrder includes Music_VermilionCity and Music_ViridianCity (musicOrder[63]=Music_VermilionCity), mapSongs carries all 362 map rows.
|
||||
|
||||
*How to prove it:* Driver: dock at Vermilion, assert flags 1841/1840/1849/48 set and 1843 cleared, flypoint flag 57 set on map entry, and Vermilion/Viridian city music playing.
|
||||
|
||||
*Notes:* Doc rating: unverified/major, new.
|
||||
|
||||
|
||||
### Pokemon Fan Club chairman Rare Candy give never run
|
||||
|
||||
walkthrough section(s) 19 | unverified
|
||||
|
||||
Chairman script 59:4340: checkevent 212 (already heard), checkevent 211 (bag-full retry), yesorno where answering No gives nothing (cart behavior), two promptbutton speech pages, verbosegiveitem item 32 = RARE_CANDY, setevent 212. The cart quirk that nothing ever sets the bag-full retry event 211 is faithfully carried in the extracted bytecode. Same map also holds the Clefairy doll object (flag 1908) and Clefairy Guy script 59:437b that the later Copycat quest reads. yesorno, verbosegiveitem, and multi-page writetext all have proven Johto call sites. Matched against pokegold maps/PokemonFanClub.asm; RARE_CANDY present in items.lua.
|
||||
|
||||
*How to prove it:* Covered by any Kanto smoke-run that talks to the chairman and asserts RARE_CANDY given and event 212 set.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new.
|
||||
|
||||
|
||||
### Lt. Surge gym: trainers, Thunder Badge, AI item use fully extracted, never fought
|
||||
|
||||
walkthrough section(s) 19 | unverified
|
||||
|
||||
Surge script 59:4bfc: checkflag 36 re-fight guard, loadtrainer class 19 member 1, winlosstext, reloadmapafterbattle (whose loss-abort semantics were fixed during the Johto campaign), retroactive setevent of the three gym trainers, setflag 36 = ENGINE_THUNDERBADGE which routes to save.player.kantoBadges.THUNDER. Parties in trainers.lua match data/trainers/parties.asm exactly: Surge is RAICHU44/ELECTRODE40/MAGNETON40/ELECTRODE40/ELECTABUZZ46 with full TRAINERTYPE_MOVES movesets; Vincent, Horton, Gregory all match (GUITARIST 2/JUGGLER 3/GENTLEMAN 3). Trainer AI item use implemented: Ai.ITEM_ORDER/Ai.chooseItem (Ai.lua:1368-1400) dispatched from Battle.lua:1790 (AI_SwitchOrTryItem) off the extracted class attributes. The gym has no puzzle in Gen 2, matching the walkthrough's power-outage line.
|
||||
|
||||
*How to prove it:* Driver or route rows: fight the gym trainers and Surge, assert ENGINE_THUNDERBADGE (flag 36) set into save.player.kantoBadges.THUNDER and the AI HYPER_POTION use path exercised.
|
||||
|
||||
*Notes:* Doc rating: unverified/major, new. Correction embedded: the walkthrough's claim that Surge holds a Full Restore is a FAQ error; pokegold data/trainers/attributes.asm line 114 (block 113-117) gives the Lt Surge class HYPER_POTION and the cache carries exactly that (LT_SURGE items=HYPER_POTION, baseMoney 25).
|
||||
|
||||
|
||||
### Route 6 Underground Path blocker and Saffron gate guard extracted, never walked
|
||||
|
||||
walkthrough section(s) 20 | unverified
|
||||
|
||||
The walkthrough's 'large man who blocks the entire entrance' is the ROUTE6_POKEFAN_M object at (17,4) whose event flag EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH hides him only when SET (set by the Power Plant script in a later section); the port's object-visibility rule 'flag set hides object' is implemented and documented at Events.lua:1-2, and his script is a bare jumptextfaceplayer about the Power Plant. The Route 6 Saffron gate guard at (0,4) stands off the walking lane and is dialogue only (branches on EVENT_RETURNED_MACHINE_PART). ROUTE_6 grass and water encounter tables (Abra/Magnemite slots) are present in encounters.lua. Nothing gated on engine work; it has just never been walked.
|
||||
|
||||
*How to prove it:* Covered by any Kanto walk through Route 6 before and after the Power Plant script sets the blocker flag.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new.
|
||||
|
||||
|
||||
### Mr. Psychic's TM29 Psychic give extracted verbatim, Kanto walk to the door unproven
|
||||
|
||||
walkthrough section(s) 20 | unverified
|
||||
|
||||
Cache 61:4b15: checkevent 227, writetext, promptbutton, verbosegiveitem item 221 = TM_PSYCHIC_M, iffalse full-bag fall-through WITHOUT setting the flag (cart behavior, re-talk after making room), setevent 227. TM_PSYCHIC_M is in items.lua with pocket TM_HM and price 2000; the four-pocket bag and TM teaching were exercised in the Johto run (TM08 ROCK SMASH teach passed on merit per GOLD-WALK-HANDOFF). Matched against pokegold maps/MrPsychicsHouse.asm.
|
||||
|
||||
*How to prove it:* Any Kanto run that walks to the house, talks, and asserts TM29 given and event 227 set (plus the full-bag retry arm).
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new.
|
||||
|
||||
|
||||
### Silph Co. 1F Up-Grade give extracted, never run
|
||||
|
||||
walkthrough section(s) 20 | unverified
|
||||
|
||||
Cache 61:4f81: same shape as Mr. Psychic, verbosegiveitem item 172 = UP_GRADE guarded by event 222 with the full-bag fall-through. The officer at (13,1) has event flag -1 so he never moves, and SILPH_CO_1F has only the two street-door warps: there is no upstairs in Gen 2, so the 'man blocking the stairway' never opens, matching the cart (verified against pokegold maps/SilphCo1F.asm). Generic mechanisms only. UP_GRADE present in items.lua.
|
||||
|
||||
*How to prove it:* Any Kanto run that enters Silph Co. 1F and asserts UP_GRADE given behind event 222.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new.
|
||||
|
||||
|
||||
### Saffron Gym teleporter maze (30 same-map warp pads) never exercised; same-map warp chains untested anywhere
|
||||
|
||||
walkthrough section(s) 20 | unverified
|
||||
|
||||
The gym's whole puzzle is 30 warp pads (cache SAFFRON_GYM warps 3-32) that all target SAFFRON_GYM itself; the only route to Sabrina is entrance pad (11,15) -> ... -> (1,5) -> (11,9). The engine mechanism exists and reads correct: pads are COLL_WARP_PANEL 0x7c, an immediate warp (Permissions.lua:311); World:takeWarp (World.lua:6022-6040) handles a destination equal to the current map through setMap; warpCooldown keyed on the landing cell (World.lua:6135 set, 6217-6222, 6240 suppression check) stops the landing pad from re-firing until the player steps off, exactly what a pad-to-pad maze needs. But the asm-walk's own port-coverage table flags that no driver has ever exercised a same-map warp chain, and no Kanto run exists. If this breaks, Sabrina is unreachable and the 10th badge (and eventually Red's gate) is lost.
|
||||
|
||||
*How to prove it:* Targeted driver: enter the gym, ride pad (11,15), assert arrival at (19,17), then the full 5-hop chain to (11,9).
|
||||
|
||||
*Notes:* Doc rating: unverified/major, new.
|
||||
|
||||
|
||||
### Sabrina battle and Marsh Badge fully extracted, never fought
|
||||
|
||||
walkthrough section(s) 20 | unverified
|
||||
|
||||
Sabrina script (cache 61:40cf): checkflag 39 guard, loadtrainer class 35 member 1, reloadmapafterbattle, force-sets the four gym trainer flags (so the trainers are skippable, as the walkthrough implies), setflag 39 = ENGINE_MARSHBADGE routed to save.player.kantoBadges.MARSH. Party matches the cart exactly (trainers.lua SABRINA 1: ESPEON46 Sand-Attack/Quick Attack/Swift/Psychic, MR__MIME46 Barrier/Reflect/Baton Pass/Psychic, ALAKAZAM48 Recover/Future Sight/Psychic/Reflect; class item HYPER_POTION, base reward 25, AI attribute words extracted; attributes.asm:209-213). The four trainers (Rebecca, Franklin, Doris, Jared; MEDIUM 6,7 / PSYCHIC_T 2,11; parties verified including the cart's L35 third Exeggcute that the FAQ gets wrong) are OBJECTTYPE_TRAINER with SPINRANDOM movement, which Npc.lua supports, and sight/battle handoff is the proven Trainers.lua path. Prize money base x last-level x4 is implemented in Prize.lua (matches the walkthrough's 4800G).
|
||||
|
||||
*How to prove it:* Driver through the warp maze to Sabrina; assert ENGINE_MARSHBADGE (flag 39) into save.player.kantoBadges.MARSH, the four trainer flags force-set, and the 4800G payout.
|
||||
|
||||
*Notes:* Doc rating: unverified/major, new. Corrections embedded: the L35 third Exeggcute is cart-correct (the FAQ is wrong); Sabrina's class item is HYPER_POTION.
|
||||
|
||||
|
||||
### Copycat lost doll quest chain toward the rail PASS (section-20 filing): all pieces present and generic, never run
|
||||
|
||||
walkthrough section(s) 20 | unverified
|
||||
|
||||
Spans this section's maps (Copycat's house is Saffron warp 8, the doll source is the Vermilion Fan Club) though the walkthrough text defers it to after the Power Plant (EVENT_RETURNED_MACHINE_PART gates the dialogue arms). Copycat's 2F object uses variable sprite 251 (SPRITE_COPYCAT), and the new-game variablesprite seeding that used to leave Copycat despawned was fixed during the Johto campaign (tests/gen2_variable_sprites_test.lua, GOLD-WALK-HANDOFF section 5); her script (cache 61:5235) is extracted bytecode whose only special, LoadUsedSpritesGFX, is implemented (Specials.lua:1033); the Fan Club Clefairy Guy (59:437b) checks events 210/201 and gives LOST_ITEM; the doll object (flag 1907) and the PASS handover (verbosegiveitem PASS, setevent EVENT_GOT_PASS_FROM_COPYCAT per pokegold maps/CopycatsHouse2F.asm:61-73) are ordinary ops; PASS is a KEY_ITEM in items.lua. Nothing engine-shaped is missing.
|
||||
|
||||
*How to prove it:* Same driver as the s22 PASS quest entry: full Copycat/Fan Club round trip asserting LOST_ITEM, doll flags, and PASS.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new, named in the leads. Merge-with hint: same quest chain as pass-quest-copycat-fanclub (s22); kept separate because the doc filed them as distinct headings from two section audits. This filing cites Clefairy Guy gate events 210/201 and doll flag 1907; the s22 filing cites 206/208 and doll-disappear flag 1908.
|
||||
|
||||
|
||||
### Celadon side items: TM03 Curse night gate, Leftovers trash can, hidden PP Up (plus Berserk Gene, Route 9 Ether, Route 25 Potion) never run
|
||||
|
||||
walkthrough section(s) 22 | unverified
|
||||
|
||||
TM03 Curse: roof house pharmacist script 5e:5083 in cache with checktime mask 4 (NITE) at row 7; the Vm checktime opcode (Vm.lua:788) maps NITE to bit 4 off World:timeOfDayId, so the night-only gate should behave; the give branch 5e:509a is guarded by EVENT_GOT_TM03_CURSE 218. Leftovers: CeladonCafeTrashcan bg event at (7,1) key 5e:648c present, plain giveitem bytecode with the flag only set on success so a full pack retries, exactly the cart. Hidden PP Up at Celadon (37,21): BGEVENT_ITEM row present, handled by HiddenItems (fixed PP_UP; the walkthrough's 'random PP Up' is wrong on the cart too). Berserk Gene at Cerulean (2,12), hidden Ether Route 9 (10,5), and hidden Potion Route 25 (4,5) likewise extract. All generic and statically sound, none ever run.
|
||||
|
||||
*How to prove it:* Kanto run collecting each: night visit for TM03 (checktime NITE), cafe trash can Leftovers with full-pack retry, hidden-item picks via HiddenItems.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new. Correction embedded: the PP Up is fixed, not random, on the cart.
|
||||
|
||||
|
||||
### Mt. Moon Square Clefairy dance (Monday night) + hidden Moon Stone + Rock Smash rock: all dependencies present, sequence never executed
|
||||
|
||||
walkthrough section(s) 26 | unverified
|
||||
|
||||
Coord event at (7,11) scene 0 runs 5b:676a, whose 45 rows extract completely: checkflag 87 (ENGINE_MT_MOON_SQUARE_CLEFAIRY), readvar var 11 (VAR_WEEKDAY, real in the port) ifnotequal 1 (MONDAY), checktime NITE, then the appear/follow/applymovement/cry/showemote dance, clearevent 236 (the hidden Moon Stone flag) and setflag 87. Both callbacks extracted (type 5 NEWMAP 5b:6763 re-hides the stone on entry; type 2 OBJECTS 5b:6767 hides the rock). The rock object (SPRITE_ROCK at 7,7, scriptKey 5b:67ee = jumpstd SmashRockScript) resolves to the real SmashRock/AskRockSmash bodies GOLD-INDEX.md:377-382 documents; follow/stopfollow are implemented (Vm.lua:1009,1026). Flag semantics match the cart: ENGINE_MT_MOON_SQUARE_CLEFAIRY lives in wDailyFlags2 (pokegold constants/engine_flags.asm:105-106) and the port clears id 87 in Apricorns.DAILY_ENGINE_FLAGS, so the dance is once per day, i.e. once per Monday night. Unproven in-game: (a) whether the appeared rock correctly blocks/receives the A press on the Moon Stone tile at (7,7) and Rock Smash then exposes the hidden item in the same visit, and (b) the full follow-chain animation. The stubbed RockMonEncounter (CallAsm STUB_ROWS) does NOT affect this rock: MOUNT_MOON_SQUARE is not in the cart's RockMonMaps (pokegold data/wild/treemon_maps.asm:43-48), so no encounter roll is owed here.
|
||||
|
||||
*How to prove it:* Driver: force clockDay=MONDAY clockHour=NITE, walk to (7,11), watch the dance, smash the rock, take the Moon Stone.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new.
|
||||
|
||||
|
||||
### Rival re-encounters after Mt Moon (Indigo Plateau Mon/Wed rematch, Dragon's Den Tue/Thu cameo) never run
|
||||
|
||||
walkthrough section(s) 26 | unverified
|
||||
|
||||
Both beats are pure extracted content keyed on EVENT_BEAT_RIVAL_IN_MT_MOON (set by the implemented Mt Moon script) and VAR_WEEKDAY (real in the port; World:readVar VAR_WEEKDAY at World.lua:1289): the cache carries INDIGO_PLATEAU_POKECENTER_1F's NEWMAP callback 5a:48b6 (23 rows of setmapscene/clearevent resets) plus its two coord events, and DRAGONS_DEN_B1F's callback 47:44e1 (checkevent 793 -> disappear object 4). The daily rematch flag ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT (id 92) is in Apricorns.DAILY_ENGINE_FLAGS so it clears on the daily rollover. The rematch parties RIVAL2 members 4-6 (L45-50 with CROBAT) are in trainers.lua. Nothing looks missing, but the coord-event choreography in the Pokecenter (rival walks in as you leave) is the kind of scene worth a driver before trusting; filed unverified rather than implemented for that reason.
|
||||
|
||||
*How to prove it:* Driver: set EVENT_BEAT_RIVAL_IN_MT_MOON via the Mt Moon fight, force weekday Monday/Wednesday, enter the Indigo lobby and assert the rematch triggers; force Tuesday/Thursday and assert the Dragon's Den object 4 state.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new. Merge-with hint: overlaps indigo-lobby-rival-rematch (s18), the same Indigo Plateau rematch filed from the section-18 audit; that filing cites World:weekday at World.lua:1247 and scriptKeys 5a:48ff/5a:4940.
|
||||
|
||||
|
||||
### Indigo lobby rival rematch (Monday/Wednesday, RIVAL2, post Mt. Moon) armed on tiles the walkthrough crosses, never run
|
||||
|
||||
walkthrough section(s) 18 | unverified
|
||||
|
||||
Not in the walkthrough text but armed on the lobby tiles it crosses: coord events at (16,4)/(17,4) run PlateauRivalBattle1/2 (cache scriptKeys 5a:48ff/5a:4940), which fight the RIVAL2 second-tier party when EVENT_BEAT_RIVAL_IN_MT_MOON is set, ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT is clear, and VAR_WEEKDAY is Monday or Wednesday (pokegold maps/IndigoPlateauPokecenter1F.asm). Every dependency exists: coord events and scripts extracted, VAR_WEEKDAY answers the real weekday (World:weekday, World.lua:1247), the daily engine flag is registered for midnight reset (Apricorns.lua:88, id 92 ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT), and RIVAL2 parties 4-6 (SNEASEL/45, CROBAT/48, MAGNETON/45, GENGAR/46, ALAKAZAM/46, starter/50) are in the trainers cache. But the gate flag EVENT_BEAT_RIVAL_IN_MT_MOON is Kanto content no run has ever set.
|
||||
|
||||
*How to prove it:* Should be picked up when section 26 is audited/run: same driver as rival-rematch-mtmoon, entering the lobby on Monday/Wednesday with the Mt Moon flag set and asserting the coord-event battle fires.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new. Merge-with hint: same beat as rival-rematch-mtmoon (s26); kept separate because the doc filed both headings. Line-cite difference between filings: World.lua:1247 (weekday) here vs World.lua:1289 (readVar VAR_WEEKDAY) there.
|
||||
|
||||
|
||||
### Lighthouse descent and swim to Cianwood (route rows 08.50/08.51) never walked; collision data matches the cart, blame points at the bot's region graph
|
||||
|
||||
walkthrough section(s) 08 | unverified
|
||||
|
||||
The route bot has never completed the descent from Jasmine to Route 40, so the Surf crossing, all fourteen Route 40/41 swimmers, and Cianwood arrival are engine-unwalked (runs reach section 08+ by teleport; run 28 still failed rows 08.50/08.51). The handoff narrows it to the 3F seven-cell pocket: warps 8/9 at (8,3)/(9,3) 'do not take' when stood on. Verified in the current cache: OLIVINE_LIGHTHOUSE_3F (8,3)/(9,3) decode to COLL_FLOOR 0x00, and pokegold data/tilesets/lighthouse_collision.asm block $27 is FLOOR,FLOOR,FLOOR,FLOOR, so those two warp rows are one-way landing anchors for the 4F pits at (8,3)/(9,3) (which decode to COLL_PIT 0x60) and never fire on the cart either. The pocket's real exit is warp 3, the staircase at (9,5), which decodes to 0x72 and does fire ((13,3) also 0x72). Fix shape: correct the bot's region graph, which treats landing-anchor warp coordinates as exits (the exact trap GOLD-WALK-HANDOFF.md:593 documents), not the engine; a human following the walkthrough (drop beside Connie, fight Terrell, take the Ether, head up the stairs) uses the (9,5) staircase and should be fine.
|
||||
|
||||
*How to prove it:* Something must walk 6F down to CIANWOOD_CITY in the engine (bot rows 08.50/08.51 passing, or a driver doing the descent and the Route 40/41 Surf crossing); until then the second half of section 08 stays a beat-completeness check rather than a proven path.
|
||||
|
||||
*Notes:* Doc rating: unverified/major, already named in the ledger. The doc's own re-check corrected the suspicion of an engine defect: the cache collision matches the cart exactly, so the failing rows indict the bot's region graph, not the engine or the warp data.
|
||||
|
||||
|
||||
### TM08 Rock Smash give (route row 06.21) flaky across runs; ledger contradicts itself and the failure is unresolved
|
||||
|
||||
walkthrough section(s) 06 | unverified
|
||||
|
||||
The Route 36 fisher gives TM08 after EVENT_FOUGHT_SUDOWOODO (Route36RockSmashGuyScript, generic extracted bytecode with verbosegiveitem guarded by iffalse .NoRoomForTM). The ledger contradicts itself across runs: GOLD-WALK-HANDOFF section 8 says 'Everything before it now passes on merit: Sudowoodo, TM08, the ROCK SMASH teach', but run 28's failure table lists '06.21 ROUTE_36 EVENT_GOT_TM08_ROCK_SMASH not set' while the Sudowoodo row itself passed. Plausible causes: full TM pocket taking the iffalse arm, or the bot failing to stand adjacent. The script path is generic and the same give shape works everywhere else, so this is most likely route flake, but it is unresolved in the latest continuous run and TM08 gates all Rock Smash content downstream (Burned Tower rocks, Route 40/Cianwood rock items).
|
||||
|
||||
*How to prove it:* Re-run route row 06.21 in a continuous run and assert EVENT_GOT_TM08_ROCK_SMASH gets set; instrument for TM-pocket fullness and adjacency to distinguish the two suspected causes.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, already named in the ledger. Not given severity unverified-risk because the problem is a reproduced-once run failure (a live flake), not purely never-executed content.
|
||||
|
||||
|
||||
### New Bark rival push scene (talk to rival by the lab) unproven; low risk, zero coverage
|
||||
|
||||
walkthrough section(s) 00 | unverified
|
||||
|
||||
Walkthrough: 'If you talk to him, he'll push you out of the way.' NewBarkTownRivalScript is optional, repeatable (sets no flag) and uses follow PLAYER, applymovement with turn_head/step/fix_facing/jump_step, and SFX_TACKLE. Every opcode involved is implemented (follow/stopfollow in the VM, jump_step in Movement.lua which the ledge fixes exercised), and the sibling teacher-drag scene has a dedicated driver (tests/drivers/gold_teacher_scene.lua), but no driver or test touches this specific scene and the route bot never talks to optional NPCs. Cache maps.lua NEW_BARK_TOWN objects include the rival with a scriptKey and eventFlag.
|
||||
|
||||
*How to prove it:* A short driver that talks to the rival by the lab and observes the push movement chain, mirroring gold_teacher_scene.lua.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new.
|
||||
|
||||
|
||||
### Kiyo battle and Tyrogue gift (Mount Mortar B1F): fully wired, never exercised by any driver or bot run
|
||||
|
||||
walkthrough section(s) 15 | unverified
|
||||
|
||||
The game's only Tyrogue. Cache scripts['46:5eec'] holds all 29 rows of MountMortarB1FKiyoScript (checkevent 97 / 1193, winlosstext, loadtrainer class BLACKBELT_T member 6, startbattle, reloadmapafterbattle, setevent 1193, readvar var=1 VAR_PARTYCOUNT, ifequal 6, givepoke species 236 level 10, setevent 97); trainers cache has BLACKBELT_T[6] KIYO = L34 HITMONLEE + L34 HITMONCHAN (KIYO object at 13,4 with scriptKey 46:5eec); readvar VAR_PARTYCOUNT is a real read (World.lua:1271); givepoke is implemented at Vm.lua:443. The full dependency chain has never run: Surf across the 1F Outside lake, the in-cave Waterfall climb to warp 4 at (17,5), the 2F/1F-inside/B1F warp descent, the battle, and the gift. The waterfall climb mechanic itself is bot-verified only at Tohjo Falls; route.lua contains no MORTAR/KIYO/DARK_CAVE rows.
|
||||
|
||||
*How to prove it:* A Mount Mortar driver: Surf the outside lake, Waterfall to warp 4 (17,5), descend to B1F, beat Kiyo, assert Tyrogue (species 236, L10) given with a party of <6 and events 97/1193 set.
|
||||
|
||||
*Notes:* Doc rating: unverified/major, new. Correction embedded: GOLD-INDEX's claim that only VAR_WEEKDAY and VAR_FACING answer readvar is STALE; World.lua:1265-1330 now answers essentially every VAR_*.
|
||||
|
||||
|
||||
### Blackglasses pharmacist in Dark Cave Blackthorn entrance extracted exactly, never run (same for TM13 Snore and Revive balls)
|
||||
|
||||
walkthrough section(s) 15 | unverified
|
||||
|
||||
The script is fully extracted (cache scripts['47:436c'], 13 rows matching pokegold maps/DarkCaveBlackthornEntrance.asm exactly: checkevent / verbosegiveitem / iffalse pack-full arm / setevent), the spinning NPC object (SPRITE_PHARMACIST) is in the cache at (7,3) with that scriptKey, and verbosegiveitem is implemented in the VM. Reaching him also needs Flash plus Surf inside the cave, both implemented. The same unverified state covers the TM13 Snore and Revive item balls beside him (item ball arm is implemented and tested). No driver enters Dark Cave and the bot route bypasses it (grep DARK_CAVE in route.lua is empty).
|
||||
|
||||
*How to prove it:* A Dark Cave driver: Flash + Surf to the Blackthorn entrance, talk to the pharmacist (assert Blackglasses given), pick up TM13 Snore and the Revives.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new.
|
||||
|
||||
|
||||
### Master Ball from Elm after eighth badge: route rows exist but optional, no run log confirms the event ever set
|
||||
|
||||
walkthrough section(s) 16 | unverified
|
||||
|
||||
ProfElmScript's ElmCheckMasterBall gate is checkflag ENGINE_RISINGBADGE then verbosegiveitem MASTER_BALL then setevent EVENT_GOT_MASTER_BALL_FROM_ELM (pokegold maps/ElmsLab.asm:57-61). All generic extracted bytecode; checkflag over ENGINE_* badges works since the badge-store fix (GOLD-WALK-HANDOFF.md:502, badges routed into save.player.badges). The route bot has rows 16.24/16.24b for this beat but both are marked optional = true, and no run log or handoff note confirms EVENT_GOT_MASTER_BALL_FROM_ELM was ever actually set in a live run (no MASTER hits in GOLD-NEXT-RUN.md run logs). A full bag silently skips the setevent (verbosegiveitem iffalse), same as the cart.
|
||||
|
||||
*How to prove it:* Make route rows 16.24/16.24b non-optional (or run them) and assert EVENT_GOT_MASTER_BALL_FROM_ELM set with bag space available.
|
||||
|
||||
*Notes:* Doc rating: unverified/major, new.
|
||||
|
||||
|
||||
### Everstone from Elm by showing a self-hatched Togepi: pieces individually proven, full chain never exercised end to end
|
||||
|
||||
walkthrough section(s) 16 | unverified
|
||||
|
||||
The gating special FindPartyMonThatSpeciesYourTrainerID is a real handler with the own-OT check (Specials.lua:1134), and the phone-call prerequisite is NOT required: pokegold maps/ElmsLab.asm ElmCheckTogepiEgg -> EVENT_TOGEPI_HATCHED -> ElmEggHatchedScript re-runs the same species scan, so a hatched Togepi in the party is sufficient (maps/ElmsLab.asm:62-95, engine/phone/scripts/elm.asm:46). Egg hatching has a driver (tests/drivers/gold_egg_hatch.lua) and PC withdraw exists (BoxMenu.lua / PcMenu.lua). The full chain (hatch with your OT, withdraw, talk, EVENT_SHOWED_TOGEPI_TO_ELM, verbosegiveitem EVERSTONE) has never run end to end. The .egghatched arm of ElmPhoneCalleeScript (sets EVENT_TOLD_ELM_ABOUT_TOGEPI_OVER_THE_PHONE) lives in the bank $41 callee scripts and only matters for the alternate entry path. No route row exists for the Everstone.
|
||||
|
||||
*How to prove it:* A driver chaining gold_egg_hatch-style hatching (own OT), PC withdraw, and the Elm talk, asserting EVENT_SHOWED_TOGEPI_TO_ELM and EVERSTONE in bag.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new.
|
||||
|
||||
|
||||
### TM37 Sandstorm happiness gate at the Route 27 house never exercised; gate is first-slot happiness >= 150
|
||||
|
||||
walkthrough section(s) 16 | unverified
|
||||
|
||||
The granny's script is extracted (cache scripts['60:6352'], 9 rows: checkevent, special id 88, ifgreater, sjump into the loyal/disloyal arms) and special id 88 resolves through constants.specialOrder with the VM's order[id+1] rule (Vm.lua:1773) to specialOrder[89] = GetFirstPokemonHappiness, a real handler at Specials.lua:1506 reading the first non-egg party member. The cart gate is happiness >= 150 on the FIRST party slot (ifgreater 150-1), not 'one of your Pokemon'. Happiness accrues via Happiness.lua stepped from StepEvents. No driver or bot row enters ROUTE_27_SANDSTORM_HOUSE.
|
||||
|
||||
*How to prove it:* Driver: enter ROUTE_27_SANDSTORM_HOUSE with a first-slot mon at happiness >= 150 and assert TM37 given; also assert the disloyal arm below 150.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new.
|
||||
|
||||
|
||||
### Route 27 whirlpool island (Bird Keeper Jose, TM22 Solarbeam, Rare Candy) and Tohjo Falls Moon Stone never run; whirlpool mechanic only proven on Route 41
|
||||
|
||||
walkthrough section(s) 16 | unverified
|
||||
|
||||
The whirlpool mechanic is implemented and driver-verified, but on Route 41, not here: tests/drivers/gold_water_moves.lua:105-124 clears a real whirlpool block with GLACIERBADGE gating and asserts the block replacement. The Route 27 island content is all in the cache (TM_SOLARBEAM ball at (60,12) item id 213 with event flag, Rare Candy ball at (53,12), BIRD_KEEPER[14] JOSE = L35 FARFETCH_D with phone contact rows; maps.ROUTE_27 objects rows 6-8) and the item ball arm is implemented, but the bot route crosses Route 27 only via Tohjo Falls and never surfs south past Gilbert (route rows 16.42-16.43 skip the south water), so this specific whirlpool, the Jose fight, and the TM22 pickup have never run. Same for the Tohjo Falls MOON_STONE ball at (2,6) (maps.TOHJO_FALLS object 1), which the walkthrough itself never mentions.
|
||||
|
||||
*How to prove it:* Driver or route rows: surf south past Gilbert, clear the Route 27 whirlpool, fight Jose, collect TM22 and the Rare Candy; separately pick up the Tohjo Falls Moon Stone.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new.
|
||||
|
||||
|
||||
### Route 26 heal house free heal extracted with all specials real, never entered
|
||||
|
||||
walkthrough section(s) 16 | unverified
|
||||
|
||||
The teacher's script is extracted (cache scripts['60:60e6'], 17 rows) and every special id in it resolves via order[id+1] to a real handler: 47 FadeOutToBlack, 50 ReloadSpritesNoPalettes, 27 HealParty, 49 FadeInFromBlack, 60 RestartMapMusic (Specials.lua:450, :999, :1025, and the fade-in/restart pair at :1059). Unconditional and repeatable like the cart. No driver or bot row ever enters ROUTE_26_HEAL_HOUSE; the bot route's Route 26 leg is a single travel row straight to the gate.
|
||||
|
||||
*How to prove it:* Driver: enter ROUTE_26_HEAL_HOUSE with a damaged party, talk, assert full heal and that the scene is repeatable.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new.
|
||||
|
||||
|
||||
### Victory Road item pickups (TM26 Earthquake pit, Full Restore shelf, Max Revive, Full Heal, X Special, two hidden items) never exercised
|
||||
|
||||
walkthrough section(s) 17 | unverified
|
||||
|
||||
The walkthrough collects five Poke Ball items (TM26 Earthquake via the one-way pit at (0,11), Full Restore on the ledge shelf via the (17,19) ladder, Max Revive and Full Heal in the entrance region, X Special on the HOP_LEFT column) plus two hidden items (Max Potion at (3,29), Full Heal at (3,65)). All pieces exist: cache maps.VICTORY_ROAD carries 6 objects (5 with itemball item/qty payloads), 2 bgEvents with hiddenItem tables, 10 warps, 2 coord events; World:interact consumes obj.itemball (World.lua:5483-5490, an earlier bot-found fix); HiddenItems handles BGEVENT_ITEM; Permissions models the COLL_PIT one-way warp, the six ladders, ledge hops (isLedge/ledgeFacings, Gold's own direction order where $a0 is HOP_RIGHT), and COLL_UP_WALL side walls (Permissions.lua:170-260 LEDGE_FACINGS/SIDE_BLOCKS/NEIGHBOR_ARM), with a test section at tests/gen2_world_test.lua:2936. But nothing has walked these routes: the bot's section 17 is only travel/walk rows 17.2, 17.11, 17.15 (no pickup rows), so the pit drop, the two HOP exits from the TM pocket, the HOP_DOWN off the shelf, and the X Special ball sitting ON a HOP_LEFT tile (the one placement the asm-walk flags as needing hardware confirmation) are all unproven end to end.
|
||||
|
||||
*How to prove it:* Driver or route rows through Victory Road collecting all five balls and both hidden items, specifically exercising the (0,11) pit drop, the (17,19) ladder shelf with HOP_DOWN exit, the two HOP exits from the TM pocket, and interaction with the X Special ball on its HOP_LEFT tile.
|
||||
|
||||
*Notes:* Doc rating: unverified/minor, new.
|
||||
|
||||
@@ -4,6 +4,9 @@ A native LÖVE2D recreation of Poke Red, Blue and Yellow. The engine and map
|
||||
behavior are hand-written Lua; game data and graphics are decoded from a ROM
|
||||
supplied by the player.
|
||||
|
||||
> [!CAUTION]
|
||||
> **We are NOT affiliated with the website `gen1recomp[.]com`** That website is not run by this project, was not authorized by us, and we have no idea who operates it. It is impersonating this project; do not download anything from it, and treat anything it hosts or claims as untrustworthy. Even if the site currently links back to this repository, the people behind it can change its content at any time, so nothing on it should ever be trusted. This GitHub repository and the Discord linked below are the only official sources for this project.
|
||||
|
||||
<p align="center"><img src="https://raw.githubusercontent.com/bryanthaboi/gen1recomp/refs/heads/dev/assets/logo/logo.png"></p>
|
||||
|
||||
**SUPPORT / ANNOUNCEMENTS / MODS:** [Discord](https://bois.icu)
|
||||
@@ -50,13 +53,14 @@ supplied by the player.
|
||||
|
||||
|
||||
This project does not include a ROM, emulate the Game Boy, transpile assembly,
|
||||
or download a disassembly. A canonical US Poke Red, Blue, or Yellow ROM is the
|
||||
only game content input.
|
||||
or download a disassembly. A canonical US Poke Red, Blue, Yellow, or Gold 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, and Yellow can all be imported and
|
||||
played side by side.
|
||||
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.
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -64,17 +68,31 @@ 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 1 MiB US Red, Blue, and Yellow ROMs are accepted. The
|
||||
importer verifies SHA-1 before creating any game data:
|
||||
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:
|
||||
|
||||
- Red: `ea9bcae617fdf159b045185467ae58b2e4a48b9a`
|
||||
- Blue: `d7037c83e1ae5b39bde3c30787637ba1d4c48ce2`
|
||||
- Yellow: `cc7d03262ebfaf2f06772c1a480c7d9d5f4a38e1`
|
||||
- Gold: `d8b8a3600a465308c9953dfa04f0081c05bdcb94`
|
||||
|
||||
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
|
||||
audio channel programs copied out of the verified ROM.
|
||||
|
||||
### A note on Windows Defender warnings
|
||||
|
||||
Windows Defender sometimes flags the Windows build with a generic
|
||||
machine-learning detection such as `Trojan:Win32/Wacatac!ml` (#621). This is
|
||||
a known false positive: the exe is the official LÖVE runtime with the game
|
||||
archive appended (the standard way LÖVE games ship), and Defender's
|
||||
heuristics distrust unsigned executables with appended data. Every release
|
||||
publishes SHA-256 checksums (`sha256sums.txt`) so you can verify your
|
||||
download, and you can confirm a flagged file yourself on
|
||||
[VirusTotal](https://www.virustotal.com), where these builds come back clean
|
||||
on every engine except Defender's heuristic. False positives are reported to
|
||||
Microsoft as they come up.
|
||||
|
||||
## Controls
|
||||
|
||||
|
||||
@@ -96,6 +114,7 @@ supported out of the box.
|
||||
| Key | What it does |
|
||||
| --------- | ---------------------------------------------------- |
|
||||
| `-` / `=` | Zoom out / in (overworld; also mouse wheel) |
|
||||
| `1` | Cycle GAME SPEED up (controller: R2 faster, L2 slower) |
|
||||
| `2` | Cycle COLORS |
|
||||
| `3` | Cycle TILT (free-roam overworld) |
|
||||
| `4` | Cycle ZOOM through every level (free-roam overworld) |
|
||||
@@ -105,8 +124,8 @@ supported out of the box.
|
||||
| `F10` | Open / close the mod manager |
|
||||
|
||||
|
||||
COLORS, TILT, ZOOM, GBC FX, and VOID FILL are also in the Options menu
|
||||
and persist in `options.lua`.
|
||||
COLORS, TILT, ZOOM, GBC FX, GAME SPEED, and VOID FILL are also in the
|
||||
Options menu and persist in `options.lua`.
|
||||
|
||||
### Low-end devices
|
||||
|
||||
@@ -188,6 +207,39 @@ even on a different computer, as long as the same folder comes along.
|
||||
already written to either location is touched automatically, so copy files
|
||||
over yourself if you want to carry existing progress across the switch.
|
||||
|
||||
## Launch Options
|
||||
|
||||
By default the app opens the launcher so you can pick a game. Launch options
|
||||
skip it and start one game directly, which is what you want for a one-click
|
||||
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`) |
|
||||
| `--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 |
|
||||
|
||||
|
||||
## Linux on arm64 (Raspberry Pi)
|
||||
|
||||
Alongside the x86_64 `gen1recomp-*-linux.zip`, every release ships
|
||||
`gen1recomp-*-linux-arm64.AppImage` for 64-bit ARM desktop Linux — Raspberry
|
||||
Pi 4/5, Armbian and other SBC distros, and arm64 VMs on Apple Silicon:
|
||||
|
||||
```sh
|
||||
chmod +x gen1recomp-*-linux-arm64.AppImage
|
||||
./gen1recomp-*-linux-arm64.AppImage
|
||||
```
|
||||
|
||||
LÖVE publishes no aarch64 binary of any kind, so this artifact compiles the
|
||||
engine — and SDL2, OpenAL and the codecs — from source inside a Debian
|
||||
bullseye arm64 container. It needs only glibc 2.29+, libstdc++, freetype and
|
||||
zlib on the host; OpenGL, X11, Wayland, KMSDRM, ALSA and PulseAudio are all
|
||||
dlopened, so the same image runs on a full desktop, a Wayland-only session or
|
||||
a KMSDRM handheld with no X server. Build instructions and the reasoning are
|
||||
in [docs/linux-arm64-build.md](docs/linux-arm64-build.md).
|
||||
|
||||
|
||||
## iOS
|
||||
|
||||
Every release ships `gen1recomp-*-ios.ipa`. Sideload it with AltStore
|
||||
@@ -195,6 +247,50 @@ Every release ships `gen1recomp-*-ios.ipa`. Sideload it with AltStore
|
||||
build and install from source on a Mac instead, see
|
||||
[docs/ios-install.md](docs/ios-install.md).
|
||||
|
||||
<div>
|
||||
<a href="https://intradeus.github.io/http-protocol-redirector?r=sidestore://source?url=https://github.com/bryanthaboi/gen1recomp/raw/refs/heads/main/mobile/ios/app-repo.json"><img src="./.github/resources/sidestore-badge.png" alt="Add to SideStore" height="60"></a>
|
||||
|
||||
<a href="https://intradeus.github.io/http-protocol-redirector?r=feather://source/https://github.com/bryanthaboi/gen1recomp/raw/refs/heads/main/mobile/ios/app-repo.json"><img src="./.github/resources/feather-badge.png" alt="Add to Feather" height="60"></a>
|
||||
|
||||
<a href="https://intradeus.github.io/http-protocol-redirector?r=altstore://source?url=https://github.com/bryanthaboi/gen1recomp/raw/refs/heads/main/mobile/ios/app-repo.json"><img src="./.github/resources/altstore-badge.png" alt="Add to AltStore" height="60"></a>
|
||||
|
||||
<a href="https://github.com/bryanthaboi/gen1recomp/releases/latest"><img src="./.github/resources/github-badge.png" alt="Download from GitHub" height="60"></a>
|
||||
</div>
|
||||
|
||||
## Xbox Dev Mode
|
||||
|
||||
Every release ships `gen1recomp-*-xbox-uwp.zip` for Xbox One and Xbox Series
|
||||
consoles in Developer Mode. It cannot be installed in retail mode.
|
||||
|
||||
Extract the archive, then use Xbox Device Portal to install the `.msix` and
|
||||
the x64 package under `Dependencies`.
|
||||
|
||||
### External setup
|
||||
|
||||
1. Put your legally obtained Red, Blue, or Yellow ROMs on an external drive.
|
||||
Mod ZIPs can go on the same drive.
|
||||
2. Connect the drive to the Xbox and open Gen1Recomp.
|
||||
3. Select **Import ROM** or **Import Mod**, then choose the file with the Xbox
|
||||
file picker.
|
||||
4. Repeat the ROM import for each version you want to use.
|
||||
|
||||
### Internal setup
|
||||
|
||||
1. Create a folder named `baseroms` on your PC and place your legally obtained
|
||||
Red, Blue, or Yellow ROMs inside it.
|
||||
2. ZIP the folder, keeping `baseroms` at the top level of the archive.
|
||||
3. Launch Gen1Recomp once, then close it.
|
||||
4. Open Xbox Device Portal and upload the ZIP to
|
||||
`Gen1Recomp/LocalState/pokemon-love2d/`.
|
||||
5. Choose **Yes** when Device Portal asks whether to extract the archive.
|
||||
6. Open Gen1Recomp. The launcher checks baseroms once at startup. When it finds a compatible ROM, that game’s tab shows ROM FOUND and an Import detected ROM button.
|
||||
|
||||
ROMs, generated game data, saves, and mods remain in LocalState and are not
|
||||
included in the app.
|
||||
|
||||
Source builds and package details are covered in
|
||||
[the Xbox UWP build notes](ports/uwp/BUILD.md).
|
||||
|
||||
## Handhelds
|
||||
|
||||
A PortMaster-style port for the **Anbernic RG34XXSP** on Stock OS 64-bit MOD
|
||||
@@ -202,6 +298,21 @@ ships with every release as `gen1recomp-*-rg34xxsp-stockos64-mod.zip`.
|
||||
Install steps, controls, and troubleshooting live in
|
||||
[docs/anbernic-rg34xxsp.md](docs/anbernic-rg34xxsp.md).
|
||||
|
||||
## Nintendo Switch
|
||||
|
||||
Releases ship an SD-ready `gen1recomp-*-switch.zip`. Runtime target is pinned
|
||||
[love-nx](https://github.com/retronx-team/love-nx) `11.5-nx1`. Requires a
|
||||
console that can run Switch homebrew.
|
||||
|
||||
- Players: [docs/switch-install.md](docs/switch-install.md). Download the
|
||||
zip, extract at the microSD root (install or update), title-override
|
||||
launch, import your own legal ROM, Joy-Con controls and shortcuts.
|
||||
- Builders: [docs/switch-build.md](docs/switch-build.md). `--fetch` /
|
||||
`--loose` / `--fused`, toolchain, Docker fallback, and CI vs release
|
||||
(path-gated ubuntu selftest, fused PR artifact on the main repo, release
|
||||
hard-fail).
|
||||
- File transfer (MTP / SD / FTP): [docs/switch-transfer.md](docs/switch-transfer.md).
|
||||
|
||||
## Modding
|
||||
|
||||
The game ships a native mod platform: content registries, events and hooks,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Plain Pixel font
|
||||
|
||||
"Plain Pixel Font" by Douglas Vautour (Burpy Fresh) is licensed under
|
||||
CC-BY 4.0: https://burpyfresh.itch.io
|
||||
|
||||
Version 0.009 (CJK character additions), unmodified. Characters for most
|
||||
languages have a 5x11 base but can extend vertically; double-width
|
||||
characters such as Hiragana and Katakana are 11x11.
|
||||
|
||||
Bundled so a translation mod can opt into TTF text rendering
|
||||
(`mod.content.font:register("ttf", {})`; see the Translation support
|
||||
section of docs/new-features.md) instead of drawing hundreds of glyph-page
|
||||
tiles. The tile font extracted from the player's ROM stays the default.
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 18 KiB |
@@ -89,7 +89,7 @@ mkdir -p "$GAME_SRC"
|
||||
# tools/save-editor is part of that payload: the launcher's Edit button on a
|
||||
# save row opens it in-process (main.lua).
|
||||
(cd "$ROOT" && zip -q -9 -r "$WORK/game-payload.zip" \
|
||||
main.lua conf.lua src data assets tools/save-editor \
|
||||
main.lua conf.lua src libs data assets tools/save-editor \
|
||||
tools/rom_manifest.json tools/rom_manifest_blue.json \
|
||||
-x '*.DS_Store' 'data/generated/*' 'assets/generated/*')
|
||||
if unzip -Z1 "$WORK/game-payload.zip" \
|
||||
|
||||
@@ -49,7 +49,7 @@ function love.conf(t)
|
||||
t.window.minwidth = 480
|
||||
t.window.minheight = 360
|
||||
end
|
||||
t.version = "11.5"
|
||||
t.version = love._os == "iOS" and "12.0" or "11.5"
|
||||
t.window.vsync = 1
|
||||
t.modules.joystick = true
|
||||
t.modules.physics = false
|
||||
@@ -58,7 +58,17 @@ function love.conf(t)
|
||||
-- engine before conf runs (LÖVE 11.x / 11.5).
|
||||
local osName = love._os
|
||||
local mobile = osName == "Android" or osName == "iOS"
|
||||
if mobile then
|
||||
local nx = osName == "NX"
|
||||
if nx then
|
||||
-- Switch (love-nx): hint handheld 720p. SDL auto-switches portable↔dock
|
||||
-- (720p↔1080p) only when the window is resizable and not exclusive
|
||||
-- fullscreen; NxDisplay.sync also applies the size on boot and dock change.
|
||||
t.window.width = 1280
|
||||
t.window.height = 720
|
||||
t.window.fullscreen = false
|
||||
t.window.resizable = true
|
||||
t.window.highdpi = false
|
||||
elseif mobile then
|
||||
-- resizable is what unlocks orientation. SDL's Android backend, given no
|
||||
-- SDL_HINT_ORIENTATIONS (LÖVE sets none), calls setRequestedOrientation
|
||||
-- at window creation -- FULL_SENSOR when the window is resizable (rotates
|
||||
@@ -70,6 +80,10 @@ function love.conf(t)
|
||||
-- just work. FULL_SENSOR ignores the device's rotation lock, so
|
||||
-- GameActivity.setOrientationBis remaps it to FULL_USER after SDL has
|
||||
-- run: same orientations allowed, but auto-rotate being off now wins.
|
||||
-- A persisted ORIENTATION lock (#592) overrides all of this after boot:
|
||||
-- src/core/Orientation.lua sets SDL_HINT_ORIENTATIONS over the FFI and
|
||||
-- re-triggers the request, from main.lua for the launcher and from
|
||||
-- Game:applyOptions in game.
|
||||
-- iOS follows the Info.plist orientations
|
||||
-- (see mobile/ios/overlays/love-ios.plist, now portrait + landscape).
|
||||
t.window.resizable = true
|
||||
|
||||
@@ -1683,158 +1683,172 @@ return {
|
||||
{ 0, 0, 0 },
|
||||
},
|
||||
},
|
||||
-- Species -> palette NAME, taken from pokered-gbc data/pokemon/palettes.asm
|
||||
-- ELSE branch -- the one that is NOT gated on GEN_2_GRAPHICS.
|
||||
--
|
||||
-- That file has two tables. The IF GEN_2_GRAPHICS branch assigns a
|
||||
-- PER-SPECIES palette (PAL_BULBASAUR, PAL_SQUIRTLE, ...) authored for Gen 2
|
||||
-- sprite art; the ELSE branch keeps Gen 1's own assignments (GREENMON,
|
||||
-- CYANMON, ...). This port extracts Gen 1 pics from the ROM, so the Gen 1
|
||||
-- branch is the matching one. The per-species table was imported here by
|
||||
-- mistake, which pointed every mon at colours shaded for different art:
|
||||
-- Bulbasaur wore PAL_BULBASAUR's red-orange over a sprite that has no red
|
||||
-- on it, and Squirtle wore PAL_SQUIRTLE's shell brown on his head.
|
||||
--
|
||||
-- The palette VALUES below are still pokered-gbc's, so ADVANCED keeps its
|
||||
-- richer colours -- only which palette each species points at changed.
|
||||
pokemon = {
|
||||
ABRA = "ABRA",
|
||||
AERODACTYL = "AERODACTYL",
|
||||
ALAKAZAM = "ALAKAZAM",
|
||||
ARBOK = "ARBOK",
|
||||
ARCANINE = "ARCANINE",
|
||||
ARTICUNO = "ARTICUNO",
|
||||
BEEDRILL = "BEEDRILL",
|
||||
BELLSPROUT = "BELLSPROUT",
|
||||
BLASTOISE = "BLASTOISE",
|
||||
BULBASAUR = "BULBASAUR",
|
||||
BUTTERFREE = "BUTTERFREE",
|
||||
CATERPIE = "CATERPIE",
|
||||
CHANSEY = "CHANSEY",
|
||||
CHARIZARD = "CHARIZARD",
|
||||
CHARMANDER = "CHARMANDER",
|
||||
CHARMELEON = "CHARMELEON",
|
||||
CLEFABLE = "CLEFABLE",
|
||||
CLEFAIRY = "CLEFAIRY",
|
||||
CLOYSTER = "CLOYSTER",
|
||||
CUBONE = "CUBONE",
|
||||
DEWGONG = "DEWGONG",
|
||||
DIGLETT = "DIGLETT",
|
||||
DITTO = "DITTO",
|
||||
DODRIO = "DODRIO",
|
||||
DODUO = "DODUO",
|
||||
DRAGONAIR = "DRAGONAIR",
|
||||
DRAGONITE = "DRAGONITE",
|
||||
DRATINI = "DRATINI",
|
||||
DROWZEE = "DROWZEE",
|
||||
DUGTRIO = "DUGTRIO",
|
||||
EEVEE = "EEVEE",
|
||||
EKANS = "EKANS",
|
||||
ELECTABUZZ = "ELECTABUZZ",
|
||||
ELECTRODE = "ELECTRODE",
|
||||
EXEGGCUTE = "EXEGGCUTE",
|
||||
EXEGGUTOR = "EXEGGUTOR",
|
||||
FARFETCHD = "FARFETCH_D",
|
||||
FEAROW = "FEAROW",
|
||||
FLAREON = "FLAREON",
|
||||
GASTLY = "GASTLY",
|
||||
GENGAR = "GENGAR",
|
||||
GEODUDE = "GEODUDE",
|
||||
GLOOM = "GLOOM",
|
||||
GOLBAT = "GOLBAT",
|
||||
GOLDEEN = "GOLDEEN",
|
||||
GOLDUCK = "GOLDUCK",
|
||||
GOLEM = "GOLEM",
|
||||
GRAVELER = "GRAVELER",
|
||||
GRIMER = "GRIMER",
|
||||
GROWLITHE = "GROWLITHE",
|
||||
GYARADOS = "GYARADOS",
|
||||
HAUNTER = "HAUNTER",
|
||||
HITMONCHAN = "HITMONCHAN",
|
||||
HITMONLEE = "HITMONLEE",
|
||||
HORSEA = "HORSEA",
|
||||
HYPNO = "HYPNO",
|
||||
IVYSAUR = "IVYSAUR",
|
||||
JIGGLYPUFF = "JIGGLYPUFF",
|
||||
JOLTEON = "JOLTEON",
|
||||
JYNX = "JYNX",
|
||||
KABUTO = "KABUTO",
|
||||
KABUTOPS = "KABUTOPS",
|
||||
KADABRA = "KADABRA",
|
||||
KAKUNA = "KAKUNA",
|
||||
KANGASKHAN = "KANGASKHAN",
|
||||
KINGLER = "KINGLER",
|
||||
KOFFING = "KOFFING",
|
||||
KRABBY = "KRABBY",
|
||||
LAPRAS = "LAPRAS",
|
||||
LICKITUNG = "LICKITUNG",
|
||||
MACHAMP = "MACHAMP",
|
||||
MACHOKE = "MACHOKE",
|
||||
MACHOP = "MACHOP",
|
||||
MAGIKARP = "MAGIKARP",
|
||||
MAGMAR = "MAGMAR",
|
||||
MAGNEMITE = "MAGNEMITE",
|
||||
MAGNETON = "MAGNETON",
|
||||
MANKEY = "MANKEY",
|
||||
MAROWAK = "MAROWAK",
|
||||
MEOWTH = "MEOWTH",
|
||||
METAPOD = "METAPOD",
|
||||
MEW = "MEW",
|
||||
MEWTWO = "MEWTWO",
|
||||
MOLTRES = "MOLTRES",
|
||||
MR_MIME = "MR_MIME",
|
||||
MUK = "MUK",
|
||||
NIDOKING = "NIDOKING",
|
||||
NIDOQUEEN = "NIDOQUEEN",
|
||||
NIDORAN_F = "NIDORAN_F",
|
||||
NIDORAN_M = "NIDORAN_M",
|
||||
NIDORINA = "NIDORINA",
|
||||
NIDORINO = "NIDORINO",
|
||||
NINETALES = "NINETALES",
|
||||
ODDISH = "ODDISH",
|
||||
OMANYTE = "OMANYTE",
|
||||
OMASTAR = "OMASTAR",
|
||||
ONIX = "ONIX",
|
||||
PARAS = "PARAS",
|
||||
PARASECT = "PARASECT",
|
||||
PERSIAN = "PERSIAN",
|
||||
PIDGEOT = "PIDGEOT",
|
||||
PIDGEOTTO = "PIDGEOTTO",
|
||||
PIDGEY = "PIDGEY",
|
||||
PIKACHU = "PIKACHU",
|
||||
PINSIR = "PINSIR",
|
||||
POLIWAG = "POLIWAG",
|
||||
POLIWHIRL = "POLIWHIRL",
|
||||
POLIWRATH = "POLIWRATH",
|
||||
PONYTA = "PONYTA",
|
||||
PORYGON = "PORYGON",
|
||||
PRIMEAPE = "PRIMEAPE",
|
||||
PSYDUCK = "PSYDUCK",
|
||||
RAICHU = "RAICHU",
|
||||
RAPIDASH = "RAPIDASH",
|
||||
RATICATE = "RATICATE",
|
||||
RATTATA = "RATTATA",
|
||||
RHYDON = "RHYDON",
|
||||
RHYHORN = "RHYHORN",
|
||||
SANDSHREW = "SANDSHREW",
|
||||
SANDSLASH = "SANDSLASH",
|
||||
SCYTHER = "SCYTHER",
|
||||
SEADRA = "SEADRA",
|
||||
SEAKING = "SEAKING",
|
||||
SEEL = "SEEL",
|
||||
SHELLDER = "SHELLDER",
|
||||
SLOWBRO = "SLOWBRO",
|
||||
SLOWPOKE = "SLOWPOKE",
|
||||
SNORLAX = "SNORLAX",
|
||||
SPEAROW = "SPEAROW",
|
||||
SQUIRTLE = "SQUIRTLE",
|
||||
STARMIE = "STARMIE",
|
||||
STARYU = "STARYU",
|
||||
TANGELA = "TANGELA",
|
||||
TAUROS = "TAUROS",
|
||||
TENTACOOL = "TENTACOOL",
|
||||
TENTACRUEL = "TENTACRUEL",
|
||||
VAPOREON = "VAPOREON",
|
||||
VENOMOTH = "VENOMOTH",
|
||||
VENONAT = "VENONAT",
|
||||
VENUSAUR = "VENUSAUR",
|
||||
VICTREEBEL = "VICTREEBEL",
|
||||
VILEPLUME = "VILEPLUME",
|
||||
VOLTORB = "VOLTORB",
|
||||
VULPIX = "VULPIX",
|
||||
WARTORTLE = "WARTORTLE",
|
||||
WEEDLE = "WEEDLE",
|
||||
WEEPINBELL = "WEEPINBELL",
|
||||
WEEZING = "WEEZING",
|
||||
WIGGLYTUFF = "WIGGLYTUFF",
|
||||
ZAPDOS = "ZAPDOS",
|
||||
ZUBAT = "ZUBAT",
|
||||
ABRA = "YELLOWMON",
|
||||
AERODACTYL = "GRAYMON",
|
||||
ALAKAZAM = "YELLOWMON",
|
||||
ARBOK = "PURPLEMON",
|
||||
ARCANINE = "REDMON",
|
||||
ARTICUNO = "BLUEMON",
|
||||
BEEDRILL = "YELLOWMON",
|
||||
BELLSPROUT = "GREENMON",
|
||||
BLASTOISE = "CYANMON",
|
||||
BULBASAUR = "GREENMON",
|
||||
BUTTERFREE = "CYANMON",
|
||||
CATERPIE = "GREENMON",
|
||||
CHANSEY = "PINKMON",
|
||||
CHARIZARD = "REDMON",
|
||||
CHARMANDER = "REDMON",
|
||||
CHARMELEON = "REDMON",
|
||||
CLEFABLE = "PINKMON",
|
||||
CLEFAIRY = "PINKMON",
|
||||
CLOYSTER = "GRAYMON",
|
||||
CUBONE = "GRAYMON",
|
||||
DEWGONG = "BLUEMON",
|
||||
DIGLETT = "BROWNMON",
|
||||
DITTO = "GRAYMON",
|
||||
DODRIO = "BROWNMON",
|
||||
DODUO = "BROWNMON",
|
||||
DRAGONAIR = "BLUEMON",
|
||||
DRAGONITE = "BROWNMON",
|
||||
DRATINI = "GRAYMON",
|
||||
DROWZEE = "YELLOWMON",
|
||||
DUGTRIO = "BROWNMON",
|
||||
EEVEE = "GRAYMON",
|
||||
EKANS = "PURPLEMON",
|
||||
ELECTABUZZ = "YELLOWMON",
|
||||
ELECTRODE = "YELLOWMON",
|
||||
EXEGGCUTE = "PINKMON",
|
||||
EXEGGUTOR = "GREENMON",
|
||||
FARFETCHD = "BROWNMON",
|
||||
FEAROW = "BROWNMON",
|
||||
FLAREON = "REDMON",
|
||||
GASTLY = "PURPLEMON",
|
||||
GENGAR = "PURPLEMON",
|
||||
GEODUDE = "GRAYMON",
|
||||
GLOOM = "REDMON",
|
||||
GOLBAT = "BLUEMON",
|
||||
GOLDEEN = "REDMON",
|
||||
GOLDUCK = "CYANMON",
|
||||
GOLEM = "GRAYMON",
|
||||
GRAVELER = "GRAYMON",
|
||||
GRIMER = "PURPLEMON",
|
||||
GROWLITHE = "BROWNMON",
|
||||
GYARADOS = "BLUEMON",
|
||||
HAUNTER = "PURPLEMON",
|
||||
HITMONCHAN = "BROWNMON",
|
||||
HITMONLEE = "BROWNMON",
|
||||
HORSEA = "CYANMON",
|
||||
HYPNO = "YELLOWMON",
|
||||
IVYSAUR = "GREENMON",
|
||||
JIGGLYPUFF = "PINKMON",
|
||||
JOLTEON = "YELLOWMON",
|
||||
JYNX = "MEWMON",
|
||||
KABUTO = "BROWNMON",
|
||||
KABUTOPS = "BROWNMON",
|
||||
KADABRA = "YELLOWMON",
|
||||
KAKUNA = "YELLOWMON",
|
||||
KANGASKHAN = "BROWNMON",
|
||||
KINGLER = "REDMON",
|
||||
KOFFING = "PURPLEMON",
|
||||
KRABBY = "REDMON",
|
||||
LAPRAS = "CYANMON",
|
||||
LICKITUNG = "PINKMON",
|
||||
MACHAMP = "GRAYMON",
|
||||
MACHOKE = "GRAYMON",
|
||||
MACHOP = "GRAYMON",
|
||||
MAGIKARP = "REDMON",
|
||||
MAGMAR = "REDMON",
|
||||
MAGNEMITE = "GRAYMON",
|
||||
MAGNETON = "GRAYMON",
|
||||
MANKEY = "BROWNMON",
|
||||
MAROWAK = "GRAYMON",
|
||||
MEOWTH = "YELLOWMON",
|
||||
METAPOD = "GREENMON",
|
||||
MEW = "MEWMON",
|
||||
MEWTWO = "MEWMON",
|
||||
MOLTRES = "REDMON",
|
||||
MR_MIME = "PINKMON",
|
||||
MUK = "PURPLEMON",
|
||||
NIDOKING = "PURPLEMON",
|
||||
NIDOQUEEN = "BLUEMON",
|
||||
NIDORAN_F = "BLUEMON",
|
||||
NIDORAN_M = "PURPLEMON",
|
||||
NIDORINA = "BLUEMON",
|
||||
NIDORINO = "PURPLEMON",
|
||||
NINETALES = "YELLOWMON",
|
||||
ODDISH = "GREENMON",
|
||||
OMANYTE = "BLUEMON",
|
||||
OMASTAR = "BLUEMON",
|
||||
ONIX = "GRAYMON",
|
||||
PARAS = "REDMON",
|
||||
PARASECT = "REDMON",
|
||||
PERSIAN = "YELLOWMON",
|
||||
PIDGEOT = "BROWNMON",
|
||||
PIDGEOTTO = "BROWNMON",
|
||||
PIDGEY = "BROWNMON",
|
||||
PIKACHU = "YELLOWMON",
|
||||
PINSIR = "BROWNMON",
|
||||
POLIWAG = "BLUEMON",
|
||||
POLIWHIRL = "BLUEMON",
|
||||
POLIWRATH = "BLUEMON",
|
||||
PONYTA = "REDMON",
|
||||
PORYGON = "GRAYMON",
|
||||
PRIMEAPE = "BROWNMON",
|
||||
PSYDUCK = "YELLOWMON",
|
||||
RAICHU = "YELLOWMON",
|
||||
RAPIDASH = "REDMON",
|
||||
RATICATE = "GRAYMON",
|
||||
RATTATA = "GRAYMON",
|
||||
RHYDON = "GRAYMON",
|
||||
RHYHORN = "GRAYMON",
|
||||
SANDSHREW = "BROWNMON",
|
||||
SANDSLASH = "BROWNMON",
|
||||
SCYTHER = "GREENMON",
|
||||
SEADRA = "CYANMON",
|
||||
SEAKING = "REDMON",
|
||||
SEEL = "BLUEMON",
|
||||
SHELLDER = "GRAYMON",
|
||||
SLOWBRO = "PINKMON",
|
||||
SLOWPOKE = "PINKMON",
|
||||
SNORLAX = "PINKMON",
|
||||
SPEAROW = "BROWNMON",
|
||||
SQUIRTLE = "CYANMON",
|
||||
STARMIE = "GRAYMON",
|
||||
STARYU = "REDMON",
|
||||
TANGELA = "BLUEMON",
|
||||
TAUROS = "GRAYMON",
|
||||
TENTACOOL = "CYANMON",
|
||||
TENTACRUEL = "CYANMON",
|
||||
VAPOREON = "CYANMON",
|
||||
VENOMOTH = "PURPLEMON",
|
||||
VENONAT = "PURPLEMON",
|
||||
VENUSAUR = "GREENMON",
|
||||
VICTREEBEL = "GREENMON",
|
||||
VILEPLUME = "REDMON",
|
||||
VOLTORB = "YELLOWMON",
|
||||
VULPIX = "REDMON",
|
||||
WARTORTLE = "CYANMON",
|
||||
WEEDLE = "YELLOWMON",
|
||||
WEEPINBELL = "GREENMON",
|
||||
WEEZING = "PURPLEMON",
|
||||
WIGGLYTUFF = "PINKMON",
|
||||
ZAPDOS = "YELLOWMON",
|
||||
ZUBAT = "BLUEMON",
|
||||
},
|
||||
source = "pokered-gbc data/super_palettes.asm + data/mon_palettes.asm + color/**",
|
||||
world = {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
-- BikeShop (BIKE_SHOP) flavor dialogue
|
||||
-- Source: pokered/scripts/BikeShop.asm, pokered/text/BikeShop.asm
|
||||
--
|
||||
-- TEXT_BIKESHOP_CLERK is skipped: it drives the actual voucher-for-bicycle
|
||||
-- exchange (YesNoChoice purchase menu, GiveItem, RemoveItemByID, SetEvent
|
||||
-- EVENT_GOT_BICYCLE). That is a significant standalone feature outside the
|
||||
-- scope of these two flavor NPCs and is left unported here.
|
||||
-- TEXT_BIKESHOP_CLERK lives in data/scripts/story2.lua (M.BIKE_SHOP): the
|
||||
-- voucher exchange and the BICYCLE/CANCEL price window need more than
|
||||
-- command rows (#568).
|
||||
|
||||
return {
|
||||
BIKE_SHOP = {
|
||||
@@ -20,12 +19,15 @@ return {
|
||||
-- CheckEvent EVENT_GOT_BICYCLE ; jr nz, .gotBike
|
||||
-- before the player owns a bike -> TheseBikesAreExpensiveText
|
||||
-- after the player owns a bike -> CoolBikeText
|
||||
-- The check reads the bag, not the event: the port hands out the
|
||||
-- BICYCLE itself (a key item, so it cannot be tossed), and that also
|
||||
-- reads right on saves written before the clerk set the event (#567).
|
||||
TEXT_BIKESHOP_YOUNGSTER = {
|
||||
{ "face_player" }, -- 1
|
||||
{ "check_flag", "EVENT_GOT_BICYCLE" }, -- 2
|
||||
{ "jump_if_true", 5 }, -- 3
|
||||
{ "check_item", "BICYCLE" }, -- 2
|
||||
{ "jump_if_true", 6 }, -- 3
|
||||
{ "show_text", "_BikeShopYoungsterTheseBikesAreExpensiveText" }, -- 4
|
||||
{ "jump", 6 }, -- 5
|
||||
{ "jump", "end" }, -- 5
|
||||
{ "show_text", "_BikeShopYoungsterCoolBikeText" }, -- 6
|
||||
},
|
||||
},
|
||||
|
||||
@@ -35,14 +35,25 @@ local function middleAgedMan(game, ow, npc, done)
|
||||
-- .loop: print WhichBadgeText, then show the badge list menu again
|
||||
push(game, t._CeruleanBadgeHouseMiddleAgedManWhichBadgeText, function()
|
||||
local ListMenu = require("src.ui.ListMenu")
|
||||
local Strings = require("src.core.Strings")
|
||||
local items = {}
|
||||
for _, id in ipairs(BADGE_ORDER) do
|
||||
local idef = game.data.items[id]
|
||||
items[#items + 1] = { label = idef and idef.name or id, value = id }
|
||||
end
|
||||
-- PrintListMenuEntries prints ListMenuCancelText once it hits the
|
||||
-- list's $FF terminator (home/list_menu.asm), so every
|
||||
-- DisplayListMenuID list ends in an on-screen CANCEL row; picking it
|
||||
-- takes DisplayListMenuIDLoop's ExitListMenu path, the same .done
|
||||
-- exit as B (#569)
|
||||
items[#items + 1] = { label = Strings("CANCEL") }
|
||||
local menu = ListMenu.new(game, "", items, {
|
||||
onChoose = function(item)
|
||||
game.stack:pop()
|
||||
if not item.value then
|
||||
push(game, t._CeruleanBadgeHouseMiddleAgedManVisitAnyTimeText, done)
|
||||
return
|
||||
end
|
||||
push(game, t[BADGE_TEXT[item.value]], loop)
|
||||
end,
|
||||
onCancel = function()
|
||||
|
||||
@@ -14,9 +14,9 @@ return {
|
||||
-- else -> .WhatsLostIsLostText (player has TM_DIG)
|
||||
TEXT_CERULEANTRASHEDHOUSE_FISHING_GURU = {
|
||||
{ "check_item", "TM_DIG" },
|
||||
{ "jump_if_true", 4 },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "show_text", "_CeruleanTrashedHouseFishingGuruTheyStoleATMText" },
|
||||
{ "jump", 5 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_CeruleanTrashedHouseFishingGuruWhatsLostIsLostText" },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
-- Fuchsia City (pokered/scripts/FuchsiaCity.asm)
|
||||
--
|
||||
-- The exhibit signs are text_asm bodies: PrintText of a single text_far,
|
||||
-- then DisplayPokedex for the exhibited species. The preview marks the
|
||||
-- species seen but not owned, same as the S.S. Anne passenger's Snorlax.
|
||||
--
|
||||
-- The fossil sign branches on the Mt. Moon fossil events. The exhibit
|
||||
-- holds the fossil the player did NOT take: taking the Dome Fossil puts
|
||||
-- Omanyte on display, taking the Helix Fossil puts Kabuto. With neither
|
||||
-- event set the sign only prints its undetermined line and no dex entry
|
||||
-- opens.
|
||||
--
|
||||
-- The other text pointers (city sign, Safari Game signs, mart/center/gym
|
||||
-- and warden signs, the four NPCs, the exhibited-mon FuchsiaCityPokemonText
|
||||
-- rows) are plain text_far wrappers that resolve through Data:resolveText,
|
||||
-- so they are not ported here.
|
||||
return {
|
||||
FUCHSIA_CITY = {
|
||||
talk = {
|
||||
-- FuchsiaCityChanseySignText: PrintText(_FuchsiaCityChanseySignText),
|
||||
-- then DisplayPokedex CHANSEY.
|
||||
TEXT_FUCHSIACITY_CHANSEY_SIGN = {
|
||||
{ "show_text", "_FuchsiaCityChanseySignText" },
|
||||
{ "mark_seen", "CHANSEY" },
|
||||
{ "push_screen", "DexEntryMenu", "CHANSEY" },
|
||||
},
|
||||
|
||||
-- FuchsiaCityVoltorbSignText: PrintText(_FuchsiaCityVoltorbSignText),
|
||||
-- then DisplayPokedex VOLTORB.
|
||||
TEXT_FUCHSIACITY_VOLTORB_SIGN = {
|
||||
{ "show_text", "_FuchsiaCityVoltorbSignText" },
|
||||
{ "mark_seen", "VOLTORB" },
|
||||
{ "push_screen", "DexEntryMenu", "VOLTORB" },
|
||||
},
|
||||
|
||||
-- FuchsiaCityKangaskhanSignText: PrintText(_FuchsiaCityKangaskhanSignText),
|
||||
-- then DisplayPokedex KANGASKHAN.
|
||||
TEXT_FUCHSIACITY_KANGASKHAN_SIGN = {
|
||||
{ "show_text", "_FuchsiaCityKangaskhanSignText" },
|
||||
{ "mark_seen", "KANGASKHAN" },
|
||||
{ "push_screen", "DexEntryMenu", "KANGASKHAN" },
|
||||
},
|
||||
|
||||
-- FuchsiaCitySlowpokeSignText: PrintText(_FuchsiaCitySlowpokeSignText),
|
||||
-- then DisplayPokedex SLOWPOKE.
|
||||
TEXT_FUCHSIACITY_SLOWPOKE_SIGN = {
|
||||
{ "show_text", "_FuchsiaCitySlowpokeSignText" },
|
||||
{ "mark_seen", "SLOWPOKE" },
|
||||
{ "push_screen", "DexEntryMenu", "SLOWPOKE" },
|
||||
},
|
||||
|
||||
-- FuchsiaCityLaprasSignText: PrintText(_FuchsiaCityLaprasSignText),
|
||||
-- then DisplayPokedex LAPRAS.
|
||||
TEXT_FUCHSIACITY_LAPRAS_SIGN = {
|
||||
{ "show_text", "_FuchsiaCityLaprasSignText" },
|
||||
{ "mark_seen", "LAPRAS" },
|
||||
{ "push_screen", "DexEntryMenu", "LAPRAS" },
|
||||
},
|
||||
|
||||
-- FuchsiaCityFossilSignText: CheckEvent EVENT_GOT_DOME_FOSSIL /
|
||||
-- CheckEventReuseA EVENT_GOT_HELIX_FOSSIL pick the text and the
|
||||
-- displayed entry; with neither set only the undetermined line prints.
|
||||
TEXT_FUCHSIACITY_FOSSIL_SIGN = {
|
||||
{ "check_flag", "EVENT_GOT_DOME_FOSSIL" }, -- 1
|
||||
{ "jump_if_true", 7 }, -- 2
|
||||
{ "check_flag", "EVENT_GOT_HELIX_FOSSIL" }, -- 3
|
||||
{ "jump_if_true", 11 }, -- 4
|
||||
{ "show_text", "_FuchsiaCityFossilSignUndeterminedText" }, -- 5
|
||||
{ "jump", "end" }, -- 6
|
||||
{ "show_text", "_FuchsiaCityFossilSignOmanyteText" }, -- 7
|
||||
{ "mark_seen", "OMANYTE" }, -- 8
|
||||
{ "push_screen", "DexEntryMenu", "OMANYTE" }, -- 9
|
||||
{ "jump", "end" }, -- 10
|
||||
{ "show_text", "_FuchsiaCityFossilSignKabutoText" }, -- 11
|
||||
{ "mark_seen", "KABUTO" }, -- 12
|
||||
{ "push_screen", "DexEntryMenu", "KABUTO" }, -- 13
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -85,6 +85,47 @@ return {
|
||||
alreadyGotLabel = "_GameCornerGentlemanCloselyWatchTheReelsText",
|
||||
alreadyGotFallback = "The trick is to\nwatch the reels\vclosely!",
|
||||
}),
|
||||
|
||||
-- Yellow keeps all three giveaways with the same events and amounts
|
||||
-- but renames the objects and their text labels: FISHING_GURU ->
|
||||
-- FISHING_GURU1, CLERK2 -> MIDDLE_AGED_MAN2, GENTLEMAN ->
|
||||
-- FISHING_GURU2 (pokeyellow/scripts/GameCorner.asm). The Red/Blue
|
||||
-- keys above never match there, so Yellow needs its own three (#552).
|
||||
TEXT_GAMECORNER_FISHING_GURU1 = coinGiver({
|
||||
event = "EVENT_GOT_10_COINS",
|
||||
amount = 10,
|
||||
askLabel = "_GameCornerFishingGuru1WantToPlayText",
|
||||
askFallback = "Kid, do you want\nto play?",
|
||||
receivedLabel = "_GameCornerFishingGuru1Received10CoinsText",
|
||||
coinCaseFullLabel = "_GameCornerFishingGuru1DontNeedMyCoinsText",
|
||||
coinCaseFullFallback = "You don't need my\ncoins!",
|
||||
alreadyGotLabel = "_GameCornerFishingGuru1WinsComeAndGoText",
|
||||
alreadyGotFallback = "Wins seem to come\nand go.",
|
||||
}),
|
||||
|
||||
TEXT_GAMECORNER_MIDDLE_AGED_MAN2 = coinGiver({
|
||||
event = "EVENT_GOT_20_COINS_2",
|
||||
amount = 20,
|
||||
askLabel = "_GameCornerMiddleAgedMan2WantSomeCoinsText",
|
||||
askFallback = "What's up? Want\nsome coins?",
|
||||
receivedLabel = "_GameCornerMiddleAgedMan2Received20CoinsText",
|
||||
coinCaseFullLabel = "_GameCornerMiddleAgedMan2YouHaveLotsOfCoinsText",
|
||||
coinCaseFullFallback = "You have lots of\ncoins!",
|
||||
alreadyGotLabel = "_GameCornerMiddleAgedMan2INeedMoreCoinsText",
|
||||
alreadyGotFallback = "Darn! I need more\ncoins for the\vPOKéMON I want!",
|
||||
}),
|
||||
|
||||
TEXT_GAMECORNER_FISHING_GURU2 = coinGiver({
|
||||
event = "EVENT_GOT_20_COINS",
|
||||
amount = 20,
|
||||
askLabel = "_GameCornerFishingGuru2ThrowingMeOffText",
|
||||
askFallback = "Hey, what? You're\nthrowing me off!\vHere are some\vcoins, shoo!",
|
||||
receivedLabel = "_GameCornerFishingGuru2Received20CoinsText",
|
||||
coinCaseFullLabel = "_GameCornerFishingGuru2YouGotYourOwnCoinsText",
|
||||
coinCaseFullFallback = "You've got your\nown coins!",
|
||||
alreadyGotLabel = "_GameCornerFishingGuru2CloselyWatchTheReelsText",
|
||||
alreadyGotFallback = "The trick is to\nwatch the reels\vclosely!",
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ return {
|
||||
TEXT_LAVENDERMART_COOLTRAINER_M = {
|
||||
{ "face_player" },
|
||||
{ "check_flag", "EVENT_RESCUED_MR_FUJI" },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "jump_if_true", 6 },
|
||||
{ "show_text", "_LavenderMartCooltrainerMReviveText" },
|
||||
{ "jump", 6 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_LavenderMartCooltrainerMNuggetText" },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -13,9 +13,9 @@ M.MR_FUJIS_HOUSE = {
|
||||
TEXT_MRFUJISHOUSE_SUPER_NERD = {
|
||||
{ "face_player" }, -- 1
|
||||
{ "check_flag", "EVENT_RESCUED_MR_FUJI" }, -- 2
|
||||
{ "jump_if_true", 5 }, -- 3
|
||||
{ "jump_if_true", 6 }, -- 3
|
||||
{ "show_text", "_MrFujisHouseSuperNerdMrFujiIsntHereText" }, -- 4
|
||||
{ "jump", 6 }, -- 5
|
||||
{ "jump", "end" }, -- 5
|
||||
{ "show_text", "_MrFujisHouseSuperNerdMrFujiHadBeenPrayingText" }, -- 6
|
||||
},
|
||||
|
||||
@@ -25,9 +25,9 @@ M.MR_FUJIS_HOUSE = {
|
||||
TEXT_MRFUJISHOUSE_LITTLE_GIRL = {
|
||||
{ "face_player" }, -- 1
|
||||
{ "check_flag", "EVENT_RESCUED_MR_FUJI" }, -- 2
|
||||
{ "jump_if_true", 5 }, -- 3
|
||||
{ "jump_if_true", 6 }, -- 3
|
||||
{ "show_text", "_MrFujisHouseLittleGirlThisIsMrFujisHouseText" }, -- 4
|
||||
{ "jump", 6 }, -- 5
|
||||
{ "jump", "end" }, -- 5
|
||||
{ "show_text", "_MrFujisHouseLittleGirlPokemonAreNiceToHugText" }, -- 6
|
||||
},
|
||||
|
||||
|
||||
@@ -16,9 +16,13 @@ local function push(game, s, done)
|
||||
game.stack:push(TextBox.new(game, s, done))
|
||||
end
|
||||
|
||||
-- PrintText on a text_end string returns with the box still drawn and
|
||||
-- YesNoChoice then draws the menu above it (InitYesNoTextBoxParameters,
|
||||
-- engine/menus/text_box.asm); no A press clears the question first. Ride
|
||||
-- TextBox's opts.choice, the same as Commands.ask (#854).
|
||||
local function ask(game, s, cb)
|
||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
||||
push(game, s, function() game.stack:push(ChoiceBox.new(game, cb)) end)
|
||||
local TextBox = require("src.render.TextBox")
|
||||
game.stack:push(TextBox.new(game, s, nil, { choice = cb }))
|
||||
end
|
||||
|
||||
M.PEWTER_CITY = {
|
||||
|
||||
@@ -12,9 +12,9 @@ return {
|
||||
TEXT_ROUTE16GATE1F_GUARD = {
|
||||
{ "face_player" },
|
||||
{ "check_item", "BICYCLE" },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "jump_if_true", 6 },
|
||||
{ "show_text", "_Route16Gate1FGuardNoPedestriansAllowedText" },
|
||||
{ "jump", 6 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_Route16Gate1FGuardCyclingRoadExplanationText" },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -14,9 +14,9 @@ return {
|
||||
TEXT_ROUTE18GATE1F_GUARD = {
|
||||
{ "face_player" },
|
||||
{ "check_item", "BICYCLE" },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "jump_if_true", 6 },
|
||||
{ "show_text", "_Route18Gate1FGuardYouNeedABicycleText" },
|
||||
{ "jump", 6 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_Route18Gate1FGuardCyclingRoadUphillText" },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -12,9 +12,9 @@ return {
|
||||
TEXT_SILPHCO10F_SILPH_WORKER_F = {
|
||||
{ "face_player" },
|
||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "jump_if_true", 6 },
|
||||
{ "show_text", "_SilphCo10FSilphWorkerFImScaredText" },
|
||||
{ "jump", 6 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_SilphCo10FSilphWorkerFQuietAboutMyCryingText" },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -10,9 +10,9 @@ return {
|
||||
-- not set: _SilphCo3FSilphWorkerMWhatShouldIDoText
|
||||
TEXT_SILPHCO3F_SILPH_WORKER_M = {
|
||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||
{ "jump_if_true", 4 },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "show_text", "_SilphCo3FSilphWorkerMWhatShouldIDoText" },
|
||||
{ "jump", 5 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_SilphCo3FSilphWorkerMYouSavedUsText" },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -7,9 +7,9 @@ return {
|
||||
TEXT_SILPHCO4F_SILPH_WORKER_M = {
|
||||
{"face_player"},
|
||||
{"check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI"},
|
||||
{"jump_if_true", 5},
|
||||
{"jump_if_true", 6},
|
||||
{"show_text", "_SilphCo4FSilphWorkerMImHidingText"},
|
||||
{"jump", 6},
|
||||
{"jump", "end"},
|
||||
{"show_text", "_SilphCo4FSilphWorkerMTeamRocketIsGoneText"},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -7,9 +7,9 @@ return {
|
||||
TEXT_SILPHCO5F_SILPH_WORKER_M = {
|
||||
{"face_player"},
|
||||
{"check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI"},
|
||||
{"jump_if_true", 5},
|
||||
{"jump_if_true", 6},
|
||||
{"show_text", "_SilphCo5FSilphWorkerMThatsYouRightText"},
|
||||
{"jump", 6},
|
||||
{"jump", "end"},
|
||||
{"show_text", "_SilphCo5FSilphWorkerMYoureOurHeroText"},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -11,9 +11,9 @@ return {
|
||||
TEXT_SILPHCO6F_SILPH_WORKER_M1 = {
|
||||
{ "face_player" },
|
||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "jump_if_true", 6 },
|
||||
{ "show_text", "_SilphCo6FSilphWorkerM1TookOverTheBuildingText" },
|
||||
{ "jump", 6 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_SilphCo6FSilphWorkerM1BackToWorkText" },
|
||||
},
|
||||
|
||||
@@ -21,9 +21,9 @@ return {
|
||||
TEXT_SILPHCO6F_SILPH_WORKER_M2 = {
|
||||
{ "face_player" },
|
||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "jump_if_true", 6 },
|
||||
{ "show_text", "_SilphCo6FSilphWorkerMHelpMePleaseText" },
|
||||
{ "jump", 6 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_SilphCo6FSilphWorkerMWeGotEngagedText" },
|
||||
},
|
||||
|
||||
@@ -31,9 +31,9 @@ return {
|
||||
TEXT_SILPHCO6F_SILPH_WORKER_F1 = {
|
||||
{ "face_player" },
|
||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "jump_if_true", 6 },
|
||||
{ "show_text", "_SilphCo6FSilphWorkerF1SuchACowardText" },
|
||||
{ "jump", 6 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_SilphCo6FSilphWorkerF1HaveToMarryHimText" },
|
||||
},
|
||||
|
||||
@@ -41,9 +41,9 @@ return {
|
||||
TEXT_SILPHCO6F_SILPH_WORKER_F2 = {
|
||||
{ "face_player" },
|
||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "jump_if_true", 6 },
|
||||
{ "show_text", "_SilphCo6FSilphWorkerF2TeamRocketConquerWorldText" },
|
||||
{ "jump", 6 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_SilphCo6FSilphWorkerF2TeamRocketRanText" },
|
||||
},
|
||||
|
||||
@@ -51,9 +51,9 @@ return {
|
||||
TEXT_SILPHCO6F_SILPH_WORKER_M3 = {
|
||||
{ "face_player" },
|
||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "jump_if_true", 6 },
|
||||
{ "show_text", "_SilphCo6FSilphWorkerM3TargetedSilphText" },
|
||||
{ "jump", 6 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_SilphCo6FSilphWorkerM3WorkForSilphText" },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -10,9 +10,9 @@ return {
|
||||
-- set: _SilphCo7FSilphWorkerM2CancelledMasterBallText
|
||||
TEXT_SILPHCO7F_SILPH_WORKER_M2 = {
|
||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||
{ "jump_if_true", 4 },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "show_text", "_SilphCo7FSilphWorkerM2AfterTheMasterBallText" },
|
||||
{ "jump", 5 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_SilphCo7FSilphWorkerM2CancelledMasterBallText" },
|
||||
},
|
||||
|
||||
@@ -22,9 +22,9 @@ return {
|
||||
-- set: _SilphCo7FSilphWorkerM3YouChasedOffTeamRocketText
|
||||
TEXT_SILPHCO7F_SILPH_WORKER_M3 = {
|
||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||
{ "jump_if_true", 4 },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "show_text", "_SilphCo7FSilphWorkerM3ItWouldBeBadText" },
|
||||
{ "jump", 5 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_SilphCo7FSilphWorkerM3YouChasedOffTeamRocketText" },
|
||||
},
|
||||
|
||||
@@ -34,9 +34,9 @@ return {
|
||||
-- set: _SilphCo7FSilphWorkerM4SafeAtLastText
|
||||
TEXT_SILPHCO7F_SILPH_WORKER_M4 = {
|
||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||
{ "jump_if_true", 4 },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "show_text", "_SilphCo7FSilphWorkerM4ItsReallyDangerousHereText" },
|
||||
{ "jump", 5 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_SilphCo7FSilphWorkerM4SafeAtLastText" },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -10,9 +10,9 @@ return {
|
||||
-- set: _SilphCo8FSilphWorkerMThanksForSavingUsText
|
||||
TEXT_SILPHCO8F_SILPH_WORKER_M = {
|
||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||
{ "jump_if_true", 4 },
|
||||
{ "jump_if_true", 5 },
|
||||
{ "show_text", "_SilphCo8FSilphWorkerMSilphIsFinishedText" },
|
||||
{ "jump", 5 },
|
||||
{ "jump", "end" },
|
||||
{ "show_text", "_SilphCo8FSilphWorkerMThanksForSavingUsText" },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -23,9 +23,13 @@ local function push(game, s, done)
|
||||
game.stack:push(TextBox.new(game, s, done))
|
||||
end
|
||||
|
||||
-- PrintText on a text_end string returns with the box still drawn and
|
||||
-- YesNoChoice then draws the menu above it (InitYesNoTextBoxParameters,
|
||||
-- engine/menus/text_box.asm); no A press clears the question first. Ride
|
||||
-- TextBox's opts.choice, the same as Commands.ask (#854).
|
||||
local function ask(game, s, cb)
|
||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
||||
push(game, s, function() game.stack:push(ChoiceBox.new(game, cb)) end)
|
||||
local TextBox = require("src.render.TextBox")
|
||||
game.stack:push(TextBox.new(game, s, nil, { choice = cb }))
|
||||
end
|
||||
|
||||
M.VIRIDIAN_CITY = {
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
-- hidden_text_predef spends the facing byte on the tx_pre id, so neither
|
||||
-- tile gates on facing.
|
||||
|
||||
local Menu = require("src.ui.Menu")
|
||||
local Font = require("src.render.Font")
|
||||
local Theme = require("src.ui.Theme")
|
||||
local TextBox = require("src.render.TextBox")
|
||||
|
||||
-- ViridianSchoolBlackboard (engine/events/hidden_events/school_blackboard.asm):
|
||||
@@ -25,33 +26,112 @@ local STATUS_LABELS = {
|
||||
{ " FRZ", "_ViridianBlackboardFrozenText" },
|
||||
}
|
||||
|
||||
-- The headings list is a two-column menu, which src/ui/Menu.lua does not do
|
||||
-- (it stacks one column), so the layout lives here (#591). .blackboardLoop:
|
||||
-- TextBoxBorder at hlcoord 0, 0 with `lb bc, 6, 10` is the 12x8 box,
|
||||
-- StatusAilmentText1 (" SLP"/" PSN"/" PAR") is placed at hlcoord 1, 2 and
|
||||
-- StatusAilmentText2 (" BRN"/" FRZ"/" QUIT") at hlcoord 6, 2. LEFT/RIGHT
|
||||
-- move wTopMenuItemX between those two columns and swap wMenuItemOffset
|
||||
-- between 0 and 3 while leaving wCurrentMenuItem (the row) alone; UP/DOWN
|
||||
-- are not in wMenuWatchedKeys, so they only slide the cursor and loop.
|
||||
local BOARD_LABELS = {}
|
||||
for i, row in ipairs(STATUS_LABELS) do BOARD_LABELS[i] = row[1] end
|
||||
BOARD_LABELS[#BOARD_LABELS + 1] = " QUIT"
|
||||
local BOARD_COL_X = { 1, 6 }
|
||||
local BOARD_ROW_Y = 2
|
||||
local BOARD_ROWS = 3
|
||||
|
||||
local StatusBoard = {}
|
||||
StatusBoard.__index = StatusBoard
|
||||
|
||||
function StatusBoard.new(game, onPick, onQuit)
|
||||
return setmetatable({ game = game, col = 1, row = 1, labels = BOARD_LABELS,
|
||||
onPick = onPick, onQuit = onQuit }, StatusBoard)
|
||||
end
|
||||
|
||||
-- flat index = pokered's wMenuItemOffset (0 or 3) + wCurrentMenuItem (0..2),
|
||||
-- so 1..5 are the statuses in ViridianBlackboardStatusPointers order and 6
|
||||
-- is QUIT
|
||||
function StatusBoard:selection()
|
||||
return (self.col - 1) * BOARD_ROWS + self.row
|
||||
end
|
||||
|
||||
function StatusBoard:update()
|
||||
local input = self.game.input
|
||||
if input:wasPressed("up") then
|
||||
-- wMenuWrappingEnabled is never set here, so both ends are hard stops
|
||||
if self.row > 1 then self.row = self.row - 1 end
|
||||
elseif input:wasPressed("down") then
|
||||
if self.row < BOARD_ROWS then self.row = self.row + 1 end
|
||||
elseif input:wasPressed("left") then
|
||||
self.col = 1
|
||||
elseif input:wasPressed("right") then
|
||||
self.col = 2
|
||||
elseif input:wasPressed("a") or input:wasPressed("b") then
|
||||
-- HandleMenuInput_ (home/window.asm) beeps for the PAD_A | PAD_B branch,
|
||||
-- and B and QUIT share .exitBlackboard
|
||||
require("src.core.Sound").play(self.game.data, "Press_AB")
|
||||
local sel = self:selection()
|
||||
if input:wasPressed("b") or sel > #STATUS_LABELS then
|
||||
self.onQuit()
|
||||
else
|
||||
self.onPick(sel)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function StatusBoard:draw()
|
||||
Font.drawBox(0, 0, 12, 8)
|
||||
love.graphics.setColor(0, 0, 0, 1)
|
||||
for i, label in ipairs(BOARD_LABELS) do
|
||||
local col = i <= BOARD_ROWS and 1 or 2
|
||||
local row = i - (col - 1) * BOARD_ROWS
|
||||
Font.draw(label, BOARD_COL_X[col] * 8, (BOARD_ROW_Y + row - 1) * 8)
|
||||
end
|
||||
-- wTopMenuItemX equals the column PlaceString started at, so the cursor
|
||||
-- covers the blank each label leads with
|
||||
Font.drawCode(Theme.cursor, BOARD_COL_X[self.col] * 8,
|
||||
(BOARD_ROW_Y + self.row - 1) * 8)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
|
||||
local function blackboard(game)
|
||||
local text = game.data.text or {}
|
||||
local items, showMenu, askHeading
|
||||
function showMenu()
|
||||
game.stack:push(Menu.new(game, items,
|
||||
{ tx = 0, ty = 0, tw = 12, th = 8, rowStep = 1 }))
|
||||
local openBoard
|
||||
-- wCurrentMenuItem / wMenuItemOffset are zeroed once, above .blackboardLoop,
|
||||
-- and nothing inside the loop clears them again: after a status blurb
|
||||
-- `jp .blackboardLoop` comes back with the cursor still on the row and
|
||||
-- column the player just picked. One StatusBoard lives for the whole
|
||||
-- reading and is re-pushed each pass, so only entering the blackboard
|
||||
-- resets to the left column / top row (#591).
|
||||
local board
|
||||
-- .blackboardLoop reprints ViridianSchoolBlackboardText2 and only then
|
||||
-- calls HandleMenuInput, so the prompt is on screen for exactly as long as
|
||||
-- the headings list is. That text ends in `done`, not `prompt`
|
||||
-- (data/text/text_2.asm:646), so PrintText returns with the box still up
|
||||
-- and never waits for a button: TextBox opts.stay holds it open under the
|
||||
-- list and these callbacks pop the pair together (#591).
|
||||
local function closeBoard()
|
||||
game.stack:pop() -- the headings list
|
||||
game.stack:pop() -- the held "Which heading" box under it
|
||||
end
|
||||
-- ViridianSchoolBlackboardText2 is reprinted on every .blackboardLoop
|
||||
-- pass, immediately before HandleMenuInput
|
||||
function askHeading()
|
||||
local function pick(i)
|
||||
closeBoard()
|
||||
game.stack:push(TextBox.new(game,
|
||||
text[STATUS_LABELS[i][2]] or STATUS_LABELS[i][1], openBoard))
|
||||
end
|
||||
function openBoard()
|
||||
game.stack:push(TextBox.new(game,
|
||||
text._ViridianSchoolBlackboardText2 or "Which heading do\nyou want to read?",
|
||||
showMenu))
|
||||
nil, { stay = { onShown = function()
|
||||
board = board or StatusBoard.new(game, pick, closeBoard)
|
||||
game.stack:push(board)
|
||||
end } }))
|
||||
end
|
||||
items = {}
|
||||
for i, row in ipairs(STATUS_LABELS) do
|
||||
local label, key = row[1], row[2]
|
||||
items[i] = { label = label, onSelect = function()
|
||||
game.stack:push(TextBox.new(game, text[key] or label, askHeading))
|
||||
end }
|
||||
end
|
||||
-- no onSelect: Menu's own pop closes the box, matching .exitBlackboard
|
||||
items[#items + 1] = { label = " QUIT" }
|
||||
game.stack:push(TextBox.new(game,
|
||||
text._ViridianSchoolBlackboardText1
|
||||
or "The blackboard\ndescribes POKéMON\vSTATUS changes\vduring battles.",
|
||||
askHeading))
|
||||
openBoard))
|
||||
end
|
||||
|
||||
-- ViridianSchoolNotebook (engine/events/hidden_events/school_notebooks.asm):
|
||||
|
||||
@@ -15,6 +15,7 @@ local files = {
|
||||
"data.scripts.flavor.cerulean_trashed_house",
|
||||
"data.scripts.flavor.copycats_house_1f",
|
||||
"data.scripts.flavor.copycats_house_2f",
|
||||
"data.scripts.flavor.fuchsia_city",
|
||||
"data.scripts.flavor.game_corner",
|
||||
"data.scripts.flavor.lavender_cubone_house",
|
||||
"data.scripts.flavor.lavender_mart",
|
||||
|
||||
@@ -18,6 +18,26 @@ local M = {
|
||||
VIRIDIAN_GYM = { city = "VIRIDIAN CITY", leader = "GIOVANNI", badge = "EARTHBADGE" },
|
||||
}
|
||||
|
||||
-- The originals' middle branch shared by every leader's text_asm: beaten
|
||||
-- but EVENT_GOT_TM* unset means the bag was full when the victory script
|
||||
-- ran GiveItem, so talking to the leader re-runs the ReceiveTM script.
|
||||
-- Returns true when the retry took over the talk. A save from before
|
||||
-- #797 already holds the TM without the flag; treat the owned TM as
|
||||
-- received so those saves fall through to the advice text instead of
|
||||
-- collecting a second copy.
|
||||
local function retryTmGive(game, ow, victoryKey, done)
|
||||
local reward = require("data.scripts.victories")[victoryKey]
|
||||
if not (reward and reward.gotFlag) then return false end
|
||||
if game.save.flags[reward.gotFlag] then return false end
|
||||
local owned = game.save.inventory and game.save.inventory[reward.item] or 0
|
||||
if owned > 0 then
|
||||
game.save.flags[reward.gotFlag] = true
|
||||
return false
|
||||
end
|
||||
ow:offerGymTm(reward, done)
|
||||
return true
|
||||
end
|
||||
|
||||
-- scripts/PewterGym.asm PewterGymBrockText (text_asm): CheckEvent
|
||||
-- EVENT_BEAT_BROCK branches his dialogue. Before the badge he prints
|
||||
-- _PewterGymBrockPreBattleText and engages the leader battle
|
||||
@@ -25,12 +45,14 @@ local M = {
|
||||
-- badge/TM34 rewards and EVENT_BEAT_BROCK come from
|
||||
-- data/scripts/victories.lua OPP_BROCK#1). After the badge his
|
||||
-- .afterBeat branch prints _PewterGymBrockPostBattleAdviceText ("Go to
|
||||
-- the GYM in CERULEAN..."). The original's middle branch (beat but
|
||||
-- TM34 not yet handed over, CheckEventReuseA EVENT_GOT_TM34) is
|
||||
-- unreachable in the port: the TM is granted with the victory.
|
||||
-- the GYM in CERULEAN..."). The middle branch (beat but TM34 not yet
|
||||
-- handed over, CheckEventReuseA EVENT_GOT_TM34 -> call
|
||||
-- PewterGymScriptReceiveTM34) retries the TM give when the bag was full
|
||||
-- at the victory (#797).
|
||||
M.PEWTER_GYM.talk = {
|
||||
TEXT_PEWTERGYM_BROCK = function(game, ow, npc, done)
|
||||
if game.save.flags.EVENT_BEAT_BROCK then
|
||||
if retryTmGive(game, ow, "OPP_BROCK#1", done) then return end
|
||||
local TextBox = require("src.render.TextBox")
|
||||
game.stack:push(TextBox.new(game,
|
||||
game.data.text._PewterGymBrockPostBattleAdviceText
|
||||
@@ -48,16 +70,17 @@ M.PEWTER_GYM.talk = {
|
||||
-- (engageTrainer shows that same pre-battle text via resolveText; the
|
||||
-- badge/TM rewards and the beat flag come from data/scripts/victories.lua)
|
||||
-- -- and once beaten print the post-battle advice text. As with Brock,
|
||||
-- the originals' middle branch (beaten but the TM not yet handed over,
|
||||
-- CheckEventReuseA EVENT_GOT_TM*) is unreachable in the port: the TM is
|
||||
-- granted with the victory.
|
||||
-- the middle branch (beaten but the TM not yet handed over,
|
||||
-- CheckEventReuseA EVENT_GOT_TM*) retries the TM give when the bag was
|
||||
-- full at the victory.
|
||||
-- afterAdvice, when given, takes over `done`: it is handed (game, ow, npc,
|
||||
-- done) and must call done() itself once whatever it's doing (e.g. a fade
|
||||
-- around a HideObject) finishes, rather than having it invoked
|
||||
-- automatically. Only Giovanni's farewell uses this.
|
||||
local function leaderTalk(beatFlag, adviceLabel, fallback, afterAdvice)
|
||||
local function leaderTalk(beatFlag, adviceLabel, fallback, afterAdvice, victoryKey)
|
||||
return function(game, ow, npc, done)
|
||||
if game.save.flags[beatFlag] then
|
||||
if victoryKey and retryTmGive(game, ow, victoryKey, done) then return end
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local finish = done
|
||||
if afterAdvice then
|
||||
@@ -79,42 +102,42 @@ end
|
||||
M.CERULEAN_GYM.talk = {
|
||||
TEXT_CERULEANGYM_MISTY = leaderTalk("EVENT_BEAT_MISTY",
|
||||
"_CeruleanGymMistyTM11ExplanationText",
|
||||
"TM11 teaches\nBUBBLEBEAM!"),
|
||||
"TM11 teaches\nBUBBLEBEAM!", nil, "OPP_MISTY#1"),
|
||||
}
|
||||
|
||||
-- scripts/VermilionGym.asm VermilionGymLTSurgeText .got_tm24_already
|
||||
M.VERMILION_GYM.talk = {
|
||||
TEXT_VERMILIONGYM_LT_SURGE = leaderTalk("EVENT_BEAT_LT_SURGE",
|
||||
"_VermilionGymLTSurgePostBattleAdviceText",
|
||||
"A little word of\nadvice, kid!"),
|
||||
"A little word of\nadvice, kid!", nil, "OPP_LT_SURGE#1"),
|
||||
}
|
||||
|
||||
-- scripts/CeladonGym.asm CeladonGymErikaText .afterBeat
|
||||
M.CELADON_GYM.talk = {
|
||||
TEXT_CELADONGYM_ERIKA = leaderTalk("EVENT_BEAT_ERIKA",
|
||||
"_CeladonGymErikaPostBattleAdviceText",
|
||||
"You are cataloging\nPOKéMON? I must\nsay I'm impressed."),
|
||||
"You are cataloging\nPOKéMON? I must\nsay I'm impressed.", nil, "OPP_ERIKA#1"),
|
||||
}
|
||||
|
||||
-- scripts/FuchsiaGym.asm FuchsiaGymKogaText .afterBeat
|
||||
M.FUCHSIA_GYM.talk = {
|
||||
TEXT_FUCHSIAGYM_KOGA = leaderTalk("EVENT_BEAT_KOGA",
|
||||
"_FuchsiaGymKogaPostBattleAdviceText",
|
||||
"When afflicted by\nTOXIC, POKéMON\nsuffer more and\nmore as battle\nprogresses!"),
|
||||
"When afflicted by\nTOXIC, POKéMON\nsuffer more and\nmore as battle\nprogresses!", nil, "OPP_KOGA#1"),
|
||||
}
|
||||
|
||||
-- scripts/SaffronGym.asm SaffronGymSabrinaText .afterBeat
|
||||
M.SAFFRON_GYM.talk = {
|
||||
TEXT_SAFFRONGYM_SABRINA = leaderTalk("EVENT_BEAT_SABRINA",
|
||||
"_SaffronGymSabrinaPostBattleAdviceText",
|
||||
"Everyone has\npsychic power!\nPeople just don't\nrealize it!"),
|
||||
"Everyone has\npsychic power!\nPeople just don't\nrealize it!", nil, "OPP_SABRINA#1"),
|
||||
}
|
||||
|
||||
-- scripts/CinnabarGym.asm CinnabarGymBlaineText .afterBeat
|
||||
M.CINNABAR_GYM.talk = {
|
||||
TEXT_CINNABARGYM_BLAINE = leaderTalk("EVENT_BEAT_BLAINE",
|
||||
"_CinnabarGymBlainePostBattleAdviceText",
|
||||
"FIRE BLAST is the\nultimate fire\ntechnique!"),
|
||||
"FIRE BLAST is the\nultimate fire\ntechnique!", nil, "OPP_BLAINE#1"),
|
||||
}
|
||||
|
||||
-- scripts/ViridianGym.asm ViridianGymGiovanniText .afterBeat: after the
|
||||
@@ -142,7 +165,7 @@ M.VIRIDIAN_GYM.talk = {
|
||||
"VIRIDIAN_GYM", "VIRIDIANGYM_GIOVANNI")
|
||||
end
|
||||
end, done))
|
||||
end),
|
||||
end, "OPP_GIOVANNI#3"),
|
||||
}
|
||||
|
||||
return M
|
||||
|
||||
@@ -48,7 +48,8 @@ end
|
||||
if GameVersion.isYellow() then
|
||||
for _, file in ipairs({ "data.scripts.yellow_gifts",
|
||||
"data.scripts.yellow_jessie_james",
|
||||
"data.scripts.yellow_beach_house" }) do
|
||||
"data.scripts.yellow_beach_house",
|
||||
"data.scripts.yellow_viridian_old_man" }) do
|
||||
for mapId, mod in pairs(require(file)) do
|
||||
MapScripts.attachBase(mapId, mod)
|
||||
end
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
-- takes it ("I'll take this one, then!") and both balls disappear.
|
||||
-- Source: scripts/OaksLab.asm OaksLabCharmanderPokeBallText /
|
||||
-- OaksLabRivalTakePokeBallScript.
|
||||
-- * Leftover ball (after the pick): Oak turns and reads the last-mon
|
||||
-- line instead of re-offering the starter (OaksLabLastMonScript, #601).
|
||||
-- * Rival (object 1): before starter -> "go ahead and choose" once Oak
|
||||
-- has walked you in, else "gramps isn't around" (#218); with
|
||||
-- starter -> taunt + battle OPP_RIVAL1 with the counter-pick party
|
||||
@@ -20,10 +22,10 @@ local function starterBall(askText, species, choseFlag, ownBall,
|
||||
rivalBallX, rivalBall)
|
||||
return {
|
||||
{ "check_flag", "EVENT_GOT_STARTER" }, -- 1
|
||||
{ "jump_if_true", 20 }, -- 2
|
||||
{ "jump_if_true", 22 }, -- 2
|
||||
-- no picking until Oak has walked you in (OaksLabScript gating)
|
||||
{ "check_flag", "EVENT_FOLLOWED_OAK_INTO_LAB" }, -- 3
|
||||
{ "jump_if_false", 20 }, -- 4
|
||||
{ "jump_if_false", 25 }, -- 4
|
||||
-- the Pokédex "new species" entry shows before the ask (predef
|
||||
-- StarterDex ahead of OaksLabYouWant...Text). StarterDex temporarily
|
||||
-- sets the owned bits so ShowPokedexData prints height/weight/text;
|
||||
@@ -31,30 +33,45 @@ local function starterBall(askText, species, choseFlag, ownBall,
|
||||
{ "push_screen", "DexEntryMenu",
|
||||
{ species = species, forceOwned = true } }, -- 5
|
||||
{ "ask", askText }, -- 6
|
||||
{ "jump_if_false", 21 }, -- 7
|
||||
{ "jump_if_false", "end" }, -- 7
|
||||
-- OaksLab.asm prints ReceivedMon then AddPartyMon (AskName lives
|
||||
-- inside give_pokemon). Show the received text first so the
|
||||
-- nickname prompt follows "you got X", matching Gen1.
|
||||
{ "show_text", "_OaksLabReceivedMonText", { RAM = species } }, -- 8
|
||||
{ "give_pokemon", species, 5 }, -- 9
|
||||
{ "set_flag", "EVENT_GOT_STARTER" }, -- 10
|
||||
{ "set_flag", choseFlag }, -- 11
|
||||
-- The received text carries sound_get_key_item (OaksLab.asm
|
||||
-- OaksLabReceivedMonText); the jingle plays as the box opens
|
||||
-- (same beat as the Yellow port's starter, #668).
|
||||
{ "play_sound", "Get_Key_Item" }, -- 8
|
||||
{ "show_text", "_OaksLabReceivedMonText", { RAM = species } }, -- 9
|
||||
{ "give_pokemon", species, 5 }, -- 10
|
||||
{ "set_flag", "EVENT_GOT_STARTER" }, -- 11
|
||||
{ "set_flag", choseFlag }, -- 12
|
||||
-- POKé BALLs are not handed out here in the original -- Oak gives
|
||||
-- them later, at OaksLabOak1Text's .give_poke_balls beat once the
|
||||
-- player has beaten the Route 22 rival (see TEXT_OAKSLAB_OAK1 below)
|
||||
{ "hide_object", "OAKS_LAB", ownBall }, -- 12
|
||||
{ "hide_object", "OAKS_LAB", ownBall }, -- 13
|
||||
-- the rival walks to the countering ball (around the furniture)
|
||||
{ "move_npc_to", 1, rivalBallX, 4 }, -- 13
|
||||
{ "face_object", 1, "up" }, -- 14
|
||||
{ "show_text", "_OaksLabRivalIllTakeThisOneText" }, -- 15
|
||||
{ "hide_object", "OAKS_LAB", rivalBall }, -- 16
|
||||
{ "move_npc_to", 1, rivalBallX, 4 }, -- 14
|
||||
{ "face_object", 1, "up" }, -- 15
|
||||
{ "show_text", "_OaksLabRivalIllTakeThisOneText" }, -- 16
|
||||
{ "hide_object", "OAKS_LAB", rivalBall }, -- 17
|
||||
{ "play_sound", "Get_Key_Item" }, -- 18 (sound_get_key_item)
|
||||
{ "show_text", "_OaksLabRivalReceivedMonText",
|
||||
{ RAM = rivalBall == "OAKSLAB_CHARMANDER_POKE_BALL" and "CHARMANDER"
|
||||
or rivalBall == "OAKSLAB_SQUIRTLE_POKE_BALL" and "SQUIRTLE"
|
||||
or "BULBASAUR" } }, -- 17
|
||||
{ "jump", 21 }, -- 18
|
||||
{ "jump", 21 }, -- 19 (spacer)
|
||||
{ "show_text", "_OaksLabThoseArePokeBallsText" }, -- 20
|
||||
or "BULBASAUR" } }, -- 19
|
||||
{ "jump", "end" }, -- 20
|
||||
{ "jump", "end" }, -- 21 (spacer)
|
||||
-- a leftover ball after the player's pick: Oak turns to face the
|
||||
-- player and reads the last-mon line instead of re-offering the
|
||||
-- starter (scripts/OaksLab.asm OaksLabSelectedPokeBallScript ->
|
||||
-- OaksLabLastMonScript; #601). The ROM's "#MON" ligature is spelled
|
||||
-- out as Pokémon here.
|
||||
{ "face_object", 5, "down" }, -- 22
|
||||
{ "show_text", "That's PROF.OAK's\nlast Pokémon!" }, -- 23
|
||||
-- OaksLabLastMonScript ends at TextScriptEnd; the port used to fall
|
||||
-- through into the pre-pick line below (#601 remnant, reported on #600)
|
||||
{ "jump", "end" }, -- 24
|
||||
{ "show_text", "_OaksLabThoseArePokeBallsText" }, -- 25
|
||||
}
|
||||
end
|
||||
|
||||
@@ -62,10 +79,22 @@ return {
|
||||
talk = {
|
||||
-- Oak: OaksLabOak1Text. Parcel delivery kicks SCRIPT_OAKSLAB_RIVAL_
|
||||
-- ARRIVES_AT_OAKS_REQUEST + OaksLabOakGivesPokedexScript (rival walk-
|
||||
-- in, full Pokédex speech, rival exit, Route 22 arm). Dex-rating
|
||||
-- (DisplayDexRating) is still skipped.
|
||||
-- in, full Pokédex speech, rival exit, Route 22 arm).
|
||||
TEXT_OAKSLAB_OAK1 = {
|
||||
{ "face_player" },
|
||||
-- OaksLabOak1Text leads with the dex-rating branch (#600): with
|
||||
-- EVENT_PALLET_AFTER_GETTING_POKEBALLS set (converted saves), or
|
||||
-- 2+ species owned once the Pokédex is in hand, Oak asks how it is
|
||||
-- coming and rates it (predef DisplayDexRating). Red keeps the
|
||||
-- GOT_POKEDEX gate that Yellow's copy of this text drops
|
||||
-- (data/scripts/oaks_lab_yellow.lua).
|
||||
{ "check_flag", "EVENT_PALLET_AFTER_GETTING_POKEBALLS" },
|
||||
{ "jump_if_true", "dex_rating" },
|
||||
{ "check_dex_owned", 2 },
|
||||
{ "jump_if_false", "no_rating" },
|
||||
{ "check_flag", "EVENT_GOT_POKEDEX" },
|
||||
{ "jump_if_true", "dex_rating" },
|
||||
{ "label", "no_rating" },
|
||||
{ "check_item", "POKE_BALL" },
|
||||
{ "jump_if_true", "come_see" },
|
||||
{ "check_flag", "EVENT_BEAT_ROUTE22_RIVAL_1ST_BATTLE" },
|
||||
@@ -151,6 +180,14 @@ return {
|
||||
|
||||
{ "label", "come_see" },
|
||||
{ "show_text", "_OaksLabOak1ComeSeeMeSometimesText" },
|
||||
{ "jump", "end" },
|
||||
|
||||
-- .HowIsYourPokedexComingText ends on `prompt` and OaksLabOak1Text
|
||||
-- sets wDoNotWaitForButtonPressAfterDisplayingText, so the seen/owned
|
||||
-- tally follows with no button wait (engine/events/pokedex_rating.asm)
|
||||
{ "label", "dex_rating" },
|
||||
{ "show_text", "_OaksLabOak1HowIsYourPokedexComingText" },
|
||||
{ "dex_rating" },
|
||||
},
|
||||
|
||||
TEXT_OAKSLAB_CHARMANDER_POKE_BALL =
|
||||
@@ -245,7 +282,12 @@ return {
|
||||
and y >= 6 then
|
||||
local rival = ow:npcByIndex(1)
|
||||
if not rival then return false end
|
||||
-- OaksLabRivalChallengesPlayerScript swaps in the rival encounter
|
||||
-- fanfare for the taunt/challenge exchange, same as the Yellow port
|
||||
-- (oaks_lab_yellow.lua); it was silently dropped here (#596).
|
||||
local rows = {
|
||||
{ "stop_music" },
|
||||
{ "play_music", "Music_MeetRival" },
|
||||
{ "show_text", "_OaksLabRivalIllTakeYouOnText" }, -- 1
|
||||
}
|
||||
-- the rival routes to a free cell beside the player
|
||||
@@ -283,8 +325,15 @@ return {
|
||||
table.insert(rows, { "jump_if_false", base + 6 })
|
||||
table.insert(rows, { "show_text", "_OaksLabRivalIPickedTheWrongPokemonText" })
|
||||
table.insert(rows, { "show_text", "_OaksLabRivalSmellYouLaterText" })
|
||||
-- OaksLabRivalStartsExitScript: parting shot, rival exit fanfare, then
|
||||
-- walk out past the player. The fanfare was dropped here (#683) -- the
|
||||
-- parcel scene above already plays Music_MeetRival on both arrival and
|
||||
-- departure (lines 144-146), and this exit should match (#596).
|
||||
table.insert(rows, { "stop_music" })
|
||||
table.insert(rows, { "play_music", "Music_MeetRival" })
|
||||
table.insert(rows, { "move_npc_to", 1, 4, 11 })
|
||||
table.insert(rows, { "hide_object", "OAKS_LAB", "OAKSLAB_RIVAL" })
|
||||
table.insert(rows, { "play_music", "Music_OaksLab" })
|
||||
ow.runner:run(rows, { npc = rival })
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -45,6 +45,8 @@ return {
|
||||
{ "stop_music" },
|
||||
{ "play_music", "Music_MeetRival" },
|
||||
{ "show_text", "_OaksLabRivalGrampsText" },
|
||||
-- callfar OaksLabPikachuMovementScript, before ShowObject (#1021)
|
||||
{ "pikachu_make_way" },
|
||||
{ "show_object", "OAKS_LAB", "OAKSLAB_RIVAL" },
|
||||
{ "place_npc", RIVAL, 4, 7, "up" },
|
||||
{ "move_npc_to", RIVAL, 4, 3 },
|
||||
@@ -71,8 +73,12 @@ return {
|
||||
{ "show_text", "_OaksLabRivalLeaveItAllToMeText" },
|
||||
{ "set_flag", "EVENT_GOT_POKEDEX" },
|
||||
{ "set_flag", "EVENT_OAK_GOT_PARCEL" },
|
||||
-- OaksLabOakGivesPokedexScript: HideObject TOGGLE_LYING_OLD_MAN /
|
||||
-- ShowObject TOGGLE_OLD_MAN_2 -- Yellow's tutorial old man stands
|
||||
-- on the sleeper's cell (18,9); the Red/Blue walker OLD_MAN at
|
||||
-- (17,5) never appears in Yellow (#617)
|
||||
{ "hide_object", "VIRIDIAN_CITY", "VIRIDIANCITY_OLD_MAN_SLEEPY" },
|
||||
{ "show_object", "VIRIDIAN_CITY", "VIRIDIANCITY_OLD_MAN" },
|
||||
{ "show_object", "VIRIDIAN_CITY", "VIRIDIANCITY_OLD_MAN2" },
|
||||
{ "stop_music" },
|
||||
{ "play_music", "Music_MeetRival" },
|
||||
{ "move_npc_to", RIVAL, 4, 7 },
|
||||
@@ -143,17 +149,25 @@ return {
|
||||
-- OaksLabRivalExclamationScript: "!" over the rival
|
||||
{ "emote", RIVAL, "shock" },
|
||||
}
|
||||
-- .RivalPushesPlayerAwayFromEeveeBall + the PAD_RIGHT x2 shove:
|
||||
-- the rival cuts across to the ball WHILE the player standing
|
||||
-- below it is bumped two tiles right (both movements run in the
|
||||
-- same beat, so the walk overlaps the shove like the original)
|
||||
-- .RivalPushesPlayerAwayFromEeveeBall is DOWN then RIGHT x3 (the $07
|
||||
-- bytes are Yellow's own step-right encoding, decoded by
|
||||
-- engine/overworld/movement.asm Func_5288 -> Func_532b), and the
|
||||
-- PAD_RIGHT x2 shove is NOT queued alongside it:
|
||||
-- OaksLabRivalTakesPokeballScript .asm_1c564 polls every frame and
|
||||
-- only simulates the pair once wNPCNumScriptedSteps reads 1 -- i.e.
|
||||
-- as the rival begins the LAST byte, the step onto the tile the
|
||||
-- player is standing on. Starting both on one row had Red stroll
|
||||
-- off the Eevee while the rival was still at the top of the table
|
||||
-- (#559).
|
||||
if py == 4 then
|
||||
rows[#rows + 1] = { "walk_npc", RIVAL,
|
||||
{ "down", "right", "right", "right" }, { wait = false } }
|
||||
rows[#rows + 1] = { "walk_npc", RIVAL, { "down", "right", "right" } }
|
||||
-- this one runs concurrently with the shove below
|
||||
rows[#rows + 1] = { "walk_npc", RIVAL, { "right" }, { wait = false } }
|
||||
rows[#rows + 1] = { "face_player_dir", "left" }
|
||||
rows[#rows + 1] = { "move_player", "right", 2 }
|
||||
-- let the rival finish the last stretch to (7,4)
|
||||
rows[#rows + 1] = { "wait", 40 }
|
||||
-- move_player blocks for both tiles, so the rival has already
|
||||
-- landed on (7,4); this is just the beat before he turns up
|
||||
rows[#rows + 1] = { "wait", 20 }
|
||||
else
|
||||
rows[#rows + 1] = { "move_npc_to", RIVAL, 7, 4 }
|
||||
end
|
||||
@@ -180,15 +194,17 @@ return {
|
||||
end
|
||||
rows[#rows + 1] = { "face_player_dir", "up" }
|
||||
rows[#rows + 1] = { "face_object", OAK1, "down" }
|
||||
-- OaksLabPlayerReceivedMonText: no nickname prompt -- the starter
|
||||
-- Pikachu keeps its species name
|
||||
-- OaksLabPlayerReceivedMonText clears wMonDataLocation, so AskName runs (#1013)
|
||||
rows[#rows + 1] = { "show_text", "_OaksLabOakGivesText" }
|
||||
rows[#rows + 1] = { "play_sound", "Get_Key_Item" }
|
||||
rows[#rows + 1] = { "show_text", "_OaksLabReceivedText", { RAM = "PIKACHU" } }
|
||||
rows[#rows + 1] = { "give_pokemon", "PIKACHU", 5, true }
|
||||
rows[#rows + 1] = { "give_pokemon", "PIKACHU", 5 }
|
||||
-- DisablePikachuOverworldSpriteDrawing keeps it in the ball (#1009)
|
||||
rows[#rows + 1] = { "set_field", "pikachuInBall", true }
|
||||
rows[#rows + 1] = { "set_flag", "EVENT_GOT_STARTER" }
|
||||
rows[#rows + 1] = { "set_flag", "EVENT_CHOSE_PIKACHU" }
|
||||
ow.runner:run(rows, { npc = npc, onDone = done })
|
||||
ow.runner:run(rows, { npc = npc, onDone = done,
|
||||
checkpointOnDone = "release_npc" })
|
||||
end,
|
||||
|
||||
TEXT_OAKSLAB_RIVAL = {
|
||||
@@ -275,16 +291,18 @@ return {
|
||||
table.insert(rows, { "label", "lost_lab" })
|
||||
table.insert(rows, { "set_field", "rivalStarter", 3 })
|
||||
table.insert(rows, { "label", "exit" })
|
||||
-- OaksLabRivalStartsExitScript: parting shot, walk out past the
|
||||
-- player, restore the lab theme
|
||||
-- OaksLabRivalStartsExitScript: parting shot, rival exit fanfare, then
|
||||
-- walk out past the player (#683).
|
||||
table.insert(rows, { "wait", 20 })
|
||||
table.insert(rows, { "show_text", "_OaksLabRivalSmellYouLaterText" })
|
||||
table.insert(rows, { "stop_music" })
|
||||
table.insert(rows, { "play_music", "Music_MeetRival" })
|
||||
table.insert(rows, { "move_npc_to", RIVAL, 4, 11 })
|
||||
table.insert(rows, { "hide_object", "OAKS_LAB", "OAKSLAB_RIVAL" })
|
||||
table.insert(rows, { "play_music", "Music_OaksLab" })
|
||||
-- OaksLabPikachuEscapesPokeballScript: Pikachu hates its ball.
|
||||
-- The overworld follower itself is still an open port
|
||||
-- (docs/yellow-version.md runtime backlog); the story beat plays.
|
||||
-- OaksLabPikachuEscapesPokeballScript: the follower reaches the map (#1009)
|
||||
table.insert(rows, { "face_player_dir", "up" })
|
||||
table.insert(rows, { "set_field", "pikachuInBall", false })
|
||||
table.insert(rows, { "play_cry", "PIKACHU" })
|
||||
table.insert(rows, { "show_text", "_OaksLabPikachuDislikesPokeballsText1" })
|
||||
table.insert(rows, { "show_text", "_OaksLabPikachuDislikesPokeballsText2" })
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
-- (.PlayerNextToSafariZoneWorker1CoordsArray). Paying ¥500 hands over
|
||||
-- 30 SAFARI BALLs and starts the 502-step game
|
||||
-- (SafariZoneGateWouldYouLikeToJoinScript: wSafariSteps = 502,
|
||||
-- wNumSafariBalls = SAFARI_BALLS_RECEIVED). Declining walks you back
|
||||
-- so you can't slip past. Returning to the gate ends the game and the
|
||||
-- worker takes the leftover balls back.
|
||||
-- wNumSafariBalls = SAFARI_BALLS_RECEIVED), then auto-walks the player
|
||||
-- up through the north warp into the zone. Declining walks you back
|
||||
-- so you can't slip past. Returning to the gate ends the game, the
|
||||
-- worker takes the leftover balls back and the auto-walk drops you 3
|
||||
-- cells below the warp you came in by (#540).
|
||||
--
|
||||
-- Step/ball bookkeeping lives in src/world/OverworldController.lua
|
||||
-- (safariStep/safariGameOver, from
|
||||
@@ -19,7 +21,33 @@ local FEE = 500
|
||||
local BALLS = 30
|
||||
local STEPS = 502
|
||||
|
||||
local function startGame(game, t, done, balls, introText)
|
||||
-- SafariZoneGateSafariZoneWorker1WouldYouLikeToJoinText .success closes with
|
||||
-- `ld a, PAD_UP / ld c, 3 / SafariZoneEntranceAutoWalk`: paying walks the
|
||||
-- player up out of the gate and through the north warp, it is never left to
|
||||
-- the player. EVENT_IN_SAFARI_ZONE is already set when that walk runs, so
|
||||
-- the two gate steps taken before the warp fires are charged against
|
||||
-- wSafariSteps (home/overworld.asm:307-310) -- which is why the counter
|
||||
-- reads 500/500 on arrival even though the script wrote 502 (#540). The
|
||||
-- port's counter only runs on the nine interior maps (FieldDefaults
|
||||
-- safari.stepMaps, OverworldState:inSafariStepZone), so charge those two
|
||||
-- steps here instead.
|
||||
local function walkIntoZone(game, ow)
|
||||
local p = ow.player
|
||||
-- only from the two trigger cells in front of the worker, which are the
|
||||
-- columns the north warps sit on; a player who paid after TALKING to him
|
||||
-- from somewhere else walks in on their own, as they do today
|
||||
local w = p.cellY == 2 and ow.map:warpAtCell(p.cellX, 0) or nil
|
||||
if not w then return end
|
||||
ow:scriptMove(p, "up", 2, function()
|
||||
local st = game.save.safari
|
||||
if st then st.steps = st.steps - 2 end
|
||||
-- scripted steps skip onStepComplete (and with it CheckWarpsNoCollision),
|
||||
-- so take that warp explicitly once the walk lands on it
|
||||
ow:takeWarp(w.def)
|
||||
end)
|
||||
end
|
||||
|
||||
local function startGame(game, ow, t, done, balls, introText)
|
||||
game.save.safari = { balls = balls or BALLS, steps = STEPS }
|
||||
game.save.safariNags = nil
|
||||
local TextBox = require("src.render.TextBox")
|
||||
@@ -31,7 +59,10 @@ local function startGame(game, t, done, balls, introText)
|
||||
local pa = t._SafariZoneGateSafariZoneWorker1CallYouOnThePAText
|
||||
or "\fWe'll call you on\nthe PA when you\nrun out of time\nor SAFARI BALLs!"
|
||||
local luck = t._SafariZoneGateSafariZoneWorker1GoodLuckText or "Good Luck!"
|
||||
game.stack:push(TextBox.new(game, paid .. pa .. "\f" .. luck, done))
|
||||
game.stack:push(TextBox.new(game, paid .. pa .. "\f" .. luck, function()
|
||||
if done then done() end
|
||||
walkIntoZone(game, ow)
|
||||
end))
|
||||
end
|
||||
|
||||
-- Yellow's soft-lock fix (scripts/SafariZoneGate_2.asm): a player short of
|
||||
@@ -52,7 +83,7 @@ local function yellowLowCost(game, ow, t, done, back)
|
||||
or "\fOh, all right, pay\nme what you have.")
|
||||
.. "\f" .. (t._SafariZoneLowCostText2
|
||||
or "But, I can't give\nyou all 30 BALLs.")
|
||||
startGame(game, t, done, balls, intro)
|
||||
startGame(game, ow, t, done, balls, intro)
|
||||
return
|
||||
end
|
||||
local nag = game.save.safariNags or 0
|
||||
@@ -62,7 +93,7 @@ local function yellowLowCost(game, ow, t, done, back)
|
||||
(t._SafariZoneLowCostText8 or "Read my lips, NO!\nGet it?")
|
||||
.. (t._SafariZoneLowCostText3
|
||||
or "\fYou're persistent,\naren't you?\fOK, you can go in\nfor free, but\njust this once!")
|
||||
startGame(game, t, done, 1, intro)
|
||||
startGame(game, ow, t, done, 1, intro)
|
||||
return
|
||||
end
|
||||
local nags = {
|
||||
@@ -100,7 +131,7 @@ local function joinPrompt(game, ow, done)
|
||||
end
|
||||
else
|
||||
game.save.money = game.save.money - FEE
|
||||
startGame(game, t, done)
|
||||
startGame(game, ow, t, done)
|
||||
end
|
||||
end))
|
||||
end))
|
||||
@@ -135,27 +166,38 @@ M.SAFARI_ZONE_GATE = {
|
||||
-- no walks you back into the zone
|
||||
onEnter = function(game, ow)
|
||||
if not game.save.safari or ow.player.cellY > 1 then return end
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
||||
local t = game.data.text
|
||||
game.stack:push(TextBox.new(game,
|
||||
t._SafariZoneGateSafariZoneWorker1LeavingEarlyText or "Leaving early?",
|
||||
function()
|
||||
game.stack:push(ChoiceBox.new(game, function(yes)
|
||||
if not yes then
|
||||
-- back into the zone through the entrance warp
|
||||
local w = game.data.maps.SAFARI_ZONE_CENTER.warps[1]
|
||||
ow:startWarpTo("SAFARI_ZONE_CENTER", w.x, w.y, "up")
|
||||
return
|
||||
end
|
||||
game.save.safari = nil
|
||||
game.stack:push(TextBox.new(game,
|
||||
(t._SafariZoneGateSafariZoneWorker1ReturnSafariBallsText
|
||||
or "Please return any\nSAFARI BALLs you\nhave left.")
|
||||
.. "\f" .. (t._SafariZoneGateSafariZoneWorker1GoodHaulComeAgainText
|
||||
or "Did you get a\ngood haul?\fCome again!")))
|
||||
end))
|
||||
end))
|
||||
-- QUEUED, never pushed: onEnter runs inside the arriving warp's
|
||||
-- Transition midpoint, and Transition:finish pops whatever is on top
|
||||
-- the same frame (Timing.WARP_FADE_IN is 0) -- so a box pushed here is
|
||||
-- swallowed, and on a build where it survived it drew over a screen
|
||||
-- still faded to black (#540). Same contract as M.HALL_OF_FAME in
|
||||
-- data/scripts/story.lua.
|
||||
--
|
||||
-- SafariZoneGateLeavingSafariScript .leaving_early: YES prints the
|
||||
-- return-balls text, faces the player down and runs
|
||||
-- SafariZoneEntranceAutoWalk with `PAD_DOWN, c = 3`, landing on the
|
||||
-- counter row 3 cells below the warp you came in by; NO prints
|
||||
-- "Good Luck!" and walks one step back up through that same warp.
|
||||
local rightSide = ow.player.cellX ~= 3
|
||||
local dest = game.data.maps.SAFARI_ZONE_CENTER.warps[rightSide and 2 or 1]
|
||||
ow:queueScript({
|
||||
{ "ask", "_SafariZoneGateSafariZoneWorker1LeavingEarlyText" },
|
||||
{ "jump_if_false", "stay" },
|
||||
-- the port never reaches SafariZoneGateLeavingSafariScript's own
|
||||
-- GOOD_HAUL_COME_AGAIN branch (safariGameOver warps straight to the
|
||||
-- counter), so the sign-off rides on this path
|
||||
{ "show_text", "_SafariZoneGateSafariZoneWorker1ReturnSafariBallsText" },
|
||||
{ "show_text", "_SafariZoneGateSafariZoneWorker1GoodHaulComeAgainText" },
|
||||
-- no value: set_field assigns nil, which is how save.safari is cleared
|
||||
{ "set_field", "safari" },
|
||||
-- move_player runs through scriptMove, which skips onStepComplete, so
|
||||
-- walking back down past (x,2) cannot re-fire the join trigger
|
||||
{ "move_player", "down", 3 },
|
||||
{ "jump", "end" },
|
||||
{ "label", "stay" },
|
||||
{ "show_text", "_SafariZoneGateSafariZoneWorker1GoodLuckText" },
|
||||
{ "warp", "SAFARI_ZONE_CENTER", dest.x, dest.y, "up" },
|
||||
})
|
||||
end,
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
-- field.seafoam (SEAFOAM_ISLANDS_1F/B1F/B3F holes+holeDestination and
|
||||
-- B3F's pluggedByHolesOn) plus the generic
|
||||
-- OverworldState:boulderIntoHole in src/world/OverworldController.lua;
|
||||
-- no per-map onEnter hook is needed here.
|
||||
-- no per-map onEnter hook is needed for the boulders.
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -23,4 +23,46 @@ M.VERMILION_GYM = {
|
||||
end,
|
||||
}
|
||||
|
||||
-- The PLAYER falling down those same holes (#599). field.seafoam's holes
|
||||
-- carry only the BOULDER's object cell on the floor below (landsAt), not
|
||||
-- the player's landing, so the player's landing is spelled out here: it
|
||||
-- comes from data/maps/special_warps.asm DungeonWarpList/DungeonWarpData,
|
||||
-- which the importer does not extract. Same shape as MANSION_HOLES in
|
||||
-- data/scripts/story6.lua and VICTORY_ROAD_3F.onStep in
|
||||
-- data/scripts/story.lua; CAVERN $22 is a walkable tile, so the fall has
|
||||
-- to be an onStep, not a collision block.
|
||||
--
|
||||
-- Sources: scripts/SeafoamIslands1F.asm Seafoam1HolesCoords (17,6)/(24,6),
|
||||
-- B1F.asm Seafoam2HolesCoords (18,6)/(23,6), B2F.asm Seafoam3HolesCoords
|
||||
-- (19,6)/(22,6), B3F.asm Seafoam4HolesCoords (3,16)/(6,16). Each floor
|
||||
-- sets wDungeonWarpDestinationMap and calls IsPlayerOnDungeonWarp, and
|
||||
-- wCoordIndex picks that floor's DungeonWarpData row. Unconditional in
|
||||
-- the original: a plugged hole still drops the player. The B3F and B4F
|
||||
-- landings are water; setMap's CheckForceBikeOrSurf pass
|
||||
-- (OverworldState:checkForcedMovement) mounts SURF on arrival.
|
||||
local HOLE_FALLS = {
|
||||
SEAFOAM_ISLANDS_1F = { { 17, 6, "SEAFOAM_ISLANDS_B1F", 18, 7 },
|
||||
{ 24, 6, "SEAFOAM_ISLANDS_B1F", 23, 7 } },
|
||||
SEAFOAM_ISLANDS_B1F = { { 18, 6, "SEAFOAM_ISLANDS_B2F", 19, 7 },
|
||||
{ 23, 6, "SEAFOAM_ISLANDS_B2F", 22, 7 } },
|
||||
SEAFOAM_ISLANDS_B2F = { { 19, 6, "SEAFOAM_ISLANDS_B3F", 18, 7 },
|
||||
{ 22, 6, "SEAFOAM_ISLANDS_B3F", 19, 7 } },
|
||||
SEAFOAM_ISLANDS_B3F = { { 3, 16, "SEAFOAM_ISLANDS_B4F", 4, 14 },
|
||||
{ 6, 16, "SEAFOAM_ISLANDS_B4F", 5, 14 } },
|
||||
}
|
||||
|
||||
for mapId, holes in pairs(HOLE_FALLS) do
|
||||
M[mapId] = M[mapId] or {}
|
||||
M[mapId].onStep = function(game, ow, x, y)
|
||||
for _, h in ipairs(holes) do
|
||||
if x == h[1] and y == h[2] then
|
||||
require("src.core.Sound").play(game.data, "Faint_Fall")
|
||||
ow:startWarpTo(h[3], h[4], h[5], ow.player.facing)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -188,7 +188,12 @@ M.BILLS_HOUSE = {
|
||||
end
|
||||
if ow.player.facing == "down" then
|
||||
-- the player is standing on his straight path: walk around
|
||||
-- (.PokemonWalkAroundPlayerMovement)
|
||||
-- (.PokemonWalkAroundPlayerMovement). BillsHouseScript2 runs
|
||||
-- BillsHousePikachuWatchPlayer first on this branch, so a
|
||||
-- Pikachu that is still following steps clear of Bill's detour
|
||||
-- and turns to watch the player (#455).
|
||||
require("src.world.PikachuFollower")
|
||||
.onBillWalksAroundPlayer(game, ow)
|
||||
ow:scriptMove(npc, "right", 1, function()
|
||||
ow:scriptMove(npc, "up", 2, function()
|
||||
ow:scriptMove(npc, "left", 1, function()
|
||||
@@ -600,12 +605,34 @@ local function snorlaxWake(mapId, objName, beatFlag, wokeUpText, calmedText)
|
||||
}
|
||||
end
|
||||
|
||||
-- A beaten Snorlax is gone for good: Route12/Route16DefaultScript run
|
||||
-- HideObject in the same breath as the battle that sets
|
||||
-- EVENT_BEAT_ROUTEnn_SNORLAX, so "event set, object still on the map" is a
|
||||
-- state the asm cannot produce. Here it can (a mod's world:toggleObject, a
|
||||
-- save edited or migrated from a build older than the flag), and it is a
|
||||
-- dead end: ItemEffects' adjacentSleepingSnorlax refuses to wake a Snorlax
|
||||
-- whose beat flag is set (ItemUsePokeFlute's CheckEvent, engine/items/
|
||||
-- item_effects.asm), so the sleeper sits in the road forever and Cycling
|
||||
-- Road is unreachable (#585). Reconcile the toggle from the flag on every
|
||||
-- entry -- the mirror of SaveData.lua's toggle -> flag backfill, and the
|
||||
-- same repair shape the Silph Co. floors use below.
|
||||
local function hideBeatenSnorlax(mapId, objName, beatFlag)
|
||||
return function(game, ow)
|
||||
if not game.save.flags[beatFlag] then return end
|
||||
local Commands = require("src.script.Commands")
|
||||
Commands.hide_object({ game = game, save = game.save, overworld = ow },
|
||||
mapId, objName)
|
||||
end
|
||||
end
|
||||
|
||||
-- snorlaxWake is looked up by ItemEffects.lua/BagMenu.lua (via
|
||||
-- data/scripts/init.lua's M.get) and run when the flute wakes Snorlax;
|
||||
-- objName/beatFlag let ItemEffects find the NPC and check whether it's
|
||||
-- already been beaten before allowing the wake.
|
||||
M.ROUTE_12 = {
|
||||
talk = { TEXT_ROUTE12_SNORLAX = { { "show_text", "_Route12SnorlaxText" } } },
|
||||
onEnter = hideBeatenSnorlax("ROUTE_12", "ROUTE12_SNORLAX",
|
||||
"EVENT_BEAT_ROUTE12_SNORLAX"),
|
||||
snorlaxWake = {
|
||||
objName = "ROUTE12_SNORLAX", beatFlag = "EVENT_BEAT_ROUTE12_SNORLAX",
|
||||
script = snorlaxWake("ROUTE_12", "ROUTE12_SNORLAX", "EVENT_BEAT_ROUTE12_SNORLAX",
|
||||
@@ -614,6 +641,8 @@ M.ROUTE_12 = {
|
||||
}
|
||||
M.ROUTE_16 = {
|
||||
talk = { TEXT_ROUTE16_SNORLAX = { { "show_text", "_Route16Text7" } } },
|
||||
onEnter = hideBeatenSnorlax("ROUTE_16", "ROUTE16_SNORLAX",
|
||||
"EVENT_BEAT_ROUTE16_SNORLAX"),
|
||||
snorlaxWake = {
|
||||
objName = "ROUTE16_SNORLAX", beatFlag = "EVENT_BEAT_ROUTE16_SNORLAX",
|
||||
script = snorlaxWake("ROUTE_16", "ROUTE16_SNORLAX", "EVENT_BEAT_ROUTE16_SNORLAX",
|
||||
@@ -645,16 +674,18 @@ M.SAFARI_ZONE_SECRET_HOUSE = {
|
||||
M.WARDENS_HOUSE = {
|
||||
talk = {
|
||||
TEXT_WARDENSHOUSE_WARDEN = {
|
||||
-- Labelled rather than hand-numbered: the branches here have been
|
||||
-- re-pointed twice now (#535, #645), and every insert used to mean
|
||||
-- renumbering three jumps that had no way of announcing they were stale.
|
||||
{ "face_player" }, -- 1
|
||||
{ "check_flag", "EVENT_GOT_HM04" }, -- 2
|
||||
-- #535: previously jumped to the same silent-end target as the
|
||||
-- give-then-thank fallthrough (row 13), so the warden said nothing
|
||||
-- on every visit after the trade. pokered's .got_item branch
|
||||
-- (scripts/WardensHouse.asm) instead prints HM04ExplanationText,
|
||||
-- so route here to the new row 17 that does the same.
|
||||
{ "jump_if_true", 17 }, -- 3
|
||||
-- give-then-thank fallthrough, so the warden said nothing on every
|
||||
-- visit after the trade. pokered's .got_item branch
|
||||
-- (scripts/WardensHouse.asm) instead prints HM04ExplanationText.
|
||||
{ "jump_if_true", "got_hm04" }, -- 3
|
||||
{ "check_item", "GOLD_TEETH" }, -- 4
|
||||
{ "jump_if_false", 15 }, -- 5
|
||||
{ "jump_if_false", "no_teeth" }, -- 5
|
||||
{ "show_text", "_WardensHouseWardenGaveTheGoldTeethText" }, -- 6
|
||||
{ "take_item", "GOLD_TEETH", 1 }, -- 7
|
||||
{ "set_flag", "EVENT_GAVE_GOLD_TEETH" }, -- 8
|
||||
@@ -663,15 +694,27 @@ M.WARDENS_HOUSE = {
|
||||
{ "give_item", "HM_STRENGTH", 1, false }, -- 10
|
||||
{ "show_text", "_WardensHouseWardenReceivedHM04Text" }, -- 11
|
||||
{ "set_flag", "EVENT_GOT_HM04" }, -- 12
|
||||
{ "jump", 18 }, -- 13 (already got it this convo; jp .done)
|
||||
{ "jump", 18 }, -- 14 (unused)
|
||||
{ "show_text", "_WardensHouseWardenGibberish1Text" }, -- 15
|
||||
{ "jump", 18 }, -- 16 (#535: skip the new explanation row below)
|
||||
{ "jump", "end" }, -- 13 (jp .done)
|
||||
|
||||
-- #645: WardensHouseWardenText prints Gibberish1, then YesNoChoice,
|
||||
-- and the warden answers the same gibberish either way -- Gibberish2
|
||||
-- on yes, Gibberish3 on no (scripts/WardensHouse.asm). The port
|
||||
-- printed the question and walked off before the answer.
|
||||
{ "label", "no_teeth" }, -- 14
|
||||
{ "ask", "_WardensHouseWardenGibberish1Text" }, -- 15
|
||||
{ "jump_if_true", "gibberish_yes" }, -- 16
|
||||
{ "show_text", "_WardensHouseWardenGibberish3Text" }, -- 17
|
||||
{ "jump", "end" }, -- 18
|
||||
{ "label", "gibberish_yes" }, -- 19
|
||||
{ "show_text", "_WardensHouseWardenGibberish2Text" }, -- 20
|
||||
{ "jump", "end" }, -- 21
|
||||
|
||||
-- #535: pokered .got_item branch (scripts/WardensHouse.asm) --
|
||||
-- printed on every subsequent talk once EVENT_GOT_HM04 is set.
|
||||
-- Text is _WardensHouseWardenHM04ExplanationText (text/WardensHouse.asm):
|
||||
-- HM04 teaches Strength, and hints at the Safari Zone secret house.
|
||||
{ "show_text", "_WardensHouseWardenHM04ExplanationText" }, -- 17
|
||||
{ "label", "got_hm04" }, -- 22
|
||||
{ "show_text", "_WardensHouseWardenHM04ExplanationText" }, -- 23
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -723,6 +766,28 @@ local function silphRocketsLeave(game, ow, onlyMap)
|
||||
end
|
||||
end
|
||||
|
||||
-- SilphCo11FGiovanniAfterBattleScript (scripts/SilphCo11F.asm) is the whole
|
||||
-- aftermath: DisplayTextID TEXT_SILPHCO11F_GIOVANNI_YOU_RUINED_OUR_PLANS,
|
||||
-- GBFadeOutToBlack, SilphCo11FTeamRocketLeavesScript, Delay3,
|
||||
-- GBFadeInFromBlack, then SetEvent. The port had only the hide pass, so the
|
||||
-- speech never played and every rocket blinked out in front of the player
|
||||
-- (#722). Same hide list as silphRocketsLeave, spelled as script rows so the
|
||||
-- fade can hold over it.
|
||||
local function silphAftermathRows()
|
||||
local rows = {
|
||||
{ "show_text", "_SilphCo11FGiovanniYouRuinedOurPlansText" },
|
||||
{ "fade", "out" },
|
||||
}
|
||||
for _, floor in ipairs(SILPH_ROCKET_OBJECTS) do
|
||||
for _, name in ipairs(floor[2]) do
|
||||
rows[#rows + 1] = { "hide_object", floor[1], name }
|
||||
end
|
||||
end
|
||||
rows[#rows + 1] = { "wait", 3 } -- Delay3
|
||||
rows[#rows + 1] = { "fade", "in" }
|
||||
return rows
|
||||
end
|
||||
|
||||
M.SILPH_CO_11F = {
|
||||
-- Giovanni's battle is a COORDINATE TRIGGER, not a talk.
|
||||
-- SilphCo11FDefaultScript (scripts/SilphCo11F.asm) checks
|
||||
@@ -735,9 +800,13 @@ M.SILPH_CO_11F = {
|
||||
-- line) would touch, and the whole Silph ending -- the flag, the Master
|
||||
-- Ball, the Saffron streets clearing -- silently never happened.
|
||||
--
|
||||
-- engageTrainer shows TEXT_SILPHCO11F_GIOVANNI as the battle text and,
|
||||
-- via victories.lua OPP_GIOVANNI#2, sets the event on a win; a loss
|
||||
-- sets nothing, so the trigger re-arms exactly as vanilla does.
|
||||
-- SilphCo11FDefaultScript orders it DisplayTextID TEXT_SILPHCO11F_GIOVANNI
|
||||
-- FIRST, then MoveSprite .GiovanniMovement: he speaks from behind the desk
|
||||
-- and only then walks the three tiles down. Moving him before the box made
|
||||
-- him cross the room in silence and deliver the speech point-blank (#869),
|
||||
-- so the box comes first here and engageTrainer skips its own battle text.
|
||||
-- victories.lua OPP_GIOVANNI#2 sets the event on a win; a loss sets
|
||||
-- nothing, so the trigger re-arms exactly as vanilla does.
|
||||
onStep = function(game, ow, x, y)
|
||||
if game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI then return false end
|
||||
if not ((x == 6 and y == 13) or (x == 7 and y == 12)) then return false end
|
||||
@@ -746,17 +815,28 @@ M.SILPH_CO_11F = {
|
||||
if npc.def and npc.def.name == "SILPHCO11F_GIOVANNI" then gio = npc break end
|
||||
end
|
||||
if not gio or ow:trainerDefeated(gio) then return false end
|
||||
ow:scriptMove(gio, "down", 3, function()
|
||||
gio:facePlayer(ow.player)
|
||||
ow:engageTrainer(gio, function()
|
||||
-- SilphCo11FTeamRocketLeavesScript: every Silph rocket leaves
|
||||
-- after the loss (the street rockets are handled by
|
||||
-- M.SAFFRON_CITY.onEnter in story4.lua).
|
||||
if game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI then
|
||||
silphRocketsLeave(game, ow)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
local TextBox = require("src.render.TextBox")
|
||||
game.stack:push(TextBox.new(game,
|
||||
game.data.text._SilphCo11FGiovanniText
|
||||
or "Ah {PLAYER}!\nSo we meet again!",
|
||||
function()
|
||||
ow:scriptMove(gio, "down", 3, function()
|
||||
gio:facePlayer(ow.player)
|
||||
ow:engageTrainer(gio, function()
|
||||
-- SilphCo11FGiovanniAfterBattleScript: the "Blast it all!"
|
||||
-- speech, then SilphCo11FTeamRocketLeavesScript behind a fade so
|
||||
-- every Silph rocket leaves off-screen (the street rockets are
|
||||
-- handled by M.SAFFRON_CITY.onEnter in story4.lua). Queued, not
|
||||
-- run here: the battle's own callbacks are still unwinding, so
|
||||
-- queueScript starts it on the first idle overworld frame --
|
||||
-- after the end-battle "Arrgh!!" box victories.lua OPP_GIOVANNI#2
|
||||
-- pushes (#722).
|
||||
if game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI then
|
||||
ow:queueScript(silphAftermathRows())
|
||||
end
|
||||
end, nil, true)
|
||||
end)
|
||||
end))
|
||||
return true
|
||||
end,
|
||||
onEnter = function(game, ow)
|
||||
@@ -899,6 +979,7 @@ M.VICTORY_ROAD_3F = {
|
||||
-- fall is onStep, not a collision block.
|
||||
onStep = function(game, ow, x, y)
|
||||
if x == 23 and y == 15 then
|
||||
require("src.core.Sound").play(game.data, "Faint_Fall")
|
||||
ow:startWarpTo("VICTORY_ROAD_2F", 22, 16, ow.player.facing)
|
||||
return true
|
||||
end
|
||||
@@ -937,40 +1018,67 @@ M.VICTORY_ROAD_3F = {
|
||||
local championsRoomRivalScript = {
|
||||
{ "face_player" }, -- 1
|
||||
{ "check_flag", "EVENT_BEAT_CHAMPION_RIVAL_THIS_RUN" }, -- 2
|
||||
{ "jump_if_true", 25 }, -- 3 past end
|
||||
-- "end" rather than a row number past the tail: this script grew by a row
|
||||
-- when the follow-Oak walk landed (#704), which silently turned the old
|
||||
-- numeric 26 into a jump ONTO the closing HALL_OF_FAME warp instead of past
|
||||
-- it, so a returning champion warped straight into the induction.
|
||||
{ "jump_if_true", "end" }, -- 3
|
||||
{ "show_text", "_ChampionsRoomRivalIntroText" }, -- 4
|
||||
{ "rival_battle", "OPP_RIVAL3", 1 }, -- 5
|
||||
{ "jump_if_false", 25 }, -- 6 past end
|
||||
{ "set_flag", "EVENT_BEAT_CHAMPION_RIVAL_THIS_RUN" }, -- 7
|
||||
{ "set_flag", "EVENT_BEAT_CHAMPION_RIVAL" }, -- 8
|
||||
-- ChampionsRoomRivalReadyToBattleScript plays MUSIC_FINAL_BATTLE after
|
||||
-- the intro text, before the battle itself (#706); pushBattle's wipe-time
|
||||
-- playBattle("final") then no-ops on the same song, so the theme stays
|
||||
-- continuous into the fight
|
||||
{ "play_music", "Music_FinalBattle" }, -- 5
|
||||
{ "rival_battle", "OPP_RIVAL3", 1 }, -- 6
|
||||
-- losing halts here; the numeric target this replaced pointed at the
|
||||
-- closing warp, which inducted a player who had just lost the fight (#704)
|
||||
{ "jump_if_false", "end" }, -- 7
|
||||
{ "set_flag", "EVENT_BEAT_CHAMPION_RIVAL_THIS_RUN" }, -- 8
|
||||
{ "set_flag", "EVENT_BEAT_CHAMPION_RIVAL" }, -- 9
|
||||
-- ChampionsRoomRivalDefeatedScript re-displays TEXT_CHAMPIONSROOM_RIVAL,
|
||||
-- whose text_asm takes the EVENT_BEAT_CHAMPION_RIVAL branch =
|
||||
-- _ChampionsRoomRivalAfterBattleText (the in-battle _RivalDefeatedText
|
||||
-- is the port's generic "<PLAYER> defeated BLUE!" engine line instead).
|
||||
{ "show_text", "_ChampionsRoomRivalAfterBattleText" }, -- 9
|
||||
{ "show_text", "_ChampionsRoomRivalAfterBattleText" }, -- 10
|
||||
-- ChampionsRoomOakArrivesScript: Music_Cities1AlternateTempo
|
||||
-- (Cities1, kept into HALL_OF_FAME like BIT_NO_MAP_MUSIC after
|
||||
-- defeating RIVAL3), then Oak's "{PLAYER}!" + reveal + walk in
|
||||
{ "play_music", "Music_Cities1", { keep = true } }, -- 10
|
||||
{ "show_text", "_ChampionsRoomOakText" }, -- 11
|
||||
{ "show_object", "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK" }, -- 12
|
||||
{ "move_npc", 2, "up", 5 }, -- 13 OakEntranceAfterVictoryMovement
|
||||
-- defeating RIVAL3), then Oak's "{PLAYER}!" + reveal + walk in.
|
||||
-- audio/alternate_tempo.asm Music_Cities1AlternateTempo is not a plain
|
||||
-- PlayMusic: it fades the current song out (wAudioFadeOutControl = 10),
|
||||
-- waits 100 frames for the fade, then restarts Cities1 with channel 1
|
||||
-- pointed at Music_Cities1_Ch1_AlternateTempo -- `tempo 232` where the
|
||||
-- normal Music_Cities1_Ch1 opens `tempo 144`, i.e. the slower, heavier
|
||||
-- reading of the town theme this scene is known for (#847).
|
||||
{ "fade_music", 10 }, -- 11
|
||||
{ "wait", 100 }, -- 12
|
||||
{ "play_music", "Music_Cities1", { keep = true, tempo = 232 } }, -- 13
|
||||
{ "show_text", "_ChampionsRoomOakText" }, -- 14
|
||||
{ "show_object", "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK" }, -- 15
|
||||
{ "move_npc", 2, "up", 5 }, -- 16 OakEntranceAfterVictoryMovement
|
||||
-- OakCongratulatesPlayerScript: rival faces left, Oak faces down
|
||||
{ "face_object", 1, "left" }, -- 14
|
||||
{ "face_object", 2, "down" }, -- 15
|
||||
{ "show_text", "_ChampionsRoomOakCongratulatesPlayerText" }, -- 16
|
||||
{ "face_object", 1, "left" }, -- 17
|
||||
{ "face_object", 2, "down" }, -- 18
|
||||
{ "show_text", "_ChampionsRoomOakCongratulatesPlayerText" }, -- 19
|
||||
-- OakDisappointedWithRivalScript: Oak turns to the rival (right)
|
||||
{ "face_object", 2, "right" }, -- 17
|
||||
{ "show_text", "_ChampionsRoomOakDisappointedWithRivalText" }, -- 18
|
||||
{ "face_object", 2, "right" }, -- 20
|
||||
{ "show_text", "_ChampionsRoomOakDisappointedWithRivalText" }, -- 21
|
||||
-- OakComeWithMeScript: Oak faces down again, then exits up
|
||||
{ "face_object", 2, "down" }, -- 19
|
||||
{ "show_text", "_ChampionsRoomOakComeWithMeText" }, -- 20
|
||||
{ "move_npc", 2, "up", 2 }, -- 21 OakExitChampionsRoomMovement
|
||||
{ "hide_object", "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK" }, -- 22
|
||||
{ "face_object", 2, "down" }, -- 22
|
||||
{ "show_text", "_ChampionsRoomOakComeWithMeText" }, -- 23
|
||||
{ "move_npc", 2, "up", 2 }, -- 24 OakExitChampionsRoomMovement
|
||||
{ "hide_object", "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK" }, -- 25
|
||||
-- ChampionsRoomPlayerFollowsOakScript / WalkToHallOfFame_RLEMovement.
|
||||
-- The player walks out after Oak instead of the screen just fading on the
|
||||
-- spot (#704). Route one tile right before walking north so the player
|
||||
-- reaches the north-wall HALL_OF_FAME warp without sharing the rival's
|
||||
-- (4,2) cell. The original simulated movement bypasses entity collision,
|
||||
-- but this scene should not visibly walk through the defeated rival.
|
||||
{ "move_player", "right", 1 }, -- 26
|
||||
{ "move_player", "up", 3 }, -- 27
|
||||
-- hand the induction off to the HALL_OF_FAME room (consumed by its
|
||||
-- onEnter), then warp up into it (destWarp 1 lands at (4,7) facing up)
|
||||
{ "set_field", "pendingHallOfFame", true }, -- 23
|
||||
{ "warp", "HALL_OF_FAME", 4, 7, "up" }, -- 24
|
||||
{ "set_field", "pendingHallOfFame", true }, -- 28
|
||||
{ "warp", "HALL_OF_FAME", 4, 7, "up" }, -- 29
|
||||
}
|
||||
|
||||
M.CHAMPIONS_ROOM = {
|
||||
|
||||
@@ -206,6 +206,13 @@ M.PALLET_TOWN = {
|
||||
end
|
||||
|
||||
local function escortToLab(oak)
|
||||
-- PalletMovementScript_OakMoveLeft
|
||||
-- (engine/overworld/auto_movement.asm) starts MUSIC_MUSEUM_GUY
|
||||
-- when the escort begins in Yellow. Until then, Pallet Town plays
|
||||
-- after the battle; Red/Blue leave MUSIC_MEET_PROF_OAK playing.
|
||||
if yellow then
|
||||
Music.play(game.data, "Music_MuseumGuy")
|
||||
end
|
||||
local numSteps = x - 10
|
||||
if oak and numSteps > 0 then
|
||||
ow:scriptMove(oak, "left", numSteps, function()
|
||||
@@ -249,14 +256,24 @@ M.PALLET_TOWN = {
|
||||
function()
|
||||
-- Oak turns toward the horizontally adjacent grass (left exit
|
||||
-- looks right, right exit looks left -- the
|
||||
-- EVENT_PLAYER_AT_RIGHT_EXIT_TO_PALLET_TOWN branch)
|
||||
-- EVENT_PLAYER_AT_RIGHT_EXIT_TO_PALLET_TOWN branch).
|
||||
-- In pokeyellow, PalletTownOakGreetsPlayerScript turns Oak and
|
||||
-- PalletTownPikachuBattleScript arms the battle on the next
|
||||
-- overworld iteration. OverworldLoopLessDelay
|
||||
-- (home/overworld.asm) burns two DelayFrame calls at the top
|
||||
-- of each iteration and calls RunMapScript before checking
|
||||
-- wCurOpponent, so those two DelayFrame calls are what keep
|
||||
-- Oak's turn on screen before the battle check fires.
|
||||
if oak then oak.facing = x == 10 and "right" or "left" end
|
||||
local battle = BattleState.newWild(game, "PIKACHU", 5)
|
||||
battle:makeOldManDemo("PROF.OAK")
|
||||
battle.onFinish = function()
|
||||
afterPikaBattle()
|
||||
end
|
||||
game.stack:push(battle)
|
||||
hold(2, nil, function()
|
||||
local battle = BattleState.newWild(game, "PIKACHU", 5)
|
||||
battle:makeOldManDemo("PROF.OAK")
|
||||
battle.onFinish = function()
|
||||
afterPikaBattle()
|
||||
end
|
||||
-- Use the standard wild-battle entry transition.
|
||||
Commands.pushBattle(ctx, battle)
|
||||
end)
|
||||
end))
|
||||
end
|
||||
|
||||
@@ -400,35 +417,163 @@ M.POKEMON_FAN_CLUB = {
|
||||
TEXT_POKEMONFANCLUB_CHAIRMAN = {
|
||||
{ "face_player" }, -- 1
|
||||
{ "check_flag", "EVENT_RECEIVED_BIKE_VOUCHER" }, -- 2
|
||||
{ "jump_if_true", 9 }, -- 3
|
||||
{ "show_text", "_PokemonFanClubChairmanIntroText" }, -- 4
|
||||
{ "show_text", "_PokemonFanClubChairmanStoryText" }, -- 5
|
||||
{ "jump_if_true", "nothing_left" }, -- 3
|
||||
-- YesNoChoice (scripts/PokemonFanClub.asm): NO forfeits the voucher (#1050)
|
||||
{ "ask", "_PokemonFanClubChairmanIntroText" }, -- 4
|
||||
{ "jump_if_false", "no_story" }, -- 5
|
||||
{ "show_text", "_PokemonFanClubChairmanStoryText" }, -- 6
|
||||
-- give-then-print like scripts/PokemonFanClub.asm (GiveItem
|
||||
-- fills wStringBuffer; the received text reads it)
|
||||
{ "give_item", "BIKE_VOUCHER", 1, false }, -- 6
|
||||
{ "show_text", "_PokemonFanClubReceivedBikeVoucherText" }, -- 7
|
||||
{ "set_flag", "EVENT_RECEIVED_BIKE_VOUCHER" }, -- 8
|
||||
{ "show_text", "_PokemonFanClubExplainBikeVoucherText" }, -- 9
|
||||
{ "give_item", "BIKE_VOUCHER", 1, false }, -- 7
|
||||
{ "show_text", "_PokemonFanClubReceivedBikeVoucherText" }, -- 8
|
||||
{ "set_flag", "EVENT_RECEIVED_BIKE_VOUCHER" }, -- 9
|
||||
{ "show_text", "_PokemonFanClubExplainBikeVoucherText" }, -- 10
|
||||
{ "jump", "end" }, -- 11
|
||||
{ "label", "no_story" }, -- 12
|
||||
{ "show_text", "_PokemonFanClubNoStoryText" }, -- 13
|
||||
{ "jump", "end" }, -- 14
|
||||
-- .nothingleft: the gift is done, he only reminisces now
|
||||
{ "label", "nothing_left" }, -- 15
|
||||
{ "show_text", "_PokemonFanClubChairFinalText" }, -- 16
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
-- BikeShopClerkText (scripts/BikeShop.asm) runs three ways: the BICYCLE is
|
||||
-- already yours, you are carrying the BIKE VOUCHER, or you get the sales
|
||||
-- pitch. The pitch draws its own window (TextBoxBorder hlcoord 0,0, b=4
|
||||
-- c=15) holding BikeShopMenuText and BikeShopMenuPrice, and leaves it up
|
||||
-- while the clerk keeps talking in the bottom box: the original never
|
||||
-- erases it before TextScriptEnd (#568).
|
||||
local BikeShopWindow = {}
|
||||
BikeShopWindow.__index = BikeShopWindow
|
||||
|
||||
function BikeShopWindow.new(game, footer, onChoose)
|
||||
local self = setmetatable({}, BikeShopWindow)
|
||||
self.game = game
|
||||
self.onChoose = onChoose
|
||||
self.index = 1
|
||||
self.active = true
|
||||
-- BikeShopClerkDoYouLikeItText stays on screen under the window for as
|
||||
-- long as the menu is up. The text box pushed on top types it out and
|
||||
-- pops itself; this copy of its last page takes over from there, so the
|
||||
-- bottom box never blanks between the pitch and the answer. Paginated
|
||||
-- with the themed column budget so the copy breaks where the box did.
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local box = require("src.ui.Theme").textBox or {}
|
||||
local pages = TextBox.paginate(TextBox.substitute(game, footer), box.maxCols)
|
||||
self.footer = pages[#pages]
|
||||
return self
|
||||
end
|
||||
|
||||
function BikeShopWindow:update()
|
||||
-- one answer only: the boxes pushed by onChoose sit on top of this
|
||||
-- state, but a second A on the same frame must not fire it twice
|
||||
if not self.active then return end
|
||||
local input = self.game.input
|
||||
-- HandleMenuInput with wMenuWrappingEnabled clear: the two rows clamp
|
||||
if input:wasPressed("up") then
|
||||
self.index = 1
|
||||
elseif input:wasPressed("down") then
|
||||
self.index = 2
|
||||
elseif input:wasPressed("a") or input:wasPressed("b") then
|
||||
local cancelled = input:wasPressed("b") -- bit B_PAD_B -> .cancel
|
||||
require("src.core.Sound").play(self.game.data, "Press_AB")
|
||||
self.active = false
|
||||
self.onChoose(not cancelled and self.index == 1)
|
||||
end
|
||||
end
|
||||
|
||||
function BikeShopWindow:draw()
|
||||
local Font = require("src.render.Font")
|
||||
local Strings = require("src.core.Strings")
|
||||
local Theme = require("src.ui.Theme")
|
||||
Font.drawBox(0, 0, 17, 6)
|
||||
love.graphics.setColor(0, 0, 0, 1)
|
||||
local bike = self.game.data.items.BICYCLE
|
||||
Font.draw(bike and bike.name or "BICYCLE", 16, 16) -- hlcoord 2, 2
|
||||
Font.draw("¥1000000", 64, 24) -- hlcoord 8, 3
|
||||
Font.draw(Strings("CANCEL"), 16, 32) -- `next` skips a row
|
||||
-- wTopMenuItemX 1, wTopMenuItemY 2, rows two apart
|
||||
Font.drawCode(Theme.cursor, 8, self.index == 1 and 16 or 32)
|
||||
if self.footer then
|
||||
-- same geometry TextBox resolves against, so a themed box matches
|
||||
local box = Theme.textBox or {}
|
||||
local tx, ty = box.tx or 0, box.ty or 12
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
Font.drawBox(tx, ty, box.tw or 20, box.th or 6)
|
||||
love.graphics.setColor(0, 0, 0, 1)
|
||||
for i, line in ipairs(self.footer) do
|
||||
Font.draw(line, (tx + 1) * 8, (ty + 2 * i) * 8)
|
||||
end
|
||||
end
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
|
||||
M.BIKE_SHOP = {
|
||||
talk = {
|
||||
TEXT_BIKESHOP_CLERK = function(game, ow, npc, done)
|
||||
local Bag = require("src.inventory.Bag")
|
||||
local Flags = require("src.script.Flags")
|
||||
local TextBox = require("src.render.TextBox")
|
||||
if (game.save.inventory.BICYCLE or 0) > 0 then
|
||||
game.stack:push(TextBox.new(game, "How's the\nBICYCLE treating\nyou?", done))
|
||||
elseif (game.save.inventory.BIKE_VOUCHER or 0) > 0 then
|
||||
game.save.inventory.BIKE_VOUCHER = nil
|
||||
game.save.inventory.BICYCLE = 1
|
||||
game.stack:push(TextBox.new(game,
|
||||
("Oh, that's a\nBIKE VOUCHER!\f%s exchanged\nit for a BICYCLE!")
|
||||
:format(game.save.player.name), done))
|
||||
else
|
||||
game.stack:push(TextBox.new(game,
|
||||
"A BICYCLE costs\n¥1000000. Sorry,\nno instalments!", done))
|
||||
local t = game.data.text
|
||||
local function say(text, after, opts)
|
||||
game.stack:push(TextBox.new(game, text, after, opts))
|
||||
end
|
||||
|
||||
-- CheckEvent EVENT_GOT_BICYCLE. Saves made before the clerk started
|
||||
-- setting the event still have the bike in the bag, so either counts.
|
||||
if (game.save.inventory.BICYCLE or 0) > 0
|
||||
or Flags.get(game.save, "EVENT_GOT_BICYCLE") then
|
||||
say(t._BikeShopClerkHowDoYouLikeYourBicycleText, done)
|
||||
return
|
||||
end
|
||||
|
||||
-- .dontHaveBike: IsItemInBag BIKE_VOUCHER
|
||||
if (game.save.inventory.BIKE_VOUCHER or 0) > 0 then
|
||||
say(t._BikeShopClerkOhThatsAVoucherText, function()
|
||||
-- GiveItem's `jr nc, .BagFull`: the voucher is only spent once
|
||||
-- the BICYCLE is actually in the bag
|
||||
if not Bag.add(game.save, "BICYCLE", 1) then
|
||||
say(t._BikeShopBagFullText, done)
|
||||
return
|
||||
end
|
||||
Bag.remove(game.save, "BIKE_VOUCHER", 1)
|
||||
Flags.set(game.save, "EVENT_GOT_BICYCLE")
|
||||
-- BikeShopExchangedVoucherText carries sound_get_key_item; the
|
||||
-- map runs EnableAutoTextBoxDrawing, so the box still waits for
|
||||
-- a button once the jingle has played (auto.wait, #247)
|
||||
say(t._BikeShopExchangedVoucherText, done, {
|
||||
auto = { wait = true, sound = function()
|
||||
return require("src.core.Sound").play(game.data, "Get_Key_Item")
|
||||
end },
|
||||
})
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
-- .dontHaveVoucher: welcome, then the BICYCLE/CANCEL window
|
||||
say(t._BikeShopClerkWelcomeText, function()
|
||||
local pitch = t._BikeShopClerkDoYouLikeItText
|
||||
game.stack:push(BikeShopWindow.new(game, pitch, function(bought)
|
||||
local function comeAgain()
|
||||
say(t._BikeShopComeAgainText, function()
|
||||
game.stack:pop() -- the window, still up under the text
|
||||
done()
|
||||
end)
|
||||
end
|
||||
if bought then
|
||||
-- a million is out of anyone's reach: BikeShopCantAffordText
|
||||
say(t._BikeShopCantAffordText, comeAgain)
|
||||
else
|
||||
comeAgain()
|
||||
end
|
||||
end))
|
||||
-- PrintText BikeShopClerkDoYouLikeItText, then straight into
|
||||
-- HandleMenuInput: the box types out and hands over without
|
||||
-- waiting, leaving the window's copy of the line on screen
|
||||
say(pitch, nil, { auto = { delay = 0 } })
|
||||
end)
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -111,8 +111,9 @@ M.POKEMON_TOWER_5F = {
|
||||
--
|
||||
-- PokemonTower6FDefaultScript starts the RESTLESS SOUL battle with NO
|
||||
-- Silph Scope check at the trigger -- the scope only decides whether the
|
||||
-- battle is disguised (IsGhostBattle -> makeGhost: "too scared to move",
|
||||
-- balls dodged). An earlier version of this port turned the player back
|
||||
-- disguise sticks (IsGhostBattle -> makeGhost: "too scared to move", balls
|
||||
-- dodged) or comes off in the unveil (makeUnveiledGhost, #492). An earlier
|
||||
-- version of this port turned the player back
|
||||
-- without the scope and never opened the battle, which made 6F
|
||||
-- impassable on any route that skips Rocket Hideout; vanilla lets the
|
||||
-- battle open and a POKE_DOLL end it (see wBattleResult below).
|
||||
@@ -133,7 +134,13 @@ M.POKEMON_TOWER_6F = {
|
||||
-- or not the scope revealed it, so the "can't be caught" state rides
|
||||
-- the battle instead of IsGhostBattle alone (#444)
|
||||
battle.noCatch = true
|
||||
if not game.save.inventory.SILPH_SCOPE then
|
||||
-- InitWildBattle enters disguised for the RESTLESS SOUL either way
|
||||
-- (core.asm:6698-6700). The scope does not skip the disguise, it
|
||||
-- buys the unveil PrintBeginningBattleText .isMarowak plays over it
|
||||
-- before the battle proceeds as an ordinary wild one (#492).
|
||||
if game.save.inventory.SILPH_SCOPE then
|
||||
battle:makeUnveiledGhost()
|
||||
else
|
||||
battle:makeGhost()
|
||||
end
|
||||
battle.onFinish = function(result)
|
||||
@@ -144,9 +151,27 @@ M.POKEMON_TOWER_6F = {
|
||||
-- trick, and the speedrun route this bot follows depends on it.
|
||||
if result == "win" or battle.pokeDollEscape then
|
||||
game.save.flags.EVENT_BEAT_GHOST_MAROWAK = true
|
||||
game.stack:push(TextBox.new(game,
|
||||
t._PokemonTower6FSoulWasCalmedText
|
||||
or "The mother's soul\nwas calmed.\012It departed to\nthe afterlife!"))
|
||||
-- PokemonTower6FMarowakDepartedText (scripts/PokemonTower6F.asm)
|
||||
-- is two texts, not one: the CUBONE's-mother line first, then
|
||||
-- PlayCry RESTLESS_SOUL (EQU MAROWAK, constants/pokemon_constants
|
||||
-- .asm:209) + WaitForSoundToFinish + DelayFrames 30 before the
|
||||
-- calmed line; the port dropped the first text and the cry
|
||||
-- (#867). play_cry arms the next show_text, so the cry rides
|
||||
-- the calmed box's open with the button prompt kept, and the
|
||||
-- wait row stands in for the asm's 30-frame gap.
|
||||
local rows = {
|
||||
{ "show_text", t._PokemonTower6FGhostWasCubonesMotherText
|
||||
or "The GHOST was the\nrestless soul of\vCUBONE's mother!" },
|
||||
{ "play_cry", "MAROWAK", true },
|
||||
{ "wait", 30 },
|
||||
{ "show_text", t._PokemonTower6FSoulWasCalmedText
|
||||
or "The mother's soul\nwas calmed.\012It departed to\nthe afterlife!" },
|
||||
}
|
||||
if ow.runner then
|
||||
ow.runner:run(rows)
|
||||
elseif ow.queueScript then
|
||||
ow:queueScript(rows)
|
||||
end
|
||||
elseif result ~= "lose" then
|
||||
-- .did_not_defeat: one simulated step right, off the trigger,
|
||||
-- so fleeing does not leave you standing on a cell that
|
||||
@@ -353,6 +378,38 @@ M.ROCKET_HIDEOUT_B4F = {
|
||||
end))
|
||||
end,
|
||||
|
||||
-- Yellow swaps Rocket1/Rocket2 for Jessie & James and keeps a single
|
||||
-- grunt, spelled ROCKETHIDEOUTB4F_ROCKET / TEXT_ROCKETHIDEOUTB4F_ROCKET
|
||||
-- (pokeyellow/scripts/RocketHideoutB4F.asm), so the Red/Blue key above
|
||||
-- never matched there and the LIFT KEY never dropped (#552, a
|
||||
-- regression of #105). Yellow also moves the drop off the second
|
||||
-- talk: RocketHideoutB4FRocketEndBattleText is text_promptbutton +
|
||||
-- text_asm, so its SetEvent EVENT_ROCKET_DROPPED_LIFT_KEY / ShowObject
|
||||
-- TOGGLE_ROCKET_HIDEOUT_B4F_ITEM_5 run the instant the battle ends.
|
||||
TEXT_ROCKETHIDEOUTB4F_ROCKET = function(game, ow, npc, done)
|
||||
if not ow:trainerDefeated(npc) then
|
||||
ow:engageTrainer(npc, function()
|
||||
-- engageTrainer records the win before it calls back, so this
|
||||
-- is the end-battle text's SetEvent + ShowObject
|
||||
if ow:trainerDefeated(npc)
|
||||
and not game.save.flags.EVENT_ROCKET_DROPPED_LIFT_KEY then
|
||||
game.save.flags.EVENT_ROCKET_DROPPED_LIFT_KEY = true
|
||||
local Commands = require("src.script.Commands")
|
||||
Commands.show_object(
|
||||
{ game = game, save = game.save, overworld = ow },
|
||||
"ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_LIFT_KEY")
|
||||
end
|
||||
done()
|
||||
end)
|
||||
return
|
||||
end
|
||||
-- RocketHideoutB4FRocketAfterBattleText: later talks only reprint
|
||||
local TextBox = require("src.render.TextBox")
|
||||
game.stack:push(TextBox.new(game,
|
||||
game.data.text._RocketHideoutB4FRocketAfterBattleText
|
||||
or "Oh no! I dropped\nthe LIFT KEY!", done))
|
||||
end,
|
||||
|
||||
TEXT_ROCKETHIDEOUTB4F_GIOVANNI = function(game, ow, npc, done)
|
||||
-- Giovanni has no trainer-header row (def_trainers 2); his text_asm
|
||||
-- owns both the engage and the BeatGiovanniScript aftermath.
|
||||
@@ -478,6 +535,14 @@ M.GAME_CORNER = {
|
||||
done()
|
||||
return
|
||||
end
|
||||
-- GameCornerRocketText hands the battle its own loss line through
|
||||
-- SaveEndBattleTextPointers (.BattleEndText ->
|
||||
-- _GameCornerRocketBattleEndText, "Dang!"), and PrintEndBattleText
|
||||
-- prints it ON the battle screen between TrainerDefeatedText and
|
||||
-- MoneyForWinningText (engine/battle/core.asm TrainerBattleVictory).
|
||||
-- He is a text_asm trainer with no def_trainers header, so there is no
|
||||
-- header.won for engageTrainer to find and the line has to be handed
|
||||
-- over here or it never shows at all (#862).
|
||||
ow:engageTrainer(npc, function()
|
||||
if not ow:trainerDefeated(npc) then
|
||||
done()
|
||||
@@ -488,160 +553,329 @@ M.GAME_CORNER = {
|
||||
game.data.text._GameCornerRocketAfterBattleText
|
||||
or "Our hideout might\nbe discovered! I\nbetter tell BOSS!",
|
||||
function()
|
||||
-- #198: GameCornerRocketExitScript (scripts/GameCorner.asm)
|
||||
-- ApplyMovementData walks the grunt one tile UP into the poster
|
||||
-- (the hideout's secret entrance at 9,4) before HideObject, so
|
||||
-- he leaves the floor rather than popping out of existence on
|
||||
-- (9,5). scriptMove locks player input (#scriptMoves>0) and
|
||||
-- ignores collision, so we despawn + unfreeze (done) only once
|
||||
-- the step lands.
|
||||
ow:scriptMove(npc, "up", 1, function()
|
||||
hideRocket()
|
||||
done()
|
||||
end)
|
||||
-- #198/#862: GameCornerRocketBattleScript (scripts/GameCorner.asm)
|
||||
-- picks the exit walk from where the player is standing, because
|
||||
-- the grunt on (9,5) has to get past him: wYCoord == 6 (talked to
|
||||
-- from the south) or wXCoord == 8 (from the west) leaves the row
|
||||
-- clear and takes GameCornerMovement_Rocket_WalkDirect, five steps
|
||||
-- RIGHT; otherwise the player is east of him on (10,5) and
|
||||
-- GameCornerMovement_Rocket_WalkAroundPlayer steps DOWN, right, UP
|
||||
-- and right again to go AROUND him. pokeyellow's copy of the
|
||||
-- around-path takes one extra RIGHT on the lower row before coming
|
||||
-- back up (it also has to clear Pikachu); both versions end on
|
||||
-- (15,5). He never steps UP: (9,4) is the poster wall, which is
|
||||
-- where the old single UP step sent him.
|
||||
local px = ow.player and ow.player.cellX
|
||||
local py = ow.player and ow.player.cellY
|
||||
local path
|
||||
if py == 6 or px == 8 then
|
||||
path = { { "right", 5 } }
|
||||
elseif require("src.core.GameVersion").isYellow() then
|
||||
path = { { "down", 1 }, { "right", 3 }, { "up", 1 }, { "right", 3 } }
|
||||
else
|
||||
path = { { "down", 1 }, { "right", 2 }, { "up", 1 }, { "right", 4 } }
|
||||
end
|
||||
-- GameCornerRocketExitScript only HideObjects him once
|
||||
-- BIT_SCRIPTED_NPC_MOVEMENT clears, i.e. after the last step.
|
||||
-- scriptMove locks player input (#scriptMoves>0) and ignores
|
||||
-- collision, so the despawn + unfreeze (done) ride the final step.
|
||||
local function step(i)
|
||||
if i > #path then
|
||||
hideRocket()
|
||||
done()
|
||||
return
|
||||
end
|
||||
ow:scriptMove(npc, path[i][1], path[i][2],
|
||||
function() step(i + 1) end)
|
||||
end
|
||||
step(1)
|
||||
end))
|
||||
end)
|
||||
end, game.data.text._GameCornerRocketBattleEndText or "Dang!")
|
||||
end,
|
||||
-- GameCornerClerk1Text (scripts/GameCorner.asm): the offer, a
|
||||
-- YesNoChoice, then ¥1000 for 50 coins. Yellow drops the "1" from the
|
||||
-- object const and from every one of his text labels
|
||||
-- (GameCornerClerkText, pokeyellow/scripts/GameCorner.asm) with an
|
||||
-- identical body, so each line resolves under both spellings and the
|
||||
-- handler is bound to both text ids just below (#552).
|
||||
TEXT_GAMECORNER_CLERK1 = function(game, ow, npc, done)
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
||||
local Font = require("src.render.Font")
|
||||
local Strings = require("src.core.Strings")
|
||||
local t = game.data.text
|
||||
local function line(suffix, fallback)
|
||||
return t["_GameCornerClerk1" .. suffix]
|
||||
or t["_GameCornerClerk" .. suffix]
|
||||
or fallback
|
||||
end
|
||||
-- GameCornerDrawCoinBox (scripts/GameCorner.asm; pokeyellow's copy is
|
||||
-- identical): TextBoxBorder at hlcoord 11,0 with b=5 c=7, a 9x7-tile
|
||||
-- window in the top right holding MONEY at (12,2) over the amount on
|
||||
-- row 3 and COIN at (12,4) over the count on row 5. Both
|
||||
-- PrintBCDNumber calls pass LEADING_ZEROES, whose bit 7 SUPPRESSES
|
||||
-- leading zeroes (home/print_bcd.asm), and neither passes LEFT_ALIGN,
|
||||
-- so both numbers read plain and right-aligned against the inner edge
|
||||
-- at column 18. The asm draws the box before the offer and redraws it
|
||||
-- after the purchase, so it stands for the whole exchange: a draw-only
|
||||
-- state under the dialogue gets that lifetime, since StateStack draws
|
||||
-- every state above the last opaque one and updates only the top
|
||||
-- (src/core/StateStack.lua), and reading save each frame is the
|
||||
-- redraw (#624).
|
||||
local coinBox = { draw = function()
|
||||
Font.drawBox(11, 0, 9, 7)
|
||||
love.graphics.setColor(0, 0, 0, 1)
|
||||
Font.draw(Strings("MONEY"), 96, 16)
|
||||
local money = ("¥%d"):format(game.save.money or 0)
|
||||
Font.draw(money, 152 - Font.width(money), 24)
|
||||
Font.draw(Strings("COIN"), 96, 32)
|
||||
local coins = ("%d"):format(game.save.coins or 0)
|
||||
Font.draw(coins, 152 - Font.width(coins), 40)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end }
|
||||
game.stack:push(coinBox)
|
||||
-- Every branch below finishes here. A TextBox pops itself before its
|
||||
-- onDone runs, so the coin box is top of the stack again by then and
|
||||
-- this pop takes it down, never someone else's state.
|
||||
local function finish()
|
||||
game.stack:pop()
|
||||
done()
|
||||
end
|
||||
-- YesNoChoice is called with the offer still printed, so the prompt
|
||||
-- has to ride the open text box (opts.choice) instead of being pushed
|
||||
-- after it closes, which is what made the question vanish (#624).
|
||||
game.stack:push(TextBox.new(game,
|
||||
(t._GameCornerClerk1DoYouNeedSomeGameCoinsText
|
||||
or "Do you need some\ngame coins?\f¥1000 for 50."), function()
|
||||
game.stack:push(ChoiceBox.new(game, function(yes)
|
||||
line("DoYouNeedSomeGameCoinsText",
|
||||
"Do you need some\ngame coins?\f¥1000 for 50."),
|
||||
nil, { choice = function(yes)
|
||||
if not yes then
|
||||
game.stack:push(TextBox.new(game,
|
||||
t._GameCornerClerk1PleaseComePlaySometimeText
|
||||
or "No? Please come\nplay sometime!", done))
|
||||
line("PleaseComePlaySometimeText",
|
||||
"No? Please come\nplay sometime!"), finish))
|
||||
return
|
||||
end
|
||||
-- scripts/GameCorner.asm GameCornerClerk1Text: coins need
|
||||
-- the COIN CASE and room for at least 9 coins (Has9990Coins)
|
||||
if not game.save.inventory.COIN_CASE then
|
||||
game.stack:push(TextBox.new(game,
|
||||
t._GameCornerClerk1DontHaveCoinCaseText
|
||||
or "You don't have a\nCOIN CASE!", done))
|
||||
line("DontHaveCoinCaseText",
|
||||
"You don't have a\nCOIN CASE!"), finish))
|
||||
return
|
||||
end
|
||||
if (game.save.coins or 0) >= 9990 then
|
||||
game.stack:push(TextBox.new(game,
|
||||
t._GameCornerClerk1CoinCaseIsFullText
|
||||
or "Oops! Your COIN\nCASE is full.", done))
|
||||
line("CoinCaseIsFullText",
|
||||
"Oops! Your COIN\nCASE is full."), finish))
|
||||
return
|
||||
end
|
||||
if game.save.money < 1000 then
|
||||
game.stack:push(TextBox.new(game,
|
||||
t._GameCornerClerk1CantAffordTheCoinsText
|
||||
or "You can't afford\nthe coins!", done))
|
||||
line("CantAffordTheCoinsText",
|
||||
"You can't afford\nthe coins!"), finish))
|
||||
return
|
||||
end
|
||||
game.save.money = game.save.money - 1000
|
||||
game.save.coins = math.min(9999, (game.save.coins or 0) + 50)
|
||||
-- the thanks text is the plain _GameCornerClerk1ThanksHereAre50-
|
||||
-- CoinsText; the new count belongs in the coin box the asm
|
||||
-- redraws here, not appended to the line (#624)
|
||||
game.stack:push(TextBox.new(game,
|
||||
(t._GameCornerClerk1ThanksHereAre50CoinsText
|
||||
or "Thanks! Here are\nyour 50 coins!")
|
||||
.. ("\fCOINS: %d"):format(game.save.coins), done))
|
||||
end))
|
||||
end))
|
||||
line("ThanksHereAre50CoinsText",
|
||||
"Thanks! Here are\nyour 50 coins!"), finish))
|
||||
end }))
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
-- Yellow's coin clerk is GAMECORNER_CLERK / TEXT_GAMECORNER_CLERK where Red
|
||||
-- and Blue spell him CLERK1, and the two text_asm bodies are identical, so
|
||||
-- one handler answers both ids. Without the alias the Yellow clerk fell
|
||||
-- through to his extracted offer line with no yes/no box behind it (#552).
|
||||
M.GAME_CORNER.talk.TEXT_GAMECORNER_CLERK =
|
||||
M.GAME_CORNER.talk.TEXT_GAMECORNER_CLERK1
|
||||
|
||||
-- Game Corner prize lists (data/events/prizes.asm, prize_mon_levels.asm).
|
||||
-- The six mon prizes differ between Red and Blue; the three TM prizes are
|
||||
-- identical, so they are shared and appended to each version's mon list.
|
||||
-- Each counter owns ONE window of three prizes, not the whole catalogue:
|
||||
-- GetPrizeMenuId (engine/events/prize_menu.asm) subtracts
|
||||
-- TEXT_GAMECORNERPRIZEROOM_PRIZE_VENDOR_1 from hTextID and indexes
|
||||
-- PrizeDifferentMenuPtrs with the result, so vendor 1 sells
|
||||
-- PrizeMenuMon1Entries, vendor 2 PrizeMenuMon2Entries and vendor 3
|
||||
-- PrizeMenuTMsEntries (#623). The mon windows and their levels differ per
|
||||
-- version; the TM window is identical in all three, so it is shared.
|
||||
local PRIZE_TMS = {
|
||||
{ kind = "item", item = "TM_DRAGON_RAGE", cost = 3300 },
|
||||
{ kind = "item", item = "TM_HYPER_BEAM", cost = 5500 },
|
||||
{ kind = "item", item = "TM_SUBSTITUTE", cost = 7700 },
|
||||
}
|
||||
local RED_PRIZES = {
|
||||
{ kind = "mon", species = "ABRA", level = 9, cost = 180 },
|
||||
{ kind = "mon", species = "CLEFAIRY", level = 8, cost = 500 },
|
||||
{ kind = "mon", species = "NIDORINA", level = 17, cost = 1200 },
|
||||
{ kind = "mon", species = "DRATINI", level = 18, cost = 2800 },
|
||||
{ kind = "mon", species = "SCYTHER", level = 25, cost = 5500 },
|
||||
{ kind = "mon", species = "PORYGON", level = 26, cost = 9999 },
|
||||
PRIZE_TMS[1], PRIZE_TMS[2], PRIZE_TMS[3],
|
||||
local RED_PRIZE_WINDOWS = {
|
||||
{
|
||||
{ kind = "mon", species = "ABRA", level = 9, cost = 180 },
|
||||
{ kind = "mon", species = "CLEFAIRY", level = 8, cost = 500 },
|
||||
{ kind = "mon", species = "NIDORINA", level = 17, cost = 1200 },
|
||||
},
|
||||
{
|
||||
{ kind = "mon", species = "DRATINI", level = 18, cost = 2800 },
|
||||
{ kind = "mon", species = "SCYTHER", level = 25, cost = 5500 },
|
||||
{ kind = "mon", species = "PORYGON", level = 26, cost = 9999 },
|
||||
},
|
||||
PRIZE_TMS,
|
||||
}
|
||||
local BLUE_PRIZES = {
|
||||
{ kind = "mon", species = "ABRA", level = 6, cost = 120 },
|
||||
{ kind = "mon", species = "CLEFAIRY", level = 12, cost = 750 },
|
||||
{ kind = "mon", species = "NIDORINO", level = 17, cost = 1200 },
|
||||
{ kind = "mon", species = "PINSIR", level = 20, cost = 2500 },
|
||||
{ kind = "mon", species = "DRATINI", level = 24, cost = 4600 },
|
||||
{ kind = "mon", species = "PORYGON", level = 18, cost = 6500 },
|
||||
PRIZE_TMS[1], PRIZE_TMS[2], PRIZE_TMS[3],
|
||||
local BLUE_PRIZE_WINDOWS = {
|
||||
{
|
||||
{ kind = "mon", species = "ABRA", level = 6, cost = 120 },
|
||||
{ kind = "mon", species = "CLEFAIRY", level = 12, cost = 750 },
|
||||
{ kind = "mon", species = "NIDORINO", level = 17, cost = 1200 },
|
||||
},
|
||||
{
|
||||
{ kind = "mon", species = "PINSIR", level = 20, cost = 2500 },
|
||||
{ kind = "mon", species = "DRATINI", level = 24, cost = 4600 },
|
||||
{ kind = "mon", species = "PORYGON", level = 18, cost = 6500 },
|
||||
},
|
||||
PRIZE_TMS,
|
||||
}
|
||||
-- Yellow keeps the three windows but restocks both mon counters
|
||||
-- (pokeyellow/data/events/prizes.asm, prize_mon_levels.asm)
|
||||
local YELLOW_PRIZE_WINDOWS = {
|
||||
{
|
||||
{ kind = "mon", species = "ABRA", level = 15, cost = 230 },
|
||||
{ kind = "mon", species = "VULPIX", level = 18, cost = 1000 },
|
||||
{ kind = "mon", species = "WIGGLYTUFF", level = 22, cost = 2680 },
|
||||
},
|
||||
{
|
||||
{ kind = "mon", species = "SCYTHER", level = 30, cost = 6500 },
|
||||
{ kind = "mon", species = "PINSIR", level = 30, cost = 6500 },
|
||||
{ kind = "mon", species = "PORYGON", level = 26, cost = 9999 },
|
||||
},
|
||||
PRIZE_TMS,
|
||||
}
|
||||
|
||||
local function activePrizes()
|
||||
return require("src.core.GameVersion").isBlue() and BLUE_PRIZES or RED_PRIZES
|
||||
local function prizeWindow(n)
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local windows = RED_PRIZE_WINDOWS
|
||||
if GameVersion.isBlue() then
|
||||
windows = BLUE_PRIZE_WINDOWS
|
||||
elseif GameVersion.isYellow() then
|
||||
windows = YELLOW_PRIZE_WINDOWS
|
||||
end
|
||||
return windows[n]
|
||||
end
|
||||
|
||||
-- Prize counters (engine/menus/prize_menu.asm CeladonPrizeMenu; the prize
|
||||
-- Prize counters (engine/events/prize_menu.asm CeladonPrizeMenu; the prize
|
||||
-- list itself is data/events/prizes.asm, prize_mon_levels.asm). Gen1 gates
|
||||
-- the prize window on the COIN CASE: it does IsItemInBag COIN_CASE first, and
|
||||
-- with no case prints RequireCoinCaseText and returns without ever opening a
|
||||
-- window; only with the case does it print ExchangeCoinsForPrizesText and then
|
||||
-- show the prizes. #194: the port used to open the window unconditionally and
|
||||
-- skip both text boxes.
|
||||
local function prizeCounter(game, ow, npc, done)
|
||||
local ListMenu = require("src.ui.ListMenu")
|
||||
local Commands = require("src.script.Commands")
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local t = game.data.text
|
||||
-- IsItemInBag COIN_CASE: without the case, deny and open no window
|
||||
-- (COIN_CASE is a numeric count in save.inventory, nil when absent).
|
||||
if not game.save.inventory.COIN_CASE then
|
||||
-- skip both text boxes. wMaxMenuItem is 3, i.e. this window's three prizes
|
||||
-- plus the NO THANKS row, and HandlePrizeChoice confirms the pick with
|
||||
-- SoYouWantPrizeText + YesNoChoice before any coins move; every branch then
|
||||
-- rets out of CeladonPrizeMenu, so one transaction ends the conversation and
|
||||
-- buying again means talking to the counter again (#623).
|
||||
local function prizeCounter(window)
|
||||
return function(game, ow, npc, done)
|
||||
local ListMenu = require("src.ui.ListMenu")
|
||||
local Commands = require("src.script.Commands")
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local t = game.data.text
|
||||
-- IsItemInBag COIN_CASE: without the case, deny and open no window
|
||||
-- (COIN_CASE is a numeric count in save.inventory, nil when absent).
|
||||
if not game.save.inventory.COIN_CASE then
|
||||
game.stack:push(TextBox.new(game,
|
||||
t._RequireCoinCaseText or "A COIN CASE is\nrequired!", done))
|
||||
return
|
||||
end
|
||||
-- ExchangeCoinsForPrizesText plays before the prize window opens.
|
||||
game.stack:push(TextBox.new(game,
|
||||
t._RequireCoinCaseText or "A COIN CASE is\nrequired!", done))
|
||||
return
|
||||
end
|
||||
-- ExchangeCoinsForPrizesText plays before the prize window opens.
|
||||
game.stack:push(TextBox.new(game,
|
||||
t._ExchangeCoinsForPrizesText or "We exchange your\ncoins for prizes.",
|
||||
function()
|
||||
local items = {}
|
||||
for _, p in ipairs(activePrizes()) do
|
||||
local label
|
||||
if p.kind == "mon" then
|
||||
label = ("%s L%d"):format(game.data.pokemon[p.species].name, p.level)
|
||||
else
|
||||
label = game.data.items[p.item].name
|
||||
t._ExchangeCoinsForPrizesText or "We exchange your\ncoins for prizes.",
|
||||
function()
|
||||
local items = {}
|
||||
for _, p in ipairs(prizeWindow(window)) do
|
||||
local label
|
||||
if p.kind == "mon" then
|
||||
label = ("%s L%d"):format(game.data.pokemon[p.species].name, p.level)
|
||||
else
|
||||
label = game.data.items[p.item].name
|
||||
end
|
||||
table.insert(items,
|
||||
{ label = label, right = tostring(p.cost), value = p })
|
||||
end
|
||||
table.insert(items,
|
||||
{ label = label, right = tostring(p.cost), value = p })
|
||||
end
|
||||
local list
|
||||
list = ListMenu.new(game, "PRIZES (COINS)", items, {
|
||||
footer = ("COINS %d"):format(game.save.coins or 0),
|
||||
onChoose = function(item)
|
||||
local p = item.value
|
||||
-- NoThanksText (data/events/prizes.asm) sits under the three prizes
|
||||
table.insert(items, { label = "NO THANKS" })
|
||||
local list
|
||||
-- close the window first: every ending in HandlePrizeChoice leaves
|
||||
-- the menu for good, and the closing line belongs over the map
|
||||
local function finish(msg)
|
||||
list:close()
|
||||
game.stack:push(TextBox.new(game, msg, done))
|
||||
end
|
||||
local function buy(p)
|
||||
if (game.save.coins or 0) < p.cost then
|
||||
list.footer = "Not enough coins!"
|
||||
finish(t._SorryNeedMoreCoinsText or "Sorry, you need\nmore coins.")
|
||||
return
|
||||
end
|
||||
-- HasEnoughCoins passed, so hand the prize over first and only
|
||||
-- subtract once it landed: the asm rets before .subtractCoins when
|
||||
-- the bag is full, or when both the party and every box are full
|
||||
local roomless = t._OopsYouDontHaveEnoughRoomText
|
||||
or "Oops! You don't\nhave enough room."
|
||||
if p.kind == "mon" then
|
||||
-- no runner here, so give_pokemon reports through ctx.lastCheck
|
||||
-- and skips the AskName prompt (Commands.give_pokemon)
|
||||
local ctx = { save = game.save, game = game }
|
||||
Commands.give_pokemon(ctx, p.species, p.level)
|
||||
if not ctx.lastCheck then
|
||||
finish(roomless)
|
||||
return
|
||||
end
|
||||
elseif not require("src.inventory.Bag").add(
|
||||
game.save, p.item, 1, game.data) then
|
||||
finish(roomless)
|
||||
return
|
||||
end
|
||||
game.save.coins = game.save.coins - p.cost
|
||||
if p.kind == "mon" then
|
||||
Commands.give_pokemon({ save = game.save, game = game },
|
||||
p.species, p.level)
|
||||
else
|
||||
game.save.inventory[p.item] = (game.save.inventory[p.item] or 0) + 1
|
||||
end
|
||||
list.footer = ("Got it! COINS %d"):format(game.save.coins)
|
||||
end,
|
||||
onCancel = done,
|
||||
})
|
||||
game.stack:push(list)
|
||||
end))
|
||||
-- no thank-you line: HereYouGoText is unreferenced in the asm,
|
||||
-- which just redraws the coin box (PrintPrizePrice) and returns
|
||||
list:close()
|
||||
done()
|
||||
end
|
||||
list = ListMenu.new(game, "PRIZES (COINS)", items, {
|
||||
footer = ("COINS %d"):format(game.save.coins or 0),
|
||||
onChoose = function(item)
|
||||
local p = item.value
|
||||
if not p then -- NO THANKS is the B exit (cp 3 -> .noChoice)
|
||||
list:close()
|
||||
done()
|
||||
return
|
||||
end
|
||||
local name = (p.kind == "mon")
|
||||
and game.data.pokemon[p.species].name
|
||||
or game.data.items[p.item].name
|
||||
-- SoYouWantPrizeText names the prize out of wNameBuffer, which
|
||||
-- is not one of TextBox's RAM tokens, so fill it in here
|
||||
local ask = (t._SoYouWantPrizeText
|
||||
or "So, you want\n{RAM:wNameBuffer}?")
|
||||
:gsub("{RAM:wNameBuffer}", name)
|
||||
game.stack:push(TextBox.new(game, ask, nil, {
|
||||
choice = function(yes)
|
||||
if not yes then
|
||||
finish(t._OhFineThenText or "Oh, fine then.")
|
||||
return
|
||||
end
|
||||
buy(p)
|
||||
end,
|
||||
}))
|
||||
end,
|
||||
onCancel = done,
|
||||
})
|
||||
game.stack:push(list)
|
||||
end))
|
||||
end
|
||||
end
|
||||
|
||||
M.GAME_CORNER_PRIZE_ROOM = {
|
||||
talk = { -- the three prize counters are bg events
|
||||
TEXT_GAMECORNERPRIZEROOM_PRIZE_VENDOR_1 = prizeCounter,
|
||||
TEXT_GAMECORNERPRIZEROOM_PRIZE_VENDOR_2 = prizeCounter,
|
||||
TEXT_GAMECORNERPRIZEROOM_PRIZE_VENDOR_3 = prizeCounter,
|
||||
talk = { -- the three prize counters are bg events, one window each
|
||||
TEXT_GAMECORNERPRIZEROOM_PRIZE_VENDOR_1 = prizeCounter(1),
|
||||
TEXT_GAMECORNERPRIZEROOM_PRIZE_VENDOR_2 = prizeCounter(2),
|
||||
TEXT_GAMECORNERPRIZEROOM_PRIZE_VENDOR_3 = prizeCounter(3),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -13,16 +13,30 @@ local function push(game, s, done)
|
||||
game.stack:push(TextBox.new(game, s, done))
|
||||
end
|
||||
|
||||
-- The question stays on screen under the YES/NO menu. The dojo prize
|
||||
-- balls are the clearest case: FightingDojoHitmonleePokeBallText
|
||||
-- (scripts/FightingDojo.asm) is `call PrintText` on a text_end string --
|
||||
-- no prompt, so no WaitForTextScrollButtonPress -- immediately followed
|
||||
-- by `call YesNoChoice`, and InitYesNoTextBoxParameters
|
||||
-- (engine/menus/text_box.asm) puts the menu above the dialogue box
|
||||
-- rather than replacing it. Ride TextBox's opts.choice, the same as
|
||||
-- Commands.ask, instead of popping the box with an A press and leaving a
|
||||
-- bare ChoiceBox over the overworld (#854).
|
||||
local function ask(game, s, cb)
|
||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
||||
push(game, s, function() game.stack:push(ChoiceBox.new(game, cb)) end)
|
||||
local TextBox = require("src.render.TextBox")
|
||||
game.stack:push(TextBox.new(game, s, nil, { choice = cb }))
|
||||
end
|
||||
|
||||
-- fill the extracted text placeholders ({NUM:...}, {RAM:...}, {PLAYER})
|
||||
-- fill text placeholders; key on the hram/wram symbol first, since one
|
||||
-- string can carry two different NUM slots (#1006)
|
||||
local function fill(s, subs)
|
||||
s = s:gsub("{PLAYER}", subs.player or "")
|
||||
s = s:gsub("{NUM:[^}]*}", function() return tostring(subs.num or "") end)
|
||||
s = s:gsub("{RAM:[^}]*}", function() return subs.ram or "" end)
|
||||
s = s:gsub("{NUM:([%w_]*)[^}]*}", function(name)
|
||||
return tostring(subs[name] or subs.num or "")
|
||||
end)
|
||||
s = s:gsub("{RAM:([%w_]*)[^}]*}", function(name)
|
||||
return subs[name] or subs.ram or ""
|
||||
end)
|
||||
return s
|
||||
end
|
||||
|
||||
@@ -73,9 +87,12 @@ local function oaksAide(threshold, itemId, repeatText)
|
||||
{ ram = itemName, player = game.save.player.name }), done)
|
||||
end)
|
||||
else
|
||||
-- .notEnoughOwnedMons prints owned then requirement, two counts
|
||||
push(game, fill(t._OaksAideUhOhText or
|
||||
"You have only\ncaught {NUM:}!",
|
||||
{ num = owned, ram = itemName }), done)
|
||||
{ num = owned, ram = itemName,
|
||||
hOaksAideNumMonsOwned = owned,
|
||||
hOaksAideRequirement = threshold }), done)
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -155,19 +172,26 @@ local function dojoBall(species, ownBall, otherBall, askKey)
|
||||
push(game, "You'll have to\nbeat the master\nfirst!", done)
|
||||
return
|
||||
end
|
||||
ask(game, t[askKey] or ("You want\n" .. species .. "?"), function(yes)
|
||||
if not yes then done() return end
|
||||
flags["EVENT_GOT_" .. species] = true
|
||||
flags.EVENT_DEFEATED_FIGHTING_DOJO = true
|
||||
local Commands = require("src.script.Commands")
|
||||
local ctx = { save = game.save, game = game, overworld = ow }
|
||||
Commands.give_pokemon(ctx, species, 30)
|
||||
-- Hide ONLY the chosen ball; the other stays (FightingDojo.asm hides
|
||||
-- just the picked object's index) and routes to the greedy line above
|
||||
-- when talked to (#197).
|
||||
Commands.hide_object(ctx, "FIGHTING_DOJO", ownBall)
|
||||
push(game, ("%s got\n%s!"):format(game.save.player.name, species), done)
|
||||
end)
|
||||
-- Examining a ball shows that species' POKéDEX entry first
|
||||
-- (DisplayPokedex in FightingDojo.asm, which also marks it seen),
|
||||
-- then the yes/no take-it prompt (#853).
|
||||
local Commands = require("src.script.Commands")
|
||||
local ctx = { save = game.save, game = game, overworld = ow }
|
||||
Commands.mark_seen(ctx, species)
|
||||
local DexEntryMenu = require("src.ui.DexEntryMenu")
|
||||
game.stack:push(DexEntryMenu.new(game, species, function()
|
||||
ask(game, t[askKey] or ("You want\n" .. species .. "?"), function(yes)
|
||||
if not yes then done() return end
|
||||
flags["EVENT_GOT_" .. species] = true
|
||||
flags.EVENT_DEFEATED_FIGHTING_DOJO = true
|
||||
Commands.give_pokemon(ctx, species, 30)
|
||||
-- Hide ONLY the chosen ball; the other stays (FightingDojo.asm hides
|
||||
-- just the picked object's index) and routes to the greedy line above
|
||||
-- when talked to (#197).
|
||||
Commands.hide_object(ctx, "FIGHTING_DOJO", ownBall)
|
||||
push(game, ("%s got\n%s!"):format(game.save.player.name, species), done)
|
||||
end)
|
||||
end))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -229,27 +253,32 @@ M.FIGHTING_DOJO = {
|
||||
|
||||
M.SILPH_CO_7F = {
|
||||
talk = {
|
||||
TEXT_SILPHCO7F_SILPH_WORKER_M1 = function(game, ow, npc, done)
|
||||
local t = text(game)
|
||||
if game.save.flags.EVENT_GOT_LAPRAS then
|
||||
push(game, t._SilphCo7FSilphWorkerM1LaprasDescriptionText
|
||||
or "How is LAPRAS\ndoing?", done)
|
||||
return
|
||||
end
|
||||
push(game, t._SilphCo7FSilphWorkerM1ThankYouText
|
||||
or "Thank you for\nsaving us!\fI want you to\nhave this LAPRAS!",
|
||||
function()
|
||||
game.save.flags.EVENT_GOT_LAPRAS = true
|
||||
local Commands = require("src.script.Commands")
|
||||
Commands.give_pokemon({ save = game.save, game = game, overworld = ow },
|
||||
"LAPRAS", 15)
|
||||
push(game, ("%s got\nLAPRAS!"):format(game.save.player.name),
|
||||
function()
|
||||
push(game, t._SilphCo7FSilphWorkerM1LaprasDescriptionText
|
||||
or "It's a good\nswimmer!", done)
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
-- command rows, not a Lua handler: give_pokemon needs a runner to AskName (#1049)
|
||||
TEXT_SILPHCO7F_SILPH_WORKER_M1 = {
|
||||
{ "face_player" },
|
||||
{ "check_flag", "EVENT_GOT_LAPRAS" },
|
||||
{ "jump_if_true", "has_lapras" },
|
||||
{ "show_text", "_SilphCo7FSilphWorkerM1HaveThisPokemonText" },
|
||||
{ "give_pokemon", "LAPRAS", 15 },
|
||||
{ "jump_if_false", "box_full" },
|
||||
-- flag ahead of the jingle, like the Celadon EEVEE (#426)
|
||||
{ "set_flag", "EVENT_GOT_LAPRAS" },
|
||||
{ "play_sound", "Get_Item1" },
|
||||
{ "show_text", "_GotMonText", { RAM = "LAPRAS" } },
|
||||
{ "show_text", "_SilphCo7FSilphWorkerM1LaprasDescriptionText" },
|
||||
{ "jump", "end" },
|
||||
{ "label", "box_full" },
|
||||
{ "show_text", "_BoxIsFullText" },
|
||||
{ "jump", "end" },
|
||||
-- SilphCo7F.asm .saved_silph gates the thanks on Giovanni
|
||||
{ "label", "has_lapras" },
|
||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||
{ "jump_if_true", "saved" },
|
||||
{ "show_text", "_SilphCo7FSilphWorkerM1IsOurPresidentOkText" },
|
||||
{ "jump", "end" },
|
||||
{ "label", "saved" },
|
||||
{ "show_text", "_SilphCo7FSilphWorkerM1SavedText" },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ local function gift(opts)
|
||||
end
|
||||
end)
|
||||
end
|
||||
if opts.pre then say(opts.pre, "", give) else give() end
|
||||
if opts.pre then say(opts.pre, opts.preFallback or "", give) else give() end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -118,11 +118,21 @@ M.CINNABAR_LAB_METRONOME_ROOM = {
|
||||
},
|
||||
}
|
||||
|
||||
-- TM42 Dream Eater (scripts/ViridianCity.asm, the fisher; no pre text)
|
||||
-- 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.
|
||||
M.VIRIDIAN_CITY = {
|
||||
talk = {
|
||||
TEXT_VIRIDIANCITY_FISHER = gift({
|
||||
flag = "EVENT_GOT_TM42", item = "TM_DREAM_EATER",
|
||||
pre = "ViridianCityFisherYouCanHaveThisText",
|
||||
preFallback = "Yawn!\nI must have dozed\voff in the sun."
|
||||
.. "\fI had this dream\nabout a DROWZEE\veating my dream."
|
||||
.. "\vWhat's this?\vWhere did this TM\vcome from?"
|
||||
.. "\fThis is spooky!\nHere, you can\vhave this TM.",
|
||||
received = "_ViridianCityFisherReceivedTM42Text",
|
||||
explain = "_ViridianCityFisherTM42ExplanationText",
|
||||
noRoom = "_ViridianCityFisherTM42NoRoomText",
|
||||
@@ -176,6 +186,20 @@ M.ROUTE_18_GATE_2F = {
|
||||
{ "face_player" },
|
||||
{ "trade", 6, "EVENT_TRADED_SLOWBRO_FOR_LICKITUNG" }, -- MARC
|
||||
},
|
||||
-- Yellow replaces the youngster with a cook trading SPIKE
|
||||
-- (TANGELA -> PARASECT): pokeyellow/scripts/Route18Gate2F.asm
|
||||
-- Route18Gate2FCookText runs TRADE_FOR_SPIKE, index 6 in the Yellow
|
||||
-- TradeMons table that Data:applyVersionedFieldData swaps in. Red
|
||||
-- maps have no COOK object here and Yellow maps have no YOUNGSTER,
|
||||
-- so each version only ever fires its own row (#651). Both rows
|
||||
-- share the Red-flavoured done flag on purpose: a .sav tracks
|
||||
-- "trade slot 6 completed" in one wCompletedInGameTradeFlags bit
|
||||
-- either version reads, and the save codec maps that bit to this
|
||||
-- flag name (src/save_convert/GenSave.lua EXTRA_FLAG_BITS).
|
||||
TEXT_ROUTE18GATE2F_COOK = {
|
||||
{ "face_player" },
|
||||
{ "trade", 6, "EVENT_TRADED_SLOWBRO_FOR_LICKITUNG" }, -- SPIKE (Yellow)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,13 @@ local function push(game, s, done)
|
||||
game.stack:push(TextBox.new(game, s, done))
|
||||
end
|
||||
|
||||
-- PrintText on a text_end string returns with the box still drawn and
|
||||
-- YesNoChoice then draws the menu above it (InitYesNoTextBoxParameters,
|
||||
-- engine/menus/text_box.asm); no A press clears the question first. Ride
|
||||
-- TextBox's opts.choice, the same as Commands.ask (#854).
|
||||
local function ask(game, s, cb)
|
||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
||||
push(game, s, function() game.stack:push(ChoiceBox.new(game, cb)) end)
|
||||
local TextBox = require("src.render.TextBox")
|
||||
game.stack:push(TextBox.new(game, s, nil, { choice = cb }))
|
||||
end
|
||||
|
||||
-- -------------------------------------------------------------------
|
||||
@@ -118,6 +122,7 @@ local MANSION_HOLES = {
|
||||
M.POKEMON_MANSION_3F.onStep = function(game, ow, x, y)
|
||||
for _, h in ipairs(MANSION_HOLES) do
|
||||
if x == h[1] and y == h[2] then
|
||||
require("src.core.Sound").play(game.data, "Faint_Fall")
|
||||
ow:startWarpTo(h[3], h[4], h[5], ow.player.facing)
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -18,6 +18,16 @@
|
||||
-- script). Leaders are not def_trainers entries, so engageTrainer has
|
||||
-- no header.won -- checkVictoryRewards shows this chain instead of a
|
||||
-- synthetic "received badge/TM" stub.
|
||||
--
|
||||
-- Gym entries split the TM hand-over out of `dialogue`, mirroring the
|
||||
-- originals' GiveItem check (`call GiveItem` / `jr nc, .BagFull`):
|
||||
-- `tmPre` is the ReceiveTM script's lead-in (badge info / "Wait! Take
|
||||
-- this!"), shown at the victory and again when a beaten leader retries
|
||||
-- the hand-over; `tmDialogue` shows only when the TM actually goes in
|
||||
-- the bag; `noRoom` is the "make room" line shown instead when the bag
|
||||
-- is full; `gotFlag` (pokered's EVENT_GOT_TM*) is set only on a
|
||||
-- successful give, which is what makes the leader's talk script retry
|
||||
-- later (gyms.lua).
|
||||
|
||||
local function range(prefix, first, last)
|
||||
local t = {}
|
||||
@@ -33,6 +43,8 @@ return {
|
||||
-- escort NPC and the first Route 22 rival stay gone after the badge.
|
||||
["OPP_BROCK#1"] = { badge = "BOULDERBADGE", flag = "EVENT_BEAT_BROCK",
|
||||
item = "TM_BIDE",
|
||||
gotFlag = "EVENT_GOT_TM34",
|
||||
noRoom = "_PewterGymTM34NoRoomText",
|
||||
deactivate = { "EVENT_BEAT_PEWTER_GYM_TRAINER_0" },
|
||||
hide = {
|
||||
{ "PEWTER_CITY", "PEWTERCITY_YOUNGSTER" },
|
||||
@@ -41,75 +53,113 @@ return {
|
||||
dialogue = {
|
||||
"_PewterGymBrockReceivedBoulderBadgeText",
|
||||
"_PewterGymBrockBoulderBadgeInfoText",
|
||||
"_PewterGymBrockWaitTakeThisText",
|
||||
},
|
||||
tmPre = { "_PewterGymBrockWaitTakeThisText" },
|
||||
tmDialogue = {
|
||||
"_PewterGymReceivedTM34Text",
|
||||
"_TM34ExplanationText",
|
||||
} },
|
||||
["OPP_MISTY#1"] = { badge = "CASCADEBADGE", flag = "EVENT_BEAT_MISTY",
|
||||
item = "TM_BUBBLEBEAM",
|
||||
gotFlag = "EVENT_GOT_TM11",
|
||||
noRoom = "_CeruleanGymMistyTM11NoRoomText",
|
||||
deactivate = range("EVENT_BEAT_CERULEAN_GYM_TRAINER_", 0, 1),
|
||||
dialogue = {
|
||||
"_CeruleanGymMistyReceivedCascadeBadgeText",
|
||||
"_CeruleanGymMistyCascadeBadgeInfoText",
|
||||
},
|
||||
tmPre = { "_CeruleanGymMistyCascadeBadgeInfoText" },
|
||||
tmDialogue = {
|
||||
"_CeruleanGymMistyReceivedTM11Text",
|
||||
} },
|
||||
["OPP_LT_SURGE#1"] = { badge = "THUNDERBADGE", flag = "EVENT_BEAT_LT_SURGE",
|
||||
item = "TM_THUNDERBOLT",
|
||||
gotFlag = "EVENT_GOT_TM24",
|
||||
noRoom = "_VermilionGymLTSurgeTM24NoRoomText",
|
||||
deactivate = range("EVENT_BEAT_VERMILION_GYM_TRAINER_", 0, 2),
|
||||
dialogue = {
|
||||
"_VermilionGymLTSurgeReceivedThunderBadgeText",
|
||||
"_VermilionGymLTSurgeThunderBadgeInfoText",
|
||||
},
|
||||
tmPre = { "_VermilionGymLTSurgeThunderBadgeInfoText" },
|
||||
tmDialogue = {
|
||||
"_VermilionGymLTSurgeReceivedTM24Text",
|
||||
"_TM24ExplanationText",
|
||||
} },
|
||||
["OPP_ERIKA#1"] = { badge = "RAINBOWBADGE", flag = "EVENT_BEAT_ERIKA",
|
||||
item = "TM_MEGA_DRAIN",
|
||||
gotFlag = "EVENT_GOT_TM21",
|
||||
noRoom = "_CeladonGymTM21NoRoomText",
|
||||
deactivate = range("EVENT_BEAT_CELADON_GYM_TRAINER_", 0, 6),
|
||||
dialogue = {
|
||||
"_CeladonGymErikaReceivedRainbowBadgeText",
|
||||
"_CeladonGymRainbowBadgeInfoText",
|
||||
},
|
||||
tmPre = { "_CeladonGymRainbowBadgeInfoText" },
|
||||
tmDialogue = {
|
||||
"_CeladonGymReceivedTM21Text",
|
||||
"_TM21ExplanationText",
|
||||
} },
|
||||
["OPP_KOGA#1"] = { badge = "SOULBADGE", flag = "EVENT_BEAT_KOGA",
|
||||
item = "TM_TOXIC",
|
||||
gotFlag = "EVENT_GOT_TM06",
|
||||
noRoom = "_FuchsiaGymKogaTM06NoRoomText",
|
||||
deactivate = range("EVENT_BEAT_FUCHSIA_GYM_TRAINER_", 0, 5),
|
||||
dialogue = {
|
||||
"_FuchsiaGymKogaReceivedSoulBadgeText",
|
||||
"_FuchsiaGymKogaSoulBadgeInfoText",
|
||||
},
|
||||
tmPre = { "_FuchsiaGymKogaSoulBadgeInfoText" },
|
||||
tmDialogue = {
|
||||
"_FuchsiaGymKogaReceivedTM06Text",
|
||||
"_FuchsiaGymKogaTM06ExplanationText",
|
||||
} },
|
||||
["OPP_SABRINA#1"] = { badge = "MARSHBADGE", flag = "EVENT_BEAT_SABRINA",
|
||||
item = "TM_PSYWAVE",
|
||||
gotFlag = "EVENT_GOT_TM46",
|
||||
noRoom = "_SaffronGymSabrinaTM46NoRoomText",
|
||||
deactivate = range("EVENT_BEAT_SAFFRON_GYM_TRAINER_", 0, 6),
|
||||
dialogue = {
|
||||
"_SaffronGymSabrinaReceivedMarshBadgeText",
|
||||
"_SaffronGymSabrinaMarshBadgeInfoText",
|
||||
},
|
||||
tmPre = { "_SaffronGymSabrinaMarshBadgeInfoText" },
|
||||
tmDialogue = {
|
||||
"_SaffronGymSabrinaReceivedTM46Text",
|
||||
"_TM46ExplanationText",
|
||||
} },
|
||||
["OPP_BLAINE#1"] = { badge = "VOLCANOBADGE", flag = "EVENT_BEAT_BLAINE",
|
||||
item = "TM_FIRE_BLAST",
|
||||
gotFlag = "EVENT_GOT_TM38",
|
||||
noRoom = "_CinnabarGymBlaineTM38NoRoomText",
|
||||
deactivate = range("EVENT_BEAT_CINNABAR_GYM_TRAINER_", 0, 6),
|
||||
dialogue = {
|
||||
"_CinnabarGymBlaineReceivedVolcanoBadgeText",
|
||||
"_CinnabarGymBlaineVolcanoBadgeInfoText",
|
||||
},
|
||||
tmPre = { "_CinnabarGymBlaineVolcanoBadgeInfoText" },
|
||||
tmDialogue = {
|
||||
"_CinnabarGymBlaineReceivedTM38Text",
|
||||
"_CinnabarGymBlaineTM38ExplanationText",
|
||||
} },
|
||||
["OPP_GIOVANNI#3"] = { badge = "EARTHBADGE", flag = "EVENT_BEAT_GIOVANNI",
|
||||
item = "TM_FISSURE",
|
||||
gotFlag = "EVENT_GOT_TM27",
|
||||
noRoom = "_ViridianGymGiovanniTM27NoRoomText",
|
||||
deactivate = range("EVENT_BEAT_VIRIDIAN_GYM_TRAINER_", 0, 7),
|
||||
dialogue = {
|
||||
"_ViridianGymGiovanniReceivedEarthBadgeText",
|
||||
"_ViridianGymGiovanniEarthBadgeInfoText",
|
||||
},
|
||||
tmPre = { "_ViridianGymGiovanniEarthBadgeInfoText" },
|
||||
tmDialogue = {
|
||||
"_ViridianGymGiovanniReceivedTM27Text",
|
||||
"_ViridianGymGiovanniTM27ExplanationText",
|
||||
} },
|
||||
|
||||
-- Silph Co. Giovanni: unlocks the president's Master Ball gift
|
||||
["OPP_GIOVANNI#2"] = { flag = "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||
-- Silph Co. Giovanni: unlocks the president's Master Ball gift.
|
||||
-- SilphCo11FGiovanniStartBattleScript (scripts/SilphCo11F.asm) hands the
|
||||
-- battle SilphCo10FGiovanniILostAgainText through SaveEndBattleTextPointers,
|
||||
-- but he has no def_trainers header on 11F, so engageTrainer finds no
|
||||
-- header.won to give it -- this chain is the port's stand-in for that loss
|
||||
-- line (#722). The "Blast it all!" speech, the fade and the rockets
|
||||
-- leaving are SilphCo11FGiovanniAfterBattleScript, ported in M.SILPH_CO_11F
|
||||
-- (data/scripts/story.lua).
|
||||
["OPP_GIOVANNI#2"] = { flag = "EVENT_BEAT_SILPH_CO_GIOVANNI",
|
||||
dialogue = { "_SilphCo10FGiovanniILostAgainText" } },
|
||||
|
||||
-- Fighting Dojo Karate Master (scripts/FightingDojo.asm
|
||||
-- FightingDojoKarateMasterPostBattleScript sets EVENT_BEAT_KARATE_MASTER,
|
||||
|
||||
@@ -36,7 +36,8 @@ M.CERULEAN_MELANIES_HOUSE = {
|
||||
rows[#rows + 1] = { "label", "declined" }
|
||||
rows[#rows + 1] = { "show_text", "MelanieText5" }
|
||||
end
|
||||
ow.runner:run(rows, { npc = npc, onDone = done })
|
||||
ow.runner:run(rows, { npc = npc, onDone = done,
|
||||
checkpointOnDone = "release_npc" })
|
||||
end,
|
||||
-- pet flavor: the text with the species' cry over it
|
||||
TEXT_CERULEANMELANIESHOUSE_BULBASAUR = {
|
||||
@@ -105,7 +106,8 @@ M.VERMILION_CITY = {
|
||||
rows[#rows + 1] = { "label", "declined" }
|
||||
rows[#rows + 1] = { "show_text", "_OfficerJennyText4" }
|
||||
end
|
||||
ow.runner:run(rows, { npc = npc, onDone = done })
|
||||
ow.runner:run(rows, { npc = npc, onDone = done,
|
||||
checkpointOnDone = "release_npc" })
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -62,10 +62,15 @@ M.MT_MOON_B2F = {
|
||||
{ "walk_npc", 6, { "left", "left", "left", "left", "left" } },
|
||||
{ "face_object", 6, "left" },
|
||||
{ "show_text", "_MtMoonJessieJamesText2" },
|
||||
-- MtMoonB2FScript12 arms _MtMoonJessieJamesText3 with
|
||||
-- SaveEndBattleTextPointers before it sets wCurOpponent, so
|
||||
-- TrainerBattleVictory prints it on the battle screen as "ROCKET: A
|
||||
-- brat beat us?" between TrainerDefeatedText and MoneyForWinningText.
|
||||
-- Its one-word first line only reads right behind that tag (#866).
|
||||
{ "save_end_battle_text", "_MtMoonJessieJamesText3" },
|
||||
{ "start_battle", "trainer", "OPP_ROCKET", 42 },
|
||||
{ "check_battle_result", "win" },
|
||||
{ "jump_if_false", "end" },
|
||||
{ "show_text", "_MtMoonJessieJamesText3" },
|
||||
{ "show_text", "_MtMoonJessieJamesText4" },
|
||||
{ "stop_music" },
|
||||
{ "play_music", "Music_MeetJessieJames" },
|
||||
@@ -85,7 +90,8 @@ M.MT_MOON_B2F = {
|
||||
-- motto plays from off-screen FIRST, then the duo pops in at (25,10) /
|
||||
-- (24,10) and whichever of them shares the player's column ($18=24 or
|
||||
-- $19=25, EVENT_ROCKET_HIDEOUT_4_JESSIE_JAMES_ON_LEFT) walks the three
|
||||
-- tiles down to loom over the player while the other steps one. A loss
|
||||
-- tiles down to loom over the player while the other walks four and ends
|
||||
-- up beside him. A loss
|
||||
-- re-hides them (RocketHideoutB4FResetScripts via EVENT_6A0), so the
|
||||
-- trigger re-arms clean.
|
||||
-- -------------------------------------------------------------------
|
||||
@@ -106,7 +112,7 @@ M.ROCKET_HIDEOUT_B4F = {
|
||||
if f.EVENT_BEAT_ROCKET_HIDEOUT_4_JESSIE_JAMES then return false end
|
||||
-- ON_LEFT: player under James's column (25); movement data pairs
|
||||
-- RocketHideoutB4FJessieJamesMovementData_45605/45606 swap so the
|
||||
-- column-mate walks 3, the other 1.
|
||||
-- column-mate walks 3, the other 4.
|
||||
local onLeft = (x == 25)
|
||||
ow.runner:run({
|
||||
{ "stop_music" },
|
||||
@@ -116,16 +122,30 @@ M.ROCKET_HIDEOUT_B4F = {
|
||||
{ "emote", "player", "shock", 30 },
|
||||
{ "show_object", "ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_JAMES" },
|
||||
{ "show_object", "ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_JESSIE" },
|
||||
-- James (object 2) then Jessie (object 3), Script4..Script9 order
|
||||
{ "walk_npc", 2, onLeft and { "down", "down", "down" } or { "down" } },
|
||||
-- James (object 2) then Jessie (object 3), Script4..Script9 order.
|
||||
-- RocketHideoutB4FJessieJamesMovementData_45605 is a lone $4 that FALLS
|
||||
-- THROUGH into _45606 ($4 $4 $4 $ff), so MoveSprite_ (home/pathfinding.asm)
|
||||
-- reads _45605 as FOUR steps and _45606 as three; $4 is DOWN in Yellow's
|
||||
-- Func_5288 lookup (engine/overworld/movement.asm), which walks with no
|
||||
-- collision test. From (25,10)/(24,10) against a player on y=14 the
|
||||
-- column-mate stops three down, right above him, and the other walks the
|
||||
-- full four to stand alongside -- which is what the facings below assume.
|
||||
-- Reading _45605 as a single step stranded whoever was off-column three
|
||||
-- tiles away, so James never reached the player (#865).
|
||||
{ "walk_npc", 2, onLeft and { "down", "down", "down" }
|
||||
or { "down", "down", "down", "down" } },
|
||||
{ "face_object", 2, onLeft and "down" or "left" },
|
||||
{ "walk_npc", 3, onLeft and { "down" } or { "down", "down", "down" } },
|
||||
{ "walk_npc", 3, onLeft and { "down", "down", "down", "down" }
|
||||
or { "down", "down", "down" } },
|
||||
{ "face_object", 3, onLeft and "right" or "down" },
|
||||
{ "show_text", "_RocketHideoutJessieJamesText2" },
|
||||
-- RocketHideoutB4FScript10 saves _RocketHideoutJessieJamesText3 as the
|
||||
-- end-battle text, so it prints as "ROCKET: Such a dreadful twerp!" on
|
||||
-- the battle screen ahead of MoneyForWinningText (#866).
|
||||
{ "save_end_battle_text", "_RocketHideoutJessieJamesText3" },
|
||||
{ "start_battle", "trainer", "OPP_ROCKET", 43 },
|
||||
{ "check_battle_result", "win" },
|
||||
{ "jump_if_false", "lost" },
|
||||
{ "show_text", "_RocketHideoutJessieJamesText3" },
|
||||
{ "show_text", "_RocketHideoutJessieJamesText4" },
|
||||
{ "stop_music" },
|
||||
{ "play_music", "Music_MeetJessieJames" },
|
||||
@@ -175,16 +195,27 @@ M.POKEMON_TOWER_7F = {
|
||||
{ "show_text", "_PokemonTowerJessieJamesText1" },
|
||||
{ "face_player_dir", "up" },
|
||||
{ "emote", "player", "shock", 30 },
|
||||
-- Jessie (object 1) then James (object 2), Script1..Script6 order
|
||||
{ "walk_npc", 1, onLeft and { "down" } or { "down", "down", "down" } },
|
||||
-- Jessie (object 1) then James (object 2), Script1..Script6 order.
|
||||
-- Same fall-through blob as the hideout: PokemonTower7FMovementData_60d7a
|
||||
-- is a lone $4 running into _60d7b ($4 $4 $4 $FF), so _60d7a is FOUR
|
||||
-- steps and _60d7b is three. From (10,8)/(11,8) against a player on
|
||||
-- y=12 the column-mate halts one tile above him and the other closes the
|
||||
-- full four to his side; the single-step reading is why James only
|
||||
-- "moved a bit" here (#865).
|
||||
{ "walk_npc", 1, onLeft and { "down", "down", "down", "down" }
|
||||
or { "down", "down", "down" } },
|
||||
{ "face_object", 1, onLeft and "right" or "down" },
|
||||
{ "walk_npc", 2, onLeft and { "down", "down", "down" } or { "down" } },
|
||||
{ "walk_npc", 2, onLeft and { "down", "down", "down" }
|
||||
or { "down", "down", "down", "down" } },
|
||||
{ "face_object", 2, onLeft and "down" or "left" },
|
||||
{ "show_text", "_PokemonTowerJessieJamesText2" },
|
||||
-- PokemonTower7FScript7 saves _PokemonTowerJessieJamesText3 as the
|
||||
-- end-battle text: "ROCKET: You will regret this!" on the battle screen,
|
||||
-- before the prize money (#866).
|
||||
{ "save_end_battle_text", "_PokemonTowerJessieJamesText3" },
|
||||
{ "start_battle", "trainer", "OPP_ROCKET", 44 },
|
||||
{ "check_battle_result", "win" },
|
||||
{ "jump_if_false", "end" },
|
||||
{ "show_text", "_PokemonTowerJessieJamesText3" },
|
||||
{ "show_text", "_PokemonTowerJessieJamesText4" },
|
||||
{ "stop_music" },
|
||||
{ "play_music", "Music_MeetJessieJames" },
|
||||
@@ -254,10 +285,12 @@ M.SILPH_CO_11F = {
|
||||
{ "walk_npc", 6, jessieDirs },
|
||||
{ "face_object", 6, jessieFace },
|
||||
{ "show_text", "_SilphCoJessieJamesText2" },
|
||||
-- SilphCo11FScript11 saves _SilphCoJessieJamesText3 (SilphCo11FText_624c2)
|
||||
-- as the end-battle text: "ROCKET: Like always..." before the money (#866).
|
||||
{ "save_end_battle_text", "_SilphCoJessieJamesText3" },
|
||||
{ "start_battle", "trainer", "OPP_ROCKET", 45 },
|
||||
{ "check_battle_result", "win" },
|
||||
{ "jump_if_false", "end" },
|
||||
{ "show_text", "_SilphCoJessieJamesText3" },
|
||||
{ "show_text", "_SilphCoJessieJamesText4" },
|
||||
{ "stop_music" },
|
||||
{ "play_music", "Music_MeetJessieJames" },
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
-- The Viridian City old-man catch tutorial, Yellow's way
|
||||
-- (pokeyellow scripts/ViridianCity.asm, scripts/ViridianCity_2.asm,
|
||||
-- scripts/OaksLab.asm). Registered on top of the shared tables by
|
||||
-- data/scripts/init.lua on a Yellow boot.
|
||||
--
|
||||
-- Yellow has TWO gambler objects where Red/Blue have one:
|
||||
-- * VIRIDIANCITY_OLD_MAN at (17,5) -- a Red/Blue leftover; its
|
||||
-- toggle stays OFF forever in Yellow, no script ever shows it.
|
||||
-- * VIRIDIANCITY_OLD_MAN2 at (18,9) -- replaces the sleeper the
|
||||
-- moment the Pokédex is given (OaksLabOakGivesPokedexScript:
|
||||
-- HideObject TOGGLE_LYING_OLD_MAN / ShowObject TOGGLE_OLD_MAN_2).
|
||||
--
|
||||
-- This is the tutorial old man. The Red/Blue "Are you in a hurry?"
|
||||
-- yes/no script must NOT run against him: Yellow's
|
||||
-- _ViridianCityOldManHadMyCoffeeNowText is the apology speech ("I've had
|
||||
-- my coffee now ... I'll show you how to catch POKéMON as my apology"),
|
||||
-- and the shared story.lua TEXT_VIRIDIANCITY_OLD_MAN rows hang an
|
||||
-- invented yes/no over it -- YES printed the TimeIsMoney alias
|
||||
-- (_ViridianCityOldManLosingMyTouchText) and NO ran the demo, every
|
||||
-- talk, forever (#617).
|
||||
--
|
||||
-- The real flow (ViridianCityCheckWaitingOldMan + ViridianCityOldMan2Text
|
||||
-- + ViridianCityOldManInitialCatchTrainingScript + ...EndInitial... +
|
||||
-- ViridianCityPostInitialCatchTraining): stepping into (19,9) -- the gap
|
||||
-- east of the sleeper's cell -- faces the old man right and the player
|
||||
-- left, prints the apology, and without any choice runs the demo battle
|
||||
-- (BATTLE_TYPE_OLD_MAN, RATTATA lvl 5), which he FAILS -- the ball shakes
|
||||
-- three times and breaks open. After it, the same text pointer
|
||||
-- now prints _ViridianCityOldManLosingMyTouchText ("That didn't work!
|
||||
-- I must be losing my touch."), the old man walks off (down 6 with the
|
||||
-- player on (19,9), right 1 otherwise, Pikachu nudged out of the way
|
||||
-- first) and TOGGLE_OLD_MAN_2 hides. A direct talk does the same.
|
||||
|
||||
local M = {}
|
||||
|
||||
local OLD_MAN2 = "VIRIDIANCITY_OLD_MAN2"
|
||||
|
||||
-- Capture the FUNCTION, not the table: attachBase stores the module
|
||||
-- table itself, so once this file's onStep is attached the table's slot
|
||||
-- points back here -- delegating through the table would self-recurse.
|
||||
-- story5's VIRIDIAN_CITY.onStep chains story.lua's sleeping-old-man
|
||||
-- gate and its own gym-lock step (same pattern as yellow_jessie_james).
|
||||
local baseViridianStep = require("data.scripts.story5").VIRIDIAN_CITY.onStep
|
||||
|
||||
-- pokeyellow text/ViridianCity.asm, _ViridianCityOldManHadMyCoffeeNowText
|
||||
-- and _ViridianCityOldManLosingMyTouchText, spelled with the extractor's
|
||||
-- markers (line -> \n, cont -> \v, para -> \f)
|
||||
local function text(game)
|
||||
return {
|
||||
apology = game.data.text._ViridianCityOldManHadMyCoffeeNowText
|
||||
or "Ahh, I've had my\ncoffee now and I\vfeel great!\fSure, you can go\n"
|
||||
.. "through!\fI'm sorry I was\nso rude to you!\fI see you're using\n"
|
||||
.. "a POKéDEX.\fI'll show you how\nto catch POKéMON\vas my apology.",
|
||||
losingMyTouch = game.data.text._ViridianCityOldManLosingMyTouchText
|
||||
or "That didn't work!\nI must be losing\vmy touch.\fI've run out of\n"
|
||||
.. "POKé BALLs too.\fI have to get some\nat POKéMON MART.",
|
||||
}
|
||||
end
|
||||
|
||||
-- The row list for the initial-tutorial branch, keyed by where the
|
||||
-- player stands when the battle ends (ViridianCityPostInitialCatchTraining
|
||||
-- reads wXCoord: (19,9) walks the old man down the corridor, anywhere
|
||||
-- else walks him right 1 after moving the follower Pikachu aside).
|
||||
local function oldMan2Rows(game, ow, npc)
|
||||
local rows = {
|
||||
{ "show_text", "_ViridianCityOldManHadMyCoffeeNowText" },
|
||||
-- ViridianCityOldManInitialCatchTrainingScript sets
|
||||
-- EVENT_INITIAL_CATCH_TRAINING before the battle runs, and
|
||||
-- ItemUseBall's .oldManBattle branch turns that event into anim data
|
||||
-- $63: three shakes, then the ball breaks open. The losing-my-touch
|
||||
-- line below only follows a throw that failed (#636).
|
||||
{ "old_man_demo", "fail" },
|
||||
{ "set_flag", "EVENT_COMPLETED_CATCH_TRAINING" },
|
||||
{ "show_text", "_ViridianCityOldManLosingMyTouchText" },
|
||||
}
|
||||
if ow.player and ow.player.cellX == 19 then
|
||||
rows[#rows + 1] =
|
||||
{ "walk_npc", npc.def.index,
|
||||
{ "down", "down", "down", "down", "down", "down" } }
|
||||
else
|
||||
-- ViridianCityMovePikachu (scripts/ViridianCity_2.asm): Pikachu
|
||||
-- steps out of the old man's way before he turns right
|
||||
local PikachuFollower = require("src.world.PikachuFollower")
|
||||
local pika = ow and PikachuFollower.current(ow)
|
||||
if pika then
|
||||
rows[#rows + 1] = { "walk_npc", pika.def.index, { "right" } }
|
||||
end
|
||||
rows[#rows + 1] = { "walk_npc", npc.def.index, { "right" } }
|
||||
end
|
||||
rows[#rows + 1] =
|
||||
{ "hide_object", "VIRIDIAN_CITY", OLD_MAN2 }
|
||||
return rows
|
||||
end
|
||||
|
||||
-- The shared talk handler: TEXT_VIRIDIANCITY_OLD_MAN2's text_asm branch
|
||||
-- (ViridianCityOldMan2Text) on EVENT_COMPLETED_CATCH_TRAINING.
|
||||
local function oldMan2Talk(game, ow, npc, done)
|
||||
if game.save.flags and game.save.flags.EVENT_COMPLETED_CATCH_TRAINING then
|
||||
local TextBox = require("src.render.TextBox")
|
||||
game.stack:push(TextBox.new(game, text(game).losingMyTouch, done))
|
||||
return
|
||||
end
|
||||
ow.runner:run(oldMan2Rows(game, ow, npc), { npc = npc, onDone = done,
|
||||
checkpointOnDone = "release_npc" })
|
||||
end
|
||||
|
||||
M.VIRIDIAN_CITY = {
|
||||
talk = {
|
||||
TEXT_VIRIDIANCITY_OLD_MAN2 = oldMan2Talk,
|
||||
},
|
||||
|
||||
-- Re-apply the Pokédex swap for a save that already holds the flag but
|
||||
-- was never standing here when it fired (converted .sav imports, same
|
||||
-- shape as story.lua's VIRIDIAN_CITY.onEnter). Yellow shows OLD_MAN2,
|
||||
-- not the Red/Blue OLD_MAN at (17,5), and also puts away a stray
|
||||
-- OLD_MAN a save made by the pre-#617 build left standing.
|
||||
onEnter = function(game, ow)
|
||||
if not (game.save.flags and game.save.flags.EVENT_GOT_POKEDEX) then
|
||||
return
|
||||
end
|
||||
local Commands = require("src.script.Commands")
|
||||
local ctx = { save = game.save, game = game, overworld = ow }
|
||||
Commands.hide_object(ctx, "VIRIDIAN_CITY", "VIRIDIANCITY_OLD_MAN_SLEEPY")
|
||||
Commands.hide_object(ctx, "VIRIDIAN_CITY", "VIRIDIANCITY_OLD_MAN")
|
||||
Commands.show_object(ctx, "VIRIDIAN_CITY", OLD_MAN2)
|
||||
end,
|
||||
|
||||
-- ViridianCityCheckWaitingOldMan: with the Pokédex held and the
|
||||
-- tutorial undone, (19,9) -- the gap east of the old man, the same
|
||||
-- cell the sleeper used to gate -- faces him right, turns the player
|
||||
-- left and starts the OLD_MAN2 flow with no choice.
|
||||
onStep = function(game, ow, x, y)
|
||||
if baseViridianStep and baseViridianStep(game, ow, x, y) then
|
||||
return true
|
||||
end
|
||||
local flags = game.save.flags
|
||||
if not flags.EVENT_GOT_POKEDEX then return false end
|
||||
if flags.EVENT_COMPLETED_CATCH_TRAINING then return false end
|
||||
if x ~= 19 or y ~= 9 then return false end
|
||||
local man
|
||||
for _, npc in ipairs(ow.npcs) do
|
||||
if npc.def and npc.def.name == OLD_MAN2 then man = npc break end
|
||||
end
|
||||
if not man then return false end
|
||||
man.facing = "right"
|
||||
ow.player.facing = "left"
|
||||
oldMan2Talk(game, ow, man, nil)
|
||||
return true
|
||||
end,
|
||||
}
|
||||
|
||||
return M
|
||||
@@ -38,7 +38,7 @@ the same core data and graphics into the source tree for verification.
|
||||
| | `src/core/SaveData.lua` | Lua-serialized save in the LÖVE save dir |
|
||||
| render | `src/render/Renderer.lua` | 160x144 canvas, integer nearest scaling |
|
||||
| | `src/render/TileRenderer.lua` | one SpriteBatch per map (8x8 quads) + border-block ring |
|
||||
| | `src/render/SpriteRenderer.lua` | 6-frame walker sheets, flipped right facing |
|
||||
| | `src/render/SpriteRenderer.lua` | variable-size anchored sprite sheets, 6-frame walkers and flipped right facing |
|
||||
| | `src/render/Font.lua` | glyph rendering via charmap (greedy longest match) |
|
||||
| | `src/render/TextBox.lua` | dialogue box: typewriter, `\n` line, `\v` scroll, `\f` page |
|
||||
| | `src/render/Camera.lua`, `Transition.lua` | follow camera, warp fades |
|
||||
|
||||
@@ -234,8 +234,12 @@ What was ported from pokered's engine code and where it came from.
|
||||
pre-battle text and engages the leader battle (badge/TM via
|
||||
data/scripts/victories.lua); post-badge talk prints the leader's
|
||||
post-battle advice text (Misty's is her TM11 explanation). The
|
||||
originals' middle branch (beaten but TM not handed over) is
|
||||
unreachable since the TM is granted with the victory. Giovanni's
|
||||
originals' middle branch (beaten but TM not handed over,
|
||||
CheckEventReuseA EVENT_GOT_TM*) is ported too: the victory's GiveItem
|
||||
goes through the bag's capacity check, a full bag shows the leader's
|
||||
"make room" text instead of the received lines and leaves
|
||||
EVENT_GOT_TM* unset, and talking to the leader re-runs the ReceiveTM
|
||||
script until the TM goes in (#797). Giovanni's
|
||||
farewell (`ViridianGymGiovanniText` .afterBeat) hides him inside a
|
||||
fade-to-black/fade-in Transition matching ViridianGym.asm's
|
||||
GBFadeOutToBlack → HideObject → GBFadeInFromBlack, persisted
|
||||
|
||||
@@ -0,0 +1,615 @@
|
||||
# Pokémon Gold, Silver, and Crystal: A Systematic Engine, ASM, and Behavioral Comparison
|
||||
|
||||
## Executive summary
|
||||
|
||||
Pokémon Gold and Silver are best understood as **two data-configured builds of one engine**, whereas Pokémon Crystal is an **expanded, reorganized, Color-only derivative of that engine**. The `pret/pokegold` repository reproducibly builds the international Gold and Silver ROMs byte-for-byte, while `pret/pokecrystal` separately builds multiple international Crystal revisions and debug artifacts. This makes the two repositories unusually strong primary evidence: their output hashes correspond to known retail binaries rather than merely reimplementing observed behavior.
|
||||
|
||||
The largest technical conclusion is that Crystal is **not a ground-up engine rewrite**. Core subsystems (the frame-driven sound interpreter, hardware-derived RNG, synchronized link-battle PRNG, wild-encounter probability machinery, party-mon record size, and much of the RTC pipeline) remain recognizably inherited from Gold/Silver. Crystal instead adds new ROM banks, reorganizes routines, introduces Color-specific rendering paths, fills formerly reserved fields in Pokémon records, redesigns SRAM placement, and layers Battle Tower, Mobile System GB, animated sprites, the female protagonist, Buena, Move Tutor, Suicune story logic, and Virtual Console hooks around the inherited core.
|
||||
|
||||
The most important behavioral differences are therefore caused less by wholesale algorithm replacement than by **changed control flow and data**:
|
||||
|
||||
| Area | Gold and Silver | Crystal | Technical consequence |
|
||||
|---|---|---|---|
|
||||
| Edition structure | One shared engine with edition-selected encounter, sprite, title, and other data | One enhanced-version build with regional/revision conditionals | Gold-to-Silver ports are mostly data substitutions; Gold/Silver-to-Crystal ports are architectural merges |
|
||||
| Display target | Runtime distinguishes DMG/SGB/CGB modes | Explicit Color-only path | Crystal code can assume CGB palettes, VRAM banking, and related presentation behavior more aggressively |
|
||||
| RNG core | Divider-register accumulator plus synchronized link PRNG | Same algorithms | Different outcomes normally arise from different call timing or paths, not a different generator |
|
||||
| Party records | Two bytes after Pokérus are reserved | Those same bytes become caught-time/gender/level/location metadata | Pokémon records remain structurally compatible despite Crystal gaining metadata |
|
||||
| Save layout | Main and backup data are fragmented among SRAM sections | Main and backup records become more contiguous; Crystal/Battle Tower/Mobile blocks are added | Whole `.sav` files are not drop-in interchangeable |
|
||||
| Battles | Standard Gen II battle engine | Inherited engine plus Battle Tower branches and selective bug fixes | Link-compatible behavior is intentionally retained in some otherwise-fixed cases |
|
||||
| Roaming Pokémon | Raikou, Entei, and Suicune use the roaming system | Only Raikou and Entei roam; Suicune becomes a scripted encounter | Encounter-state conversion requires special handling |
|
||||
| Graphics | Static Pokémon fronts and DMG/SGB-compatible layouts | Animated fronts, revised sprites, richer Color layouts | Animation introduces new graphics data, decompression, frame, and timing requirements |
|
||||
| Audio | Shared four-channel Game Boy music interpreter | Same interpreter plus new songs and call sites | Music ports are mostly data/pointer work rather than synthesizer rewrites |
|
||||
| Major fixed defects | Coin Case arbitrary code execution, first-save Hall of Fame corruption, Lucky Number box limit, and several map/text defects | Corrected | Crystal is safer, but not "bug-fixed Gen II" in the broad sense |
|
||||
| Persistent defects | Many Gen II battle, capture, item, and badge defects | Many remain | Mechanical compatibility often outweighed cleanup |
|
||||
|
||||
Gold and Silver themselves have almost no meaningful engine-level divergence. Their scientifically interesting comparison is principally **which tables and assets are selected at assembly time**. Crystal is the meaningful engine comparison.
|
||||
|
||||
The report assumes a technically literate audience familiar with low-level programming but not necessarily Game Boy internals. Unless explicitly stated, "Crystal" means the international English code represented by `pret/pokecrystal`; Japanese Mobile System GB and Australian/revision-specific behavior are treated as regional variants. "Scientific" here means reproducible binary-derived comparison, explicit source hierarchy, and separation of direct evidence from inference, not laboratory experimentation involving original development source code, which has not been publicly released.
|
||||
|
||||
## Scope, evidence, and reproducibility
|
||||
|
||||
### Source hierarchy
|
||||
|
||||
The strongest sources are the two pret disassemblies:
|
||||
|
||||
- [`pret/pokegold`](https://github.com/pret/pokegold), which builds exact Gold and Silver international ROM images.
|
||||
- [`pret/pokecrystal`](https://github.com/pret/pokecrystal), which builds Crystal international v1.0, v1.1, Australian, and debug images.
|
||||
|
||||
The repositories identify exact SHA-1 outputs. Gold builds to `d8b8a3600a465308c9953dfa04f0081c05bdcb94`, Silver to `49b163f7e57702bc939d642a18f591de55d92dae`, Crystal international v1.0 to `f4cd194bdee0d04ca4eac29e09b8e4e9d818c133`, and Crystal international v1.1 to `f2f52230b536214ef7c9924f483392993e226cfb`. Those reproducible identities are critical: a label such as `BadgeStatBoosts` is a reverse-engineered name, but the emitted bytes and resulting behavior are those of the retail ROM.
|
||||
|
||||
Secondary sources such as Bulbapedia and Serebii are useful for player-visible cross-checking, release differences, encounter availability, and historical context. The pret bug documents are stronger than ordinary wiki summaries for defects because they identify the responsible instructions and provide corrective diffs. Official Nintendo and Pokémon sources are most valuable for release/Virtual Console behavior and supported transfer paths, but they do not publish the games' assembly architecture.
|
||||
|
||||
### Reproducible comparison method
|
||||
|
||||
A rigorous comparison should operate at four levels:
|
||||
|
||||
| Level | Method | What it establishes | Main limitation |
|
||||
|---|---|---|---|
|
||||
| Binary identity | Build each repository with its pinned RGBDS-compatible toolchain and verify SHA-1 | The source corresponds to the target retail ROM | Does not explain why bytes differ |
|
||||
| Source topology | Compare `main.asm`, `home.asm`, bank sections, include graphs, WRAM/SRAM declarations | Architectural additions, removals, and relocation | File organization can change without behavioral change |
|
||||
| Routine semantics | Compare labels, branches, register use, calls, data offsets, and side effects | Control-flow and algorithmic differences | Labels and comments are community-derived |
|
||||
| Behavioral validation | Run controlled emulator tests with trace logging and fixed input timing | Observable consequences and RNG/cycle sensitivity | Emulator accuracy and RTC state must be controlled |
|
||||
|
||||
For a formal experiment, the recommended independent variables are edition, ROM revision, initial SRAM, RTC register state, boot hardware mode, input sequence, and link mode. Dependent variables include RNG bytes, encounter choice, battle result, SRAM writes, audio commands, VRAM/OAM writes, and cycle/frame number. Because the ordinary RNG reads the divider register, deterministic comparisons must start from a controlled boot state and reproduce input timing at frame or cycle precision.
|
||||
|
||||
### Gold versus Silver as a build-configuration comparison
|
||||
|
||||
The fact that `pokegold` emits both Gold and Silver from one repository is itself strong evidence that the two versions share the same broad code architecture. Edition differences are implemented through conditional assembly and alternate data or assets rather than through separate engines. The obvious behavioral results are version-specific wild Pokémon, title graphics, mascot-facing presentation, and selected NPC or table data; battle arithmetic, saves, RTC processing, audio interpretation, and link handling come from the same shared source.
|
||||
|
||||
This distinction matters for analysis. Treating Gold, Silver, and Crystal as three equally separate engines exaggerates Gold/Silver differences and understates Crystal's structural changes. A better model is:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
GSC[Generation II design and data model]
|
||||
GS[Gold/Silver shared engine]
|
||||
GD[Gold-selected data and assets]
|
||||
SD[Silver-selected data and assets]
|
||||
C[Crystal derivative engine]
|
||||
CF[Crystal-only feature modules]
|
||||
CR[Crystal revisions and regional flags]
|
||||
|
||||
GSC --> GS
|
||||
GS --> GD
|
||||
GS --> SD
|
||||
GS --> C
|
||||
C --> CF
|
||||
C --> CR
|
||||
```
|
||||
|
||||
## ROM and engine architecture
|
||||
|
||||
### Banked execution model
|
||||
|
||||
The games execute on the Game Boy's LR35902-class CPU and are organized around a fixed home bank plus switchable ROM banks. Calls across banks generally pass through wrappers that save the current bank, select the target bank, call the routine, and restore the original bank. The `BattleRandom` wrapper is a concise example: `_BattleRandom` resides outside the home bank, so the wrapper saves `hROMBank`, uses the bank-switch restart vector, calls the routine, preserves its return value, and restores the previous bank.
|
||||
|
||||
```asm
|
||||
BattleRandom::
|
||||
ldh a, [hROMBank]
|
||||
push af
|
||||
ld a, BANK(_BattleRandom)
|
||||
rst Bankswitch
|
||||
call _BattleRandom
|
||||
...
|
||||
pop af
|
||||
rst Bankswitch
|
||||
ret
|
||||
```
|
||||
|
||||
Relevant sources: [Gold/Silver `home/random.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/home/random.asm#L29-L46) and [Crystal `home/random.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/home/random.asm#L29-L46).
|
||||
|
||||
The repositories' top-level `main.asm` files are effectively link manifests for this architecture. Gold/Silver's file groups the battle core, encounters, party menu, RTC, phone, Pokégear, sprite animation, graphics loading, and other systems into named ROM sections. Crystal retains those categories but adds explicitly named feature sections and many additional modules.
|
||||
|
||||
### Crystal as an extension and bank-layout rewrite
|
||||
|
||||
Crystal's `main.asm` introduces a conspicuous `Crystal Features 1` section containing gender initialization, Kris-specific bag handling, Move Tutor, Crystal layouts, Celebi logic, the redesigned main menu, Mobile menu code, owned-Pokémon search, and Buena's menu. Nearby banks add Battle Tower trainer logic, caught-data handling, a revised stats screen, sliding battle intros, battle-scene checks, and the Color-only startup screen.
|
||||
|
||||
That include topology supports three conclusions.
|
||||
|
||||
First, Crystal preserves **vertical subsystem continuity**. The battle engine is still `engine/battle/core.asm`; encounters remain `engine/overworld/wildmons.asm`; Pokémon records still derive from the same party/box macros; and audio is still driven by the same interpreter.
|
||||
|
||||
Second, Crystal adds **horizontal feature coupling**. Battle Tower touches battle setup, stat boosts, experience rules, saving, menus, SRAM, trainers, music, and link-like constraints. The female protagonist touches initialization, sprites, bag graphics, caught metadata, and menu presentation. Mobile support touches interrupts, SRAM, menus, communications, rankings, and audio.
|
||||
|
||||
Third, code and data relocation is substantial enough that raw ROM addresses from Gold/Silver cannot generally be transplanted into Crystal. A symbolic port based on labels and structures is practical; a patch based on absolute offsets is brittle.
|
||||
|
||||
### File and routine comparison
|
||||
|
||||
| Subsystem | Gold/Silver source | Crystal source | ASM-level difference | Porting significance |
|
||||
|---|---|---|---|---|
|
||||
| Top-level bank graph | [`main.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/main.asm) | [`main.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/main.asm) | Crystal adds feature, Battle Tower, Mobile, gender, animation, and caught-data modules and relocates shared includes | Port by symbol and feature dependency, not ROM offset |
|
||||
| Ordinary RNG | [`home/random.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/home/random.asm) | [`home/random.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/home/random.asm) | Essentially identical routine and rejection-sampling helper | RNG-sensitive ports must preserve call timing |
|
||||
| Battle core | [`engine/battle/core.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/engine/battle/core.asm) | [`engine/battle/core.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/engine/battle/core.asm) | Crystal adds Battle Tower guards and conditional fixes while retaining link behavior | Mechanical changes can desynchronize unmodified peers |
|
||||
| Encounters | [`engine/overworld/wildmons.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/engine/overworld/wildmons.asm) | [`engine/overworld/wildmons.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/engine/overworld/wildmons.asm) | Same rate/slot pipeline; different swarm state and two rather than three roamers | World-state conversion needs special Suicune handling |
|
||||
| SRAM declaration | [`ram/sram.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/ram/sram.asm) | [`ram/sram.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/ram/sram.asm) | Crystal consolidates save/backup regions and adds GS Ball, Battle Tower, rankings, and Mobile blocks | Whole-save binary compatibility is lost |
|
||||
| Pokémon structures | [`pokemon_data_constants.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/constants/pokemon_data_constants.asm#L69-L103) | [`pokemon_data_constants.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/constants/pokemon_data_constants.asm#L69-L108) | Crystal assigns caught metadata to two bytes reserved in Gold/Silver | Record length remains compatible |
|
||||
| RTC/home time | [`home/time.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/home/time.asm) | [`home/time.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/home/time.asm) | Same RTC model; Crystal timer interrupt can dispatch Mobile timing | Emulator ports must emulate RTC and, for Japanese features, mobile timing assumptions |
|
||||
| Picture loading | [`engine/gfx/load_pics.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/engine/gfx/load_pics.asm) | [`engine/gfx/load_pics.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/engine/gfx/load_pics.asm) | Crystal has additional front-picture and animation-oriented handling | Static sprite replacement is insufficient |
|
||||
| Sound interpreter | [`audio/engine.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/audio/engine.asm) | [`audio/engine.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/audio/engine.asm) | Core interpreter is structurally the same | New Crystal music is primarily sequenced data and pointers |
|
||||
| Song table | [`audio/music_pointers.asm`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/audio/music_pointers.asm) | [`audio/music_pointers.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/audio/music_pointers.asm#L97-L108) | Crystal appends ten named songs | Existing song IDs remain stable through the Gold/Silver range |
|
||||
|
||||
The line counts of several Crystal files are larger, but line count alone is not a behavioral metric; comments, label quality, and source refactoring affect it. The stronger evidence is the presence of new branches, fields, tables, and externally observable side effects.
|
||||
|
||||
### Hardware-mode divergence
|
||||
|
||||
Gold/Silver initialization tests whether the console is a Game Boy Color and maintains non-CGB paths, including Super Game Boy initialization. Crystal includes an explicit `gbc_only.asm` module that displays the incompatibility message on non-CGB hardware. That is not merely marketing metadata: it changes what assumptions later rendering code may safely make about palettes, VRAM banking, and Color hardware.
|
||||
|
||||
For a reverse port of Crystal features into Gold/Silver, every CGB-only feature falls into one of three categories: provide a monochrome/SGB fallback, disable it outside CGB mode, or intentionally convert the resulting ROM into a Color-only build. Skipping that decision produces subtle failures rather than one clean compile error: palette attributes, second-bank VRAM data, and tile-upload timing can all be implicated.
|
||||
|
||||
## Save, RNG, clock, and entity state
|
||||
|
||||
### Save architecture
|
||||
|
||||
Gold/Silver divide their main game state into options, three player-data fragments, current-map data, Pokémon data, and a checksum. Backup pieces are distributed among several separately placed SRAM sections. The current PC box is separate, the fourteen inactive boxes occupy exactly two SRAM banks, and mail and Mystery Gift have dedicated regions.
|
||||
|
||||
Crystal instead defines a contiguous `sGameData` consisting of player, map, and Pokémon data, pads the region, and stores a checksum. Its backup is similarly grouped. Crystal then adds a GS Ball flag, `sCrystalData`, Battle Tower progress and recent-trainer state, ranking records, Mobile communication data, offers, credentials, and Japanese Mobile-specific buffers. The source explicitly notes that the international `sCrystalData` location differs from Japanese Crystal.
|
||||
|
||||
| Save component | Gold/Silver | Crystal |
|
||||
|---|---|---|
|
||||
| Options and corruption sentinels | Present | Present |
|
||||
| Main player/map/Pokémon data | Split into labeled player fragments inside the save region | Grouped into one contiguous main-game block |
|
||||
| Main checksum | 16-bit checksum after game data | 16-bit checksum after padded game-data region |
|
||||
| Backup | Fragmented among multiple SRAM sections | Consolidated backup game-data block |
|
||||
| Current box | Separate `curbox` structure | Separate full `box` structure plus padding |
|
||||
| Inactive boxes | Fourteen boxes over two SRAM banks | Same fourteen-box capacity over two banks |
|
||||
| Party and mailbox mail | Dedicated primary and backup regions | Same broad model |
|
||||
| GS Ball state | Absent | Primary and backup flags |
|
||||
| Battle Tower | Absent | Challenge state, streak, previous teams, reward |
|
||||
| Mobile/rankings | Absent | Several additional SRAM sections |
|
||||
| Full-file compatibility | Gold and Silver are closely related but still edition-specific | Not layout-compatible with Gold/Silver |
|
||||
|
||||
Crystal's save routine makes the transactional sequence visible:
|
||||
|
||||
```asm
|
||||
_SaveGameData:
|
||||
farcall StageRTCTimeForSave
|
||||
farcall BackupMysteryGift
|
||||
call ValidateSave
|
||||
call SaveOptions
|
||||
call SavePlayerData
|
||||
call SavePokemonData
|
||||
call SaveBox
|
||||
call SaveChecksum
|
||||
call ValidateBackupSave
|
||||
...
|
||||
farcall BackupGSBallFlag
|
||||
farcall SaveRTC
|
||||
```
|
||||
|
||||
Source: [Crystal `engine/menus/save.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/engine/menus/save.asm#L255-L284). The routine stages RTC state, writes the primary data and box, computes the primary checksum, writes backup data and checksum, backs up party mail and GS Ball state, persists RTC state, and normalizes a completed Battle Tower reward state.
|
||||
|
||||
The practical conclusion is precise: **do not migrate a Gold/Silver `.sav` to Crystal by copying the file or the main save block**. A converter should parse symbolic fields, validate checksums and sentinels, copy player/map/Pokémon/box/mail data field by field, initialize Crystal-only state, and regenerate Crystal checksums. Gold-to-Silver conversion is less structurally disruptive, but version-dependent event and encounter state should still be treated deliberately.
|
||||
|
||||
### Party and boxed-Pokémon records
|
||||
|
||||
One of Crystal's most elegant compatibility decisions is visible at the structure-definition level. In Gold/Silver, the two bytes after `MON_POKERUS` are simply reserved:
|
||||
|
||||
```asm
|
||||
DEF MON_HAPPINESS rb
|
||||
DEF MON_POKERUS rb
|
||||
rb_skip 2
|
||||
DEF MON_LEVEL rb
|
||||
```
|
||||
|
||||
In Crystal, the same positions are assigned to a two-byte caught-data field:
|
||||
|
||||
```asm
|
||||
DEF MON_HAPPINESS rb
|
||||
DEF MON_POKERUS rb
|
||||
DEF MON_CAUGHTDATA rw
|
||||
DEF MON_LEVEL rb
|
||||
```
|
||||
|
||||
Sources: [Gold/Silver structure](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/constants/pokemon_data_constants.asm#L83-L101) and [Crystal structure](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/constants/pokemon_data_constants.asm#L83-L107).
|
||||
|
||||
Crystal packs caught time and trainer gender into one byte and caught level and location into another. Because Game Freak repurposed reserved bytes instead of extending the record, `BOXMON_STRUCT_LENGTH` and `PARTYMON_STRUCT_LENGTH` remain compatible. Gold/Silver can carry those bytes without understanding them; Crystal can interpret them when present. This is a major reason Gen II Pokémon can move among Gold, Silver, and Crystal without a record-size translation layer.
|
||||
|
||||
A second structure change appears in the base-species TM/HM compatibility bitset. Gold/Silver size it for `NUM_TM_HM`; Crystal sizes it for `NUM_TM_HM_TUTOR`, accommodating Move Tutor compatibility in the same species-data model. This means a straight copy of Gold/Silver base-stat records into Crystal must account for Crystal's expanded learnability domain even if the visible species stats are unchanged.
|
||||
|
||||
### Item and party handling
|
||||
|
||||
The basic inventory model (separate item pockets, held-item byte in each Pokémon record, party length of six, PC boxes, mail records, and item-dispatch routines) is inherited. Crystal's changes are concentrated in new consumers and UI behavior: Move Tutor eligibility, gender-specific player presentation, caught metadata, Battle Tower restrictions, and additional event items such as GS Ball state.
|
||||
|
||||
Crystal did **not** comprehensively repair the item mechanics. Its documented defects still include Moon Ball not applying its intended multiplier, Love Ball checking the wrong gender relationship, Fast Ball applying to only a few species, three incorrect Heavy Ball weight cases, and status conditions failing to affect capture rate as intended. Those defects live in shared mechanical paths and are important when evaluating a "faithful" engine port: correcting them changes gameplay and may alter deterministic test vectors.
|
||||
|
||||
### Ordinary RNG
|
||||
|
||||
Gold/Silver and Crystal use the same ordinary RNG routine. It samples the hardware divider register and updates two one-byte accumulators, one by addition-with-carry and one by subtraction-with-carry:
|
||||
|
||||
```asm
|
||||
ldh a, [rDIV]
|
||||
ld b, a
|
||||
ldh a, [hRandomAdd]
|
||||
adc b
|
||||
ldh [hRandomAdd], a
|
||||
|
||||
ldh a, [rDIV]
|
||||
ld b, a
|
||||
ldh a, [hRandomSub]
|
||||
sbc b
|
||||
ldh [hRandomSub], a
|
||||
```
|
||||
|
||||
Sources: [Gold/Silver](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/home/random.asm#L13-L27) and [Crystal](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/home/random.asm#L13-L27). The files are effectively identical, including the `RandomRange` rejection-sampling routine used to avoid simple modulo bias.
|
||||
|
||||
This generator is not a self-contained seeded PRNG in the modern sense. Its output depends on divider phase, previous accumulator state, carry state, VBlank updates, and when the routine is reached. Consequently, adding an animation, menu delay, conditional call, or extra random-consuming feature can alter later outcomes even while the RNG instructions remain unchanged.
|
||||
|
||||
### Link-battle PRNG
|
||||
|
||||
Battles route randomness through `BattleRandom`. In non-link play, `_BattleRandom` ultimately uses the ordinary RNG. In linked battles, both systems consume a shared ten-byte random sequence and advance bytes with the recurrence:
|
||||
|
||||
```
|
||||
x[n+1] = (5 * x[n] + 1) mod 256
|
||||
```
|
||||
|
||||
The Gold/Silver and Crystal battle cores contain the same broad synchronization mechanism.
|
||||
|
||||
This has a major compatibility consequence: a battle patch can be logically correct in isolation yet break link play if it causes one participant to make a different number or order of RNG calls. That is why Crystal sometimes preserves Gold/Silver behavior specifically in link battles. Synchronization depends on matching control flow, not merely using the same random-number formula.
|
||||
|
||||
### RTC and time-of-day behavior
|
||||
|
||||
Both codebases latch the MBC3-style real-time clock, read seconds, minutes, hours, and the low/high day registers, normalize time, add the player's selected starting offset, and derive the current time-of-day state. Crystal's `UpdateTime` remains a short chain of `GetClock`, `FixDays`, `FixTime`, and `GetTimeOfDay`.
|
||||
|
||||
Crystal's source shows the day counter reduced modulo 140 for the game's weekly/event model, with status flags distinguishing an RTC count beyond 139 days and a hardware day-high overflow beyond 255 days. The displayed game time is formed by adding the new-game start offsets to RTC values with carry through seconds, minutes, hours, and day.
|
||||
|
||||
The notable architectural addition is the Crystal timer interrupt's Mobile dispatch:
|
||||
|
||||
```asm
|
||||
Timer::
|
||||
push af
|
||||
ldh a, [hMobile]
|
||||
and a
|
||||
jr z, .not_mobile
|
||||
call MobileTimer
|
||||
.not_mobile
|
||||
pop af
|
||||
reti
|
||||
```
|
||||
|
||||
Source: [Crystal `home/time.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/home/time.asm#L2-L11). The ordinary RTC logic remains inherited; Japanese/mobile Crystal adds another timing consumer.
|
||||
|
||||
For emulators, flash cartridges, and ports, SRAM alone is insufficient. RTC registers, halt/carry state, elapsed host time, and game offsets must be preserved consistently. A save imported without corresponding RTC state may be structurally valid yet produce incorrect daily events, phone behavior, berries, swarms, day-of-week encounters, or time-dependent evolutions.
|
||||
|
||||
## Battles and encounters
|
||||
|
||||
### Battle-engine inheritance
|
||||
|
||||
The core Gen II battle model is shared: turn selection, speed ordering, accuracy, damage, stat stages, held items, status, volatile effects, experience, capture, AI, and link synchronization follow the same broad engine. Crystal's battle core is larger principally because it adds Battle Tower integration, additional presentation paths, and selective corrections.
|
||||
|
||||
Crystal is therefore mechanically closer to a patched and extended Gold/Silver than to a later-generation ruleset. There are no abilities, natures, modern physical/special move split, or rewritten damage model. Its new content operates within Gen II's existing move and Pokémon structures.
|
||||
|
||||
### Control-flow difference: Battle Tower exclusions
|
||||
|
||||
Gold/Silver's `BadgeStatBoosts` exits for link battles and otherwise applies badge boosts. Crystal adds a second early return when `wInBattleTowerBattle` is nonzero:
|
||||
|
||||
```asm
|
||||
ld a, [wLinkMode]
|
||||
and a
|
||||
ret nz
|
||||
|
||||
ld a, [wInBattleTowerBattle]
|
||||
and a
|
||||
ret nz
|
||||
```
|
||||
|
||||
Source: [Crystal `BadgeStatBoosts`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/engine/battle/core.asm#L6526-L6532). Gold/Silver have only the link-mode guard at the corresponding point.
|
||||
|
||||
This tiny six-instruction addition is representative of Crystal's design. The stat algorithm was not replaced; Crystal introduced a new battle context and inserted a guard so adventure-only badge advantages do not leak into the standardized Battle Tower. Similar context checks appear around experience and other post-battle handling.
|
||||
|
||||
### Selective fixes constrained by link compatibility
|
||||
|
||||
The pret Crystal bug documentation explicitly states that Crystal fixed Gold/Silver's Reflect/Light Screen overflow and Present damage behavior only where doing so would not break ordinary cross-version link battles. Link-mode behavior retains the compatible path.
|
||||
|
||||
That is an important historical engineering tradeoff. A fully corrected Crystal battle engine would disagree with Gold/Silver on intermediate values and potentially on RNG consumption, damage, fainting, or message sequence. Compatibility with the installed base was treated as a protocol requirement. Consequently, "Crystal fixed the bug" can mean **fixed in single-player but deliberately retained in linked simulation**.
|
||||
|
||||
### Shared and persistent battle defects
|
||||
|
||||
Crystal retains a substantial Gen II defect surface. Documented examples include:
|
||||
|
||||
| Defect | Mechanical effect | Crystal status |
|
||||
|---|---|---|
|
||||
| "100%" secondary effects | Fail in 1/256 qualifying cases | Retained |
|
||||
| Belly Drum | May maximize Attack even when the HP requirement is not properly met | Retained |
|
||||
| Berserk Gene | Confusion duration can become 256 turns or inherit stale state | Retained |
|
||||
| Confusion damage | Can receive type-item and Explosion/Self-Destruct modifiers | Retained |
|
||||
| Beat Up | Can behave incorrectly and can desynchronize link battles | Retained |
|
||||
| Return/Frustration edge | Can produce zero damage at extreme happiness values | Retained |
|
||||
| Dragon boosting item | Dragon Scale is checked instead of Dragon Fang | Retained |
|
||||
| Glacier Badge | Special Defense boost depends incorrectly on overwritten accumulator state | Retained |
|
||||
| Capture status bonus | Burn, poison, and paralysis do not contribute as intended | Retained |
|
||||
| Specialty Balls | Multiple Apricorn-ball formulas target the wrong condition or table | Retained |
|
||||
|
||||
These are documented against original Crystal code, not inferred from modern competitive summaries.
|
||||
|
||||
The Glacier Badge defect is especially instructive at ASM level. The routine shifts a badge bitfield through register `b`, calls `BoostStat`, and later reuses register `a` as if it still contained the expected badge value. `BoostStat` can overwrite `a`, so whether Special Defense receives its boost depends on the Special Attack calculation's resulting register state. The bug survived Crystal even though the source gained an explicit comment identifying it.
|
||||
|
||||
### Gold/Silver defects corrected in Crystal
|
||||
|
||||
The `pokegold` bug document separates defects fixed in Crystal from defects still shared with it. It identifies seven clear Gold/Silver corrections: Coin Case arbitrary code execution, Hall of Fame corruption when no prior save exists, Lucky Number failure to inspect boxes 10-14, Present text overflow, surfing onto NPCs, fishing inside Cerulean Gym, and Route 15 capitalization.
|
||||
|
||||
The Coin Case correction is one bytecode-level terminator change:
|
||||
|
||||
```diff
|
||||
text "Coins:"
|
||||
line "@"
|
||||
text_decimal wCoins, 2, 4
|
||||
-done
|
||||
+text_end
|
||||
```
|
||||
|
||||
Source: [`pokegold/docs/bugs_and_glitches.md`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/docs/bugs_and_glitches.md#L20-L34). The Gold/Silver terminator permits text-command execution to continue into unintended memory under exploitable conditions; Crystal ends the text stream correctly.
|
||||
|
||||
The Hall of Fame correction adds a saved-at-least-once check and erases/initializes previous-save structures before attempting the Hall of Fame write. The Lucky Number correction changes the loop bound from the Japanese box count constant to the international fourteen-box count. Both are good examples of Crystal correcting localization-sensitive state assumptions rather than changing game design.
|
||||
|
||||
### Encounter-rate algorithm
|
||||
|
||||
Gold/Silver and Crystal use essentially the same high-level encounter pipeline:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
STEP[Eligible movement step]
|
||||
RATE[Read morning/day/night or water rate]
|
||||
MOD1[Apply radio modifier]
|
||||
MOD2[Apply Cleanse Tag modifier]
|
||||
ROLL[Call Random and compare with rate]
|
||||
SLOT[Choose encounter slot]
|
||||
ROAM[Check roaming Pokémon]
|
||||
REPEL[Apply Repel level check]
|
||||
BATTLE[Stage wild battle]
|
||||
|
||||
STEP --> RATE --> MOD1 --> MOD2 --> ROLL
|
||||
ROLL -->|pass| SLOT
|
||||
SLOT --> ROAM
|
||||
ROAM --> REPEL
|
||||
REPEL --> BATTLE
|
||||
```
|
||||
|
||||
In both engines, `TryWildEncounter` gets the map rate, applies Pokémon March/Ruins of Alph doubling or Pokémon Lullaby halving, applies Cleanse Tag halving, draws an RNG byte, chooses a slot from grass or water probability tables, and checks Repel.
|
||||
|
||||
Grass records contain separate morning, day, and night slot blocks; the active block is selected through `wTimeOfDay`. Water uses a separate rate and three-slot table. The structure constants remain seven grass slots and three water slots in both codebases.
|
||||
|
||||
### Encounter-table differences
|
||||
|
||||
Gold and Silver primarily diverge through edition-conditioned tables. Crystal has its own consolidated tables and changes both availability and placement. It makes several former Gold/Silver exclusives obtainable in one edition, while removing some species available in both base versions; Mareep's evolutionary family is a prominent Crystal omission. Crystal also moves species such as Sneasel to different locations.
|
||||
|
||||
The engine/table distinction is important. An encounter may differ because:
|
||||
|
||||
1. the map's encounter-rate byte changed;
|
||||
2. a morning/day/night slot changed;
|
||||
3. the slot's level changed;
|
||||
4. a swarm override changed;
|
||||
5. a roaming Pokémon was removed from the roaming subsystem;
|
||||
6. the map itself gained or lost encounter-enabled tiles.
|
||||
|
||||
A robust diff should therefore compare map headers, wild tables, swarm flags, and roaming initialization, not merely produce a Pokédex availability list.
|
||||
|
||||
### Swarms and roaming state
|
||||
|
||||
Gold/Silver's swarm lookup is comparatively generic: it compares the current map against `wSwarmMapGroup` and `wSwarmMapNumber`. Crystal's code has explicit Dunsparce and Yanma swarm flags and corresponding map state. That is a data-model specialization, not just a changed encounter table.
|
||||
|
||||
The roaming difference is even clearer. Gold/Silver initialize three records:
|
||||
|
||||
```asm
|
||||
ld a, RAIKOU
|
||||
ld [wRoamMon1Species], a
|
||||
ld a, ENTEI
|
||||
ld [wRoamMon2Species], a
|
||||
ld a, SUICUNE
|
||||
ld [wRoamMon3Species], a
|
||||
```
|
||||
|
||||
Crystal initializes only Raikou and Entei:
|
||||
|
||||
```asm
|
||||
ld a, RAIKOU
|
||||
ld [wRoamMon1Species], a
|
||||
ld a, ENTEI
|
||||
ld [wRoamMon2Species], a
|
||||
```
|
||||
|
||||
Sources: [Gold/Silver `InitRoamMons`](https://github.com/pret/pokegold/blob/a0dad0957ac8a9ffa67e950ee3ab6715a212ded5/engine/overworld/wildmons.asm#L471-L511) and [Crystal `InitRoamMons`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/engine/overworld/wildmons.asm#L476-L506).
|
||||
|
||||
Gold/Silver's subsequent selection logic describes an equal choice among three beasts after the roaming check succeeds; Crystal changes the corresponding comment and index range to two. Suicune's removal is tied to Crystal's expanded Eusine/Suicune plot and scripted Tin Tower encounter.
|
||||
|
||||
For save conversion, a Gold/Silver Suicune roaming record cannot simply remain active in Crystal. The converter must map capture/defeat/event state into Crystal's scripted Suicune flags or deliberately define a hybrid behavior.
|
||||
|
||||
## Graphics and audio
|
||||
|
||||
### Tile, palette, and sprite model
|
||||
|
||||
All three games retain the Game Boy's tile-oriented rendering model: graphics are stored as compact tile data, decompressed or copied into VRAM, arranged through background/window tilemaps, and supplemented by hardware sprites through shadow OAM. Pokémon and many UI images use four-color source palettes, with separate palette data determining their actual colors. The pokecrystal FAQ explicitly notes the four-color paletted-PNG convention and the distinction between image data and palette data.
|
||||
|
||||
Gold/Silver must support original Game Boy-style output and Super Game Boy behavior as well as Game Boy Color enhancements. Crystal's explicit Color-only startup path lets it make stronger use of CGB layout and palette machinery. Its top-level architecture adds `crystal_layouts.asm`, dedicated player graphics, revised map and battle presentation, and other CGB-focused modules.
|
||||
|
||||
### Animated Pokémon sprites
|
||||
|
||||
Crystal's headline renderer change is animated front sprites. Every Pokémon receives an entrance animation, and the status/profile viewer can play a longer animation; several designs, palettes, and back sprites were also revised. Crystal additionally gives the legendary beasts distinct overworld sprites and introduces richer trade-screen presentation.
|
||||
|
||||
At the engine level, this requires more than storing extra frames. A full animation path needs:
|
||||
|
||||
- a base front picture and additional frame or bitmask data;
|
||||
- frame sequencing and duration state;
|
||||
- tile-buffer reconstruction or differential tile updates;
|
||||
- synchronization with battle intro control flow;
|
||||
- palette and VRAM updates during safe LCD periods;
|
||||
- fallback behavior for static contexts such as link displays or icons.
|
||||
|
||||
Crystal's `load_pics.asm` is correspondingly larger and is integrated with dedicated animation and battle-intro modules, while Gold/Silver's loader primarily services static front/back pictures. The exact file-size difference should not be treated as a performance measurement, but the added code paths confirm a broader graphics pipeline.
|
||||
|
||||
Animated fronts can also alter RNG-observable timing indirectly. The ordinary RNG is updated from divider timing and VBlank activity; therefore, any test that compares post-animation random outcomes must control whether animations are enabled and how many frames elapsed. This is an inference from the animation and RNG architectures, not evidence that every animation directly invokes `Random`.
|
||||
|
||||
### Color and protagonist handling
|
||||
|
||||
Crystal adds player-gender initialization and Kris-specific bag/player graphics. The Pokémon record's caught-data bits can record whether the catcher was the boy or girl protagonist. This is an example of a feature spanning UI, overworld sprites, menus, Pokémon metadata, and save state rather than existing in one isolated "female player" switch.
|
||||
|
||||
A Crystal-to-Gold/Silver backport must decide how to handle caught-by-girl metadata when Gold/Silver have no female protagonist UI. Structurally the bytes can survive because they were reserved, but Gold/Silver will not natively display or generate the metadata.
|
||||
|
||||
### Sound-engine architecture
|
||||
|
||||
The most striking audio result is how little the interpreter changed. Both repositories' `audio/engine.asm` identify themselves as the entire sound engine, update once per frame, parse music commands, maintain eight software channel structures (four music and four sound-effect channels), and ultimately drive the four Game Boy audio channels. Both implementations handle duty, envelope, frequency, vibrato, noise sampling, channel muting, low-health sound, and fades through the same broad code.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
SONG[Song bytecode]
|
||||
PARSE[ParseMusic]
|
||||
STATE[Per-channel WRAM state]
|
||||
FX[Vibrato / pitch / envelope / noise]
|
||||
MIX[Music-SFX priority and routing]
|
||||
APU[Game Boy audio registers]
|
||||
|
||||
SONG --> PARSE --> STATE --> FX --> MIX --> APU
|
||||
```
|
||||
|
||||
Crystal therefore does not introduce a new synthesizer. It extends the music corpus and invokes new songs in new contexts.
|
||||
|
||||
### Crystal's additional music
|
||||
|
||||
Gold/Silver's song pointer table ends at `Music_PostCredits`. Crystal retains that ordering and appends ten entries:
|
||||
|
||||
| Crystal-only pointer entry | Use |
|
||||
|---|---|
|
||||
| `Music_Clair` | Clair-related scene |
|
||||
| `Music_MobileAdapterMenu` | Japanese Mobile menu |
|
||||
| `Music_MobileAdapter` | Mobile connectivity |
|
||||
| `Music_BuenasPassword` | Buena's Password |
|
||||
| `Music_LookMysticalMan` | Eusine encounter |
|
||||
| `Music_CrystalOpening` | Revised opening |
|
||||
| `Music_BattleTowerTheme` | Battle Tower battle/context |
|
||||
| `Music_SuicuneBattle` | Legendary-beast/Suicune battle theme |
|
||||
| `Music_BattleTowerLobby` | Battle Tower lobby |
|
||||
| `Music_MobileCenter` | Japanese Mobile Center |
|
||||
|
||||
The pointer table labels these explicitly as "new to Crystal."
|
||||
|
||||
Bulbapedia independently notes that Crystal gives the legendary beasts a unique battle theme and presents it as the first core-series special legendary battle music.
|
||||
|
||||
From a porting perspective, bringing Crystal music into Gold/Silver primarily requires assigning ROM space, importing sequence data, adding pointer/constants entries, and adding selection call sites. Replacing the audio engine is generally unnecessary. However, preserving existing numeric song IDs is wise because map headers, scripts, battle setup, radio state, and fades refer to those constants.
|
||||
|
||||
## Bugs, compatibility, porting, and reverse-engineering timeline
|
||||
|
||||
### Bug and quirk matrix
|
||||
|
||||
| Category | Gold/Silver | Crystal | Engineering interpretation |
|
||||
|---|---|---|---|
|
||||
| Coin Case text terminator | Exploitable continuation can permit arbitrary code execution | Correct terminator | Parser/data correction |
|
||||
| First-save Hall of Fame | Can corrupt PC boxes | Guard and initialization added | Save-state precondition correction |
|
||||
| Lucky Number boxes | International boxes 10-14 omitted | Uses full box count | Localization constant corrected |
|
||||
| Surf onto NPC | Possible | Facing-object check added | Collision precondition corrected |
|
||||
| Cerulean Gym fishing | Enabled by map fish group | Disabled | Map-header data correction |
|
||||
| Reflect/Light Screen overflow | Incorrect | Fixed outside compatible link path | Context-dependent battle fix |
|
||||
| Present damage | Incorrect | Fixed outside compatible link path | Protocol-preserving fix |
|
||||
| Glacier Badge Special Defense | Bugged | Still bugged | Shared inherited defect |
|
||||
| Apricorn specialty balls | Multiple formula defects | Still defective | Shared inherited mechanics |
|
||||
| Beat Up synchronization | Vulnerable | Still vulnerable | Shared link-engine defect |
|
||||
| Secondary-effect 1/256 failure | Present | Present | Shared probability comparison defect |
|
||||
| Animated-sprite quirks | Not applicable | New animation-specific defects possible | Feature expansion creates new failure surface |
|
||||
|
||||
The Gold/Silver and Crystal bug documents should be read together. The Gold/Silver document intentionally lists only bugs that Crystal fixed; any shared defects are documented in the Crystal repository instead.
|
||||
|
||||
Crystal is thus more polished but not mechanically "corrected" in a comprehensive sense. A modern source port must choose a compatibility target:
|
||||
|
||||
- **Retail-faithful:** preserve all version-specific bugs and timing.
|
||||
- **Crystal-faithful:** preserve Crystal's selective fixes and its link-mode exceptions.
|
||||
- **Corrected Gen II:** repair documented defects, accepting that link compatibility and historical RNG traces may change.
|
||||
- **Hybrid:** gate corrections behind flags or negotiate them between identical modified link peers.
|
||||
|
||||
### ROM revisions and regional code
|
||||
|
||||
The Crystal repository builds international v1.0, v1.1, and Australian releases. Its FAQ states that v1.1 corrected some issues in the initial international release, while the Australian build is based on v1.1 and censors gambling references. Thus "Crystal behavior" is not completely singular even within English-language retail ROMs.
|
||||
|
||||
A serious test report should always state the target hash rather than merely "Pokémon Crystal." Otherwise, an observed difference may be edition-level, v1.0/v1.1-level, Australian localization, Japanese Mobile code, Virtual Console patching, emulator behavior, or an altered ROM.
|
||||
|
||||
### Link and trade compatibility
|
||||
|
||||
Gold, Silver, and Crystal share the Gen II Pokémon record length and battle/link architecture, allowing ordinary same-generation trading and battling. The retained two-byte record size, shared `REDMON_STRUCT_LENGTH` conversion constant for Time Capsule interaction, and synchronized battle RNG are all source-level evidence of compatibility-oriented design.
|
||||
|
||||
However, three distinct notions of compatibility must not be conflated:
|
||||
|
||||
| Compatibility type | Status |
|
||||
|---|---|
|
||||
| Pokémon record compatibility | Strong: same record length; Crystal fills reserved bytes |
|
||||
| Link protocol/battle simulation compatibility | Strong for retail games, partly because Crystal preserves old link behavior |
|
||||
| Whole-save-file compatibility | Weak: Crystal's SRAM organization and added state differ substantially |
|
||||
| ROM patch-address compatibility | Weak: bank layout and code placement differ |
|
||||
| Feature-source portability | Moderate: shared architecture helps, but dependencies are broad |
|
||||
| Gen I Time Capsule data compatibility | Supported through dedicated conversion structures and restrictions |
|
||||
| Modern transfer compatibility | Supported from 3DS VC releases through Poké Transporter and Pokémon Bank |
|
||||
|
||||
Nintendo's support documentation lists the Virtual Console releases of Gold, Silver, and Crystal as compatible with Poké Transporter. Transfers proceed into Pokémon Bank and can then move onward to Pokémon HOME, but that modern path is not a raw Gen II save conversion and is effectively one-way at later stages.
|
||||
|
||||
### Virtual Console modifications
|
||||
|
||||
Crystal's save code contains an explicit `vc_hook` after Hall of Fame insertion. The hook sets the primary and backup GS Ball flags to make the GS Ball quest and Celebi encounter available, with compile-time assertions pinning the expected SRAM addresses and flag value.
|
||||
|
||||
```asm
|
||||
vc_hook Enable_GS_Ball_mobile_event
|
||||
vc_assert BANK(sGSBallFlag) == $1
|
||||
vc_assert BANK(sGSBallFlagBackup) == $1
|
||||
```
|
||||
|
||||
Source: [Crystal `engine/menus/save.asm`](https://github.com/pret/pokecrystal/blob/8e8f7e20052a596371a77022f0392c285e51bbf1/engine/menus/save.asm#L157-L167).
|
||||
|
||||
This is a fascinating compatibility shim: the original international cartridge retained GS Ball-related structures but lacked the original Japanese distribution path, so the Virtual Console wrapper activates the event through a targeted runtime hook. The Pokémon Company confirms that the Virtual Console version permits the Celebi encounter at Ilex Forest's shrine.
|
||||
|
||||
The pret repositories also contain `vc` build material, and community GitHub discussion has addressed preserving or generating Virtual Console patches. A port targeting 3DS VC behavior should therefore compare not only the retail ROM but the external patch/hook layer.
|
||||
|
||||
### Japanese Crystal and mapper/SRAM concerns
|
||||
|
||||
Crystal's SRAM source reserves several Mobile sections and explicitly comments that a Mobile Easy Chat initialization routine uses an "MBC30 bank" available to Japanese Crystal but inaccessible with ordinary MBC3 behavior. This is direct evidence that regional hardware assumptions matter when emulating or reproducing Japanese Mobile features.
|
||||
|
||||
An implementation that supports only the common international MBC3-style SRAM/RTC configuration may run the international game correctly yet fail Japanese Crystal's extended Mobile storage accesses. Conversely, allocating the larger memory blindly does not implement Mobile Adapter protocols, timer behavior, ranking checksums, or server-era workflows.
|
||||
|
||||
### Porting guidance
|
||||
|
||||
The safest migration strategy between the two disassemblies is subsystem-oriented:
|
||||
|
||||
| Port direction | Recommended approach | Main hazard |
|
||||
|---|---|---|
|
||||
| Gold <-> Silver data | Preserve shared engine; switch edition tables/assets | Conditional data references and event assumptions |
|
||||
| Crystal feature -> Gold/Silver | Import feature plus all WRAM/SRAM, graphics, script, audio, and menu dependencies | DMG/SGB fallback and bank-space pressure |
|
||||
| Gold/Silver behavior -> Crystal | Replace data or selectively restore old branch | Crystal story/event state may expect new semantics |
|
||||
| Gold/Silver save -> Crystal | Parse and rebuild symbolic fields | Relocated blocks and Crystal-only state |
|
||||
| Crystal save -> Gold/Silver | Strip Crystal state and preserve common Pokémon/player data | Caught metadata becomes opaque; Suicune/Battle Tower state has no target |
|
||||
| Retail battle fix | Gate by non-link mode or require identical modified peers | RNG/control-flow desynchronization |
|
||||
| Crystal graphics -> DMG-compatible engine | Create monochrome layouts or make build CGB-only | Palette attributes and animation timing |
|
||||
| Japanese Mobile feature port | Emulate mapper, extended SRAM, timer, and communication assumptions | Hardware and defunct-service dependencies |
|
||||
|
||||
Bank space is a concrete constraint. The pokecrystal FAQ describes the international ROM as 2 MiB divided across banks and notes that adding features can overflow fixed bank placement, requiring sections to be moved through the linker script. Crystal's extra features already consume carefully arranged banks, so a port that compiles at the object level can still fail at link time.
|
||||
|
||||
The deepest practical rule is: **port invariants before routines**. Before copying code, define the target's expected record lengths, bank-switch convention, WRAM variables, SRAM addresses, script command set, palette mode, RNG call contract, and link-mode semantics. Once those invariants match, most inherited Gen II routines are straightforward. Without them, a perfectly copied routine can read the wrong field, switch to the wrong bank, write outside a save block, or desynchronize a link battle.
|
||||
|
||||
### Release and reverse-engineering evidence timeline
|
||||
|
||||
```mermaid
|
||||
timeline
|
||||
title Gold, Silver, and Crystal technical lineage
|
||||
1999-11-21 : Gold and Silver released in Japan
|
||||
2000-10-15 : Gold and Silver released in North America
|
||||
2000-12-14 : Crystal released in Japan
|
||||
2001 : International Crystal releases
|
||||
: Crystal adds Color-only presentation, animations, Battle Tower, and expanded Suicune story
|
||||
2017-09-22 : Gold and Silver released for Nintendo 3DS Virtual Console
|
||||
2018-01-26 : Crystal released for Nintendo 3DS Virtual Console
|
||||
: VC hook enables the GS Ball and Celebi event
|
||||
2026 : pret repositories reproducibly build exact retail revisions
|
||||
: ASM-level bug catalogs and symbolic layouts support controlled comparison
|
||||
```
|
||||
|
||||
Gold and Silver launched in Japan on November 21, 1999 and in North America on October 15, 2000; Crystal followed in Japan on December 14, 2000 and internationally in 2001. Gold/Silver reached 3DS Virtual Console in September 2017 and Crystal followed in January 2018.
|
||||
|
||||
The current reverse-engineering record is better described as an evolving evidence base than as a single "discovery date." pret's labels, comments, and bug documentation have accumulated over thousands of repository commits, while the exact-build hashes provide a stable anchor to the retail binaries. Crystal's repository currently has substantially more history and feature documentation than `pokegold`, reflecting its role as the community's principal Gen II hacking platform rather than evidence that Crystal's retail source was inherently better documented.
|
||||
|
||||
### Final technical assessment
|
||||
|
||||
Gold and Silver are functionally sibling configurations of one engine. Their meaningful differences are overwhelmingly table-, asset-, and version-flag-driven. Crystal preserves that engine's defining architecture but turns it into a more specialized platform: Color-only graphics, animated Pokémon, richer event scripting, expanded persistent state, Mobile-era infrastructure, Battle Tower contexts, caught metadata, additional music, and selective defect correction.
|
||||
|
||||
At ASM level, the most consequential changes are not usually exotic algorithms. They are small branches with large semantic reach:
|
||||
|
||||
- a Battle Tower early return suppresses badge boosts;
|
||||
- two reserved Pokémon bytes acquire caught metadata without changing record size;
|
||||
- Suicune disappears from `InitRoamMons`;
|
||||
- save blocks become reorganized and gain new persistent domains;
|
||||
- a text terminator closes the Coin Case execution path;
|
||||
- link-mode checks preserve old arithmetic to avoid desynchronization;
|
||||
- a Virtual Console hook activates an otherwise inaccessible event;
|
||||
- a Mobile timer branch attaches a new subsystem to the interrupt path.
|
||||
|
||||
That pattern explains both Crystal's compatibility and its porting difficulty. It remains recognizably the Gold/Silver engine, but its new features are woven through banks, state structures, control-flow conditions, graphics timing, SRAM, scripts, and presentation. The code is cousin-shaped, not copy-paste-shaped: the classic reverse-engineering booby trap wearing a tiny Suicune hat.
|
||||
@@ -12,7 +12,7 @@ this port.
|
||||
|
||||
| # | Mechanic | Value | Why the Game Boy had this limit | Where it lives here |
|
||||
|---|---|---|---|---|
|
||||
| 1 | Bag capacity | 20 item slots | `wNumBagItems` save block was a fixed 20-entry id/quantity array in SRAM | `src/inventory/Bag.lua:8` (`Bag.CAPACITY = 20`) |
|
||||
| 1 | Bag capacity | 20 item slots by default | `wNumBagItems` save block was a fixed 20-entry id/quantity array in SRAM | `Data.constants.bagSize`, read by `src/inventory/Bag.lua` (`Bag.capacity`) |
|
||||
| 2 | Party size | 6 Pokémon | `wPartyMon1..6` were 6 fixed save-RAM slots | `src/pokemon/Party.lua:5` (`Party.MAX = 6`) |
|
||||
| 3 | PC storage | 12 boxes × 20 Pokémon | `wBoxDataStart` / Bill's PC allocated a fixed 12×20 SRAM block | `src/pokemon/Boxes.lua:7-8` |
|
||||
| 4 | Moves per Pokémon | 4 | Fixed 4-move-slot field in the party/box Pokémon struct | `src/pokemon/Pokemon.lua:20`, enforced again in `src/battle/BattleState.lua:1941` |
|
||||
@@ -38,6 +38,10 @@ this port.
|
||||
|
||||
## Notes
|
||||
|
||||
- Mods may patch `constants.bagSize` through the public content registry. The
|
||||
native `save.lua` format keeps every existing item when the configured
|
||||
limit changes; exporting to a cartridge `.sav` still writes only the first
|
||||
20 bag slots because the original SRAM layout has no room for more.
|
||||
- PC Box **overflow handling** was deliberately changed even though the
|
||||
20×12 box *shape* was kept faithful: instead of Gen 1's "full box discards
|
||||
or blocks the deposit," this port spills into the next box with room.
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
# Gen 2 link play: what it takes
|
||||
|
||||
This is a design document, not a feature announcement. Gold cannot link today
|
||||
and nothing in this document changes that on its own. What it does is state
|
||||
honestly what Gen 2 link play requires, where the Gen 1 protocol in `src/link/`
|
||||
stops working on a Gen 2 record, what the cart itself did, and which pieces of
|
||||
the work are self-contained enough to have been built already.
|
||||
|
||||
The short version: the transport, the handshake and the fingerprint are
|
||||
generation-agnostic or nearly so, and those are done. The party wire format is
|
||||
half done (a codec exists, nothing sends it). The trade session, the trade UI
|
||||
and the lockstep battle are not started, and each is a real piece of work.
|
||||
|
||||
Read `docs/mod-api-gen2-compat.md` beside this: it is the reference for what a
|
||||
Gold mod may touch, and the link fingerprint's whole job is to hash exactly that
|
||||
surface.
|
||||
|
||||
## 1. What Gen 1 link play is
|
||||
|
||||
`src/link/` is nine files and about four thousand lines:
|
||||
|
||||
| File | Job |
|
||||
| --- | --- |
|
||||
| `Net.lua` | lua-enet transport, LAN host/join plus a relay for online play. JSON messages, no game types. |
|
||||
| `CodeEntry.lua` | the online join code widget. |
|
||||
| `Handshake.lua` | the `hello` both peers exchange and the compatibility verdict drawn from the two of them. |
|
||||
| `Fingerprint.lua` | a deterministic digest of the link surface: the slice of merged Data whose value decides whether two lockstep simulations stay identical. |
|
||||
| `Protocol.lua` | mon serialization, the record-subset negotiation, and the trade session state machine. |
|
||||
| `LinkState.lua` | the LINK menu, pairing, and the trade/battle hand-off. |
|
||||
| `LinkBattle.lua` | lockstep battle: both machines run `src/battle/BattleState.lua` from mirrored perspectives on a shared seed, exchanging one action per turn and a per-turn state hash. |
|
||||
| `Tournament.lua` | bracketed online play over the relay. |
|
||||
| `Json.lua` | the encoder the wire and several unrelated callers share. |
|
||||
|
||||
Only three of those nine are shaped around Gen 1 game content: `Fingerprint`,
|
||||
`Protocol` and `LinkBattle`. `Net`, `Json`, `CodeEntry` and most of `Handshake`
|
||||
never look at a Pokemon.
|
||||
|
||||
## 2. What the cart did
|
||||
|
||||
pokegold's link code is `engine/link/link.asm` (2508 lines), and it is worth
|
||||
being precise about, because several of the port's design questions have a cart
|
||||
answer.
|
||||
|
||||
**Three rooms, one wire.** `wLinkMode` is `LINK_TIMECAPSULE`,
|
||||
`LINK_TRADECENTER` or `LINK_COLOSSEUM` (`constants/serial_constants.asm`).
|
||||
`LinkCommunications` branches once, at the top, on whether the mode is the Time
|
||||
Capsule: `Gen2ToGen1LinkComms` for the Time Capsule and `Gen2ToGen2LinkComms`
|
||||
for everything else (`engine/link/link.asm:33`). Everything after that branch is
|
||||
shared -- the same byte exchange serves trading and battling, and the mode only
|
||||
decides what is in the buffer and what the game does afterwards.
|
||||
|
||||
**What crosses the wire, in order** (`Gen2ToGen2LinkComms`,
|
||||
`engine/link/link.asm:202`):
|
||||
|
||||
1. `SERIAL_RN_PREAMBLE_LENGTH + SERIAL_RNS_LENGTH` bytes: the shared battle
|
||||
RNG state. Seven preamble bytes and ten seeds.
|
||||
2. `SERIAL_PREAMBLE_LENGTH + NAME_LENGTH + (1 + PARTY_LENGTH + 1) + 2 +
|
||||
(PARTYMON_STRUCT_LENGTH + NAME_LENGTH * 2) * PARTY_LENGTH + 3` bytes: the
|
||||
player name, the party count and species list, the **trainer ID**, six party
|
||||
structs, six OT names and six nicknames.
|
||||
3. `SERIAL_PATCH_LIST_LENGTH` bytes: the patch list (see below).
|
||||
4. In the Trade Center only, the mail block: six mail messages then six mail
|
||||
metadata structs (`Link_PrepPartyData_Gen2`, `engine/link/link.asm:810`).
|
||||
|
||||
**The shared PRNG is the whole trick behind lockstep.** `_BattleRandom`
|
||||
(`engine/battle/core.asm:6650`) refuses the normal RNG whenever `wLinkMode` is
|
||||
non-zero and pulls from `wLinkBattleRNs` instead: ten seeds, each advanced by
|
||||
`a[n+1] = (a[n] * 5 + 1) % 256` when the stream runs out, with the count in
|
||||
`wLinkBattleRNCount`. Both machines exchange those ten bytes once, before the
|
||||
battle, and never again -- the external-clock side adopts the internal-clock
|
||||
side's numbers (`Link_CopyRandomNumbers`, `engine/link/link.asm:1115`). The port
|
||||
does the same thing with a Park-Miller stream seeded by the host
|
||||
(`LinkBattle.makeRng`), which is the same idea in a different arithmetic.
|
||||
|
||||
**The patch list exists because the wire has reserved bytes.**
|
||||
`SERIAL_NO_DATA_BYTE` (`$fe`) may not appear in the payload, so
|
||||
`FixDataForLinkTransfer` (`engine/link/link.asm:556`) walks the party block,
|
||||
replaces every `$fe` with `$ff`, and records the offsets it touched in a
|
||||
200-byte list that ships alongside. Mail gets its own smaller version of this in
|
||||
`Link_PrepPartyData_Gen2`. The port's wire is JSON over enet and has no reserved
|
||||
bytes, so this whole mechanism has no analogue and needs none. It is worth
|
||||
knowing about only so nobody reimplements it by accident.
|
||||
|
||||
**The Time Capsule is the cart's own answer to a cross-generation link.**
|
||||
`CheckTimeCapsuleCompatibility` (`engine/link/link.asm:1970`) refuses the party
|
||||
outright for exactly three reasons, and returns which one in `wScriptVar`:
|
||||
|
||||
1. a species at or above `JOHTO_POKEMON` (`constants/pokemon_constants.asm:168`,
|
||||
the 152 boundary),
|
||||
2. a move above `STRUGGLE`, i.e. any move Gen 1 does not have,
|
||||
3. any party member holding mail.
|
||||
|
||||
If the party passes, `Link_PrepPartyData_Gen1` (`engine/link/link.asm:640`)
|
||||
rewrites every mon into the 44-byte `REDMON_STRUCT_LENGTH` layout:
|
||||
`ConvertMon_2to1` remaps the species index through `Pokered_MonIndices`
|
||||
(`engine/link/time_capsule_2.asm:1`), the Special stat is recomputed from
|
||||
`KantoMonSpecials` because Gen 1 has one Special and Gen 2 has two, and
|
||||
Magnemite and Magneton are shipped as pure Electric because their typing changed
|
||||
(`engine/link/link.asm:731`). Coming the other way, `Link_ConvertPartyStruct1to2`
|
||||
(`engine/link/link.asm:930`) reads the Gen 1 catch rate byte as the held item
|
||||
slot -- garbage, which is why `TimeCapsule_ReplaceTeruSama`
|
||||
(`engine/link/link.asm:1078`) maps the handful of catch rates that collide with
|
||||
real items through `data/items/catch_rate_items.asm` and turns everything else
|
||||
into a Berry. `ValidateOTTrademon` (`engine/link/time_capsule.asm:1`) then
|
||||
re-checks the incoming mon's types against the local base data and refuses
|
||||
anything that does not agree, with Magnemite and Magneton carved out again.
|
||||
|
||||
The lesson the port should take from all of that: a cross-generation link is not
|
||||
a compatibility mode, it is a lossy conversion with a validator on each end, and
|
||||
the cart wrote roughly 400 lines for it. Refusing the pairing is the honest
|
||||
default until somebody wants to write those 400 lines.
|
||||
|
||||
## 3. Where the Gen 1 protocol breaks on a Gen 2 record
|
||||
|
||||
`Protocol.packMon` / `Protocol.unpackMon` are shaped around
|
||||
`src/pokemon/Pokemon.lua`. Against `src/battle/gen2/Mon.lua`, every one of these
|
||||
is wrong:
|
||||
|
||||
| Gen 1 assumption | Gen 2 reality | Where |
|
||||
| --- | --- | --- |
|
||||
| `mon.exp` | `mon.experience` | `Mon.new`, `Mon.gainExperience` |
|
||||
| five DVs including `special` | four rolled DVs (`attack`/`defense`/`speed`/`special`) with `hp` **derived** from their low bits | `Mon.hpDV` |
|
||||
| `special` is one stat | `specialAttack` and `specialDefense` are two stats off one DV | `Mon.stats` |
|
||||
| `statExp` has five words | five words still, but the fifth feeds both special stats | `Mon.stats` |
|
||||
| no held item | `mon.item` decides damage, healing and flinching | `src/battle/gen2/Battle.lua` `heldEffect` |
|
||||
| stats recomputed with `src/pokemon/Stats.lua` | must go through `Mon.stats`, which is a different formula shape | `Mon.stats` |
|
||||
| exp curve via `src/pokemon/Growth.lua` (code) | curve coefficients are **data**, at `data.pokemon.growthRates` | `Mon.experienceForLevel` |
|
||||
| `mon.status` is `"SLP"` / `"PSN"` / `"BRN"` / `"PAR"` / `"FRZ"` | `"sleep"` / `"poison"` / `"burn"` / `"paralyze"` / `"freeze"` / `"toxic"` | `src/battle/Status.lua:62` vs `src/battle/gen2/Battle.lua:65` |
|
||||
| `mv.ppUps` | Gen 2 has no PP Ups modelled yet; moves carry `id`, `pp`, `maxPp` | `Mon.movesAtLevel` |
|
||||
| nothing else on the mon | `happiness`, `pokerus`, `caughtLevel`, `isEgg`/`eggSteps`, and the derived `shiny` / `gender` / `unownLetter` | `Mon.new` |
|
||||
| mail does not exist | mail is a **separate** block keyed by party slot, not a mon field | `src/core/gen2/Mail.lua:84`, and the cart agrees (`Link_PrepPartyData_Gen2`) |
|
||||
|
||||
The status spelling is the sharpest of these. `packMon` puts `mon.status` on the
|
||||
wire verbatim and `unpackMon` keeps it verbatim, so a shared codec would move
|
||||
`"PSN"` onto a Gold party where nothing recognises it -- a mon that arrives
|
||||
"poisoned" and never takes poison damage. Any Gen 2 codec has to be a separate
|
||||
function with the generation baked into it, not a Gen 1 function with extra
|
||||
optional keys.
|
||||
|
||||
Two things are *not* a problem, and it is worth saying so:
|
||||
|
||||
- **Species and move ids are shared name spaces.** Both generations key
|
||||
`data.pokemon` and `data.moves` by the same `TOTODILE` / `TACKLE` strings, so
|
||||
the wire never needs the cart's index remap (`ConvertMon_2to1`). Ids, not
|
||||
bytes, is what makes the port's protocol simpler than the cable's.
|
||||
- **The derived fields do not travel.** `shiny`, `gender` and `unownLetter` are
|
||||
all functions of the DVs (`Mon.isShiny`, `Mon.gender`, `Unown.letterFromDVs`),
|
||||
so the receiver recomputes them and a tampered packet cannot claim a shiny it
|
||||
did not roll. Same reasoning as `unpackMon` recomputing stats.
|
||||
|
||||
`LinkBattle.lua` breaks in a larger way. It calls
|
||||
`BattleState.makeBattler`, `src/battle/TurnOrder.lua` and Gen 1's damage
|
||||
pipeline directly. Gold's battle is `src/battle/gen2/Battle.lua`, a different
|
||||
object with a different event shape. The one piece of good news is that
|
||||
`Battle.new` already takes `opts.random` (`src/battle/gen2/Battle.lua:220`) --
|
||||
the injection seam a lockstep battle needs is there and does not have to be cut.
|
||||
|
||||
## 4. The wire format for Gen 2
|
||||
|
||||
Message types stay the ones `Protocol.lua` already documents (`hello`,
|
||||
`records`, `party`, `pick`, `confirm`, `action`, `event`, `bye`). Gen 2 adds
|
||||
fields, never renames -- the same rule the mod seams follow.
|
||||
|
||||
**`hello`** gains one field, `generation` (1 or 2). A peer that omits it is
|
||||
generation 1 by construction: no released build has ever had Gen 2 link, so
|
||||
absent means Red/Blue/Yellow. This is implemented.
|
||||
|
||||
**`party`** carries a list of packed mons whose shape is the Gen 2 one:
|
||||
|
||||
```
|
||||
{ species, level, experience, hp, status, nickname,
|
||||
dvs = { attack, defense, speed, special }, -- hp derived
|
||||
statExp = { hp, attack, defense, speed, special },
|
||||
moves = { { id, pp } ... },
|
||||
item, happiness, pokerus, caughtLevel,
|
||||
ot, otId, isEgg, eggSteps, extra }
|
||||
```
|
||||
|
||||
and, in a trade, a parallel `mail` array indexed by the same wire position --
|
||||
separate exactly as `Link_PrepPartyData_Gen2` keeps it separate, because mail is
|
||||
SRAM the cart copies out of `sPartyMail`, not part of the party struct. The
|
||||
codec for the mon half is implemented; the mail half is not.
|
||||
|
||||
**`records`** gains a third map, `heldItems`, beside `pokemon` and `moves`, so a
|
||||
subset trade can refuse a mon whose held item the other game would rebuild
|
||||
differently. Implemented.
|
||||
|
||||
**`action` / `event`** for a lockstep Gen 2 battle are not designed here, because
|
||||
the design is downstream of a decision nobody has made yet: whether
|
||||
`src/battle/gen2/Battle.lua` grows a "replay these two actions" entry point or
|
||||
whether the link battle drives it through the same UI path a local battle uses.
|
||||
Guessing at a message shape before that is exactly the half-built protocol this
|
||||
document is meant to avoid.
|
||||
|
||||
## 5. The fingerprint on Gold
|
||||
|
||||
The fingerprint is the one part of the design that is fully answerable today,
|
||||
because it depends only on what a Gold mod can reach, and `src/mods/Schemas.lua`
|
||||
answers that exhaustively.
|
||||
|
||||
The Gen 1 surface is species, moves, the type chart, statuses, move effects,
|
||||
constants and mod-declared link fields. The Gen 2 surface is the same idea over
|
||||
different tables, plus one registry Gen 1 does not have:
|
||||
|
||||
| What | Gen 1 | Gen 2 | Why it is surface |
|
||||
| --- | --- | --- | --- |
|
||||
| species | `data.pokemon` | `data.pokemon` (Gen 2 shape) | base stats decide every damage roll; `evolutions` decides what a traded mon becomes |
|
||||
| exp curves | code (`src/pokemon/Growth.lua`) | `data.pokemon.growthRates` | the level a traded mon's experience buys |
|
||||
| moves | `data.moves` | `data.moves`, **plus `effectChance`** | Gen 2 stores the secondary-effect odds per move rather than per effect |
|
||||
| type chart | `data.type_chart` | same, **plus `foresightMatchups`** | Foresight rewrites Ghost's immunities mid-battle |
|
||||
| statuses | `data.statuses` | `data.gen2Statuses` | `statPenalty`, `cureOnSwitch`, `beforeMovePriority` |
|
||||
| move effects | `data.move_effects` | `data.gen2MoveEffects` | which effect is primary, secondary, or accuracy-checked |
|
||||
| held items | -- | `data.gen2HeldItems` | Leftovers, King's Rock, the type boosters: pure battle math, and the item travels with a traded mon |
|
||||
| link fields | `data.link_fields` | none (gated) | a mod-declared extra mon field; see below |
|
||||
|
||||
Deliberately **not** hashed on either generation, and the reasoning is the same
|
||||
in both:
|
||||
|
||||
- Names, dex entries, learnsets, TM/HM lists, sprite paths and `source`. None of
|
||||
them changes a battle turn or a trade rebuild, and sprite paths differ between
|
||||
two identical installs.
|
||||
- `catchRate` (#511). A ball thrown in a link battle is refused; hashing it split
|
||||
Red/Blue from Yellow over two bytes.
|
||||
- Balls and item effects. Link battles allow no bag items on either cart.
|
||||
- `data.gen2Constants`. It is the ROM's ordered name lists -- `mapOrder`,
|
||||
`spriteOrder`, `speciesOrder`, `heldEffectOrder` -- and it is an *index* space.
|
||||
Every dispatch in the Gen 2 simulation goes by name (`Battle.heldEffect`
|
||||
compares `record.heldEffect` strings, `moveEffectRecordFor` keys by
|
||||
`EFFECT_*`), so reordering a constants list moves no battle math. Hashing it
|
||||
would split two peers over a list neither of them dispatches on.
|
||||
- Breeding data (`eggGroups`, `eggMoves`, `eggSteps`). The Day-Care is local.
|
||||
There is no link breeding and an egg's contents are decided before it is
|
||||
traded.
|
||||
- Everything in the Gen 2-only registries that is not `held_items`:
|
||||
`phone_contacts`, `decorations`, `apricorns`, `landmarks`, `radio_channels`.
|
||||
None of them can be observed from inside a link session.
|
||||
|
||||
`genderRatio` **is** hashed, unlike anything in the breeding block, because Gen 2
|
||||
has Attract and a gender disagreement is a battle-math disagreement.
|
||||
|
||||
`link_fields` stays gated on Gen 2 (`Schemas.GEN2`). It is a mod's declaration
|
||||
that an extra mon field must survive the wire, and it can only be un-gated once
|
||||
there is a Gen 2 mon wire format with a session behind it. A registry that hashes
|
||||
into the fingerprint but that no packer reads would be a lie the fingerprint
|
||||
tells.
|
||||
|
||||
The hook keeps its Gen 1 name and its Gen 1 arity. `link.fingerprint` is called
|
||||
as `(data, mods)` on both generations, and the generation is captured in the
|
||||
vanilla closure rather than passed as a third argument -- a Gen 1 mod that wraps
|
||||
the hook and forwards `nxt(data, mods)` therefore works verbatim on Gold, which
|
||||
a third argument would have quietly broken.
|
||||
|
||||
## 6. Cross-generation pairing
|
||||
|
||||
`Handshake.checkCompat` refuses a pairing whose two `generation` fields differ,
|
||||
with reason `generation_mismatch`, before any other check. This is deliberately
|
||||
a refusal and not a Time Capsule: section 2 costs roughly 400 lines of lossy
|
||||
conversion plus two validators, and shipping a menu that pairs a Gold game with a
|
||||
Red one and then rebuilds a Cyndaquil as whatever species index 155 happens to
|
||||
be in Red would be worse than refusing.
|
||||
|
||||
If somebody does want the Time Capsule later, the cart's own rules are the spec,
|
||||
and the port has an advantage the cart did not: ids rather than indices, so
|
||||
`ConvertMon_2to1`'s remap table is unnecessary. What remains is real work:
|
||||
refuse species above the Kanto 151, refuse moves Gen 1 lacks, refuse held items
|
||||
and mail, fold `specialAttack`/`specialDefense` back into one Special
|
||||
(`KantoMonSpecials`, `data/pokemon/gen1_base_special.asm`), and re-validate types
|
||||
on arrival with Magnemite and Magneton carved out.
|
||||
|
||||
## 7. What is built, and what is left
|
||||
|
||||
**Built (this change).**
|
||||
|
||||
- `Fingerprint.generationOf(data)`: reads `data.type_chart.generation`, then the
|
||||
presence of the Gen 2-only Data namespaces, so the digest never has to be told
|
||||
which game it is running in.
|
||||
- A Gen 2 link surface in `Fingerprint.lua`, tagged `[gen2]` so a Gen 2 digest
|
||||
can never collide with a Gen 1 one, covering the table in section 5.
|
||||
- `Fingerprint.records(data, kind, generation)` for `pokemon`, `moves` and
|
||||
`held_items` on Gold, which is what a subset trade negotiates on.
|
||||
- `Handshake.hello` carries `generation`; `checkCompat` refuses a cross-generation
|
||||
pairing; `describe` explains it in the screen's own voice.
|
||||
- `held_items` counts as link surface for `Handshake.linkModified`, so a Gold mod
|
||||
that gives Leftovers a different heal is correctly locked out of online play.
|
||||
`growth_rates` counts too, and for a reason worth stating: a curve is an
|
||||
`expForLevel` function, so the fingerprint serializes it as `?` and cannot
|
||||
hash it at all. `Mon.growthFor` prefers the merged registry over the
|
||||
extractor's coefficient rows, so a mod declaring `affects_link = false` could
|
||||
otherwise rewrite every curve -- changing what level a traded mon's experience
|
||||
buys -- and be caught by neither the digest nor the online gate.
|
||||
- `Protocol.packMon2` / `unpackMon2`: the Gen 2 party-struct codec, with the
|
||||
receiver recomputing stats, experience, shininess and gender from real species
|
||||
data the way `unpackMon` does. Nothing sends it yet.
|
||||
- `Protocol.recordsMessage` and `eligibleParty` understand held items, and
|
||||
`TradeSession:_negotiate` builds our own side of the comparison by calling
|
||||
`recordsMessage` rather than open-coding a subset of it. That is not a
|
||||
refactor: the open-coded version left `heldItems` off our side only, and both
|
||||
held-item arms of `eligibleParty` are guarded on our own map, so the whole
|
||||
check was unreachable from the only caller that matters.
|
||||
|
||||
**Not built.** Sized as honestly as I can:
|
||||
|
||||
| Piece | Size | Why it is not here |
|
||||
| --- | --- | --- |
|
||||
| Mail on the wire | small (1-2 days) | needs `Mail.state(save).party` packed as a parallel array and the receiver writing it into its own slot; no consumer until the trade session exists |
|
||||
| Gen 2 trade session | medium (about a week) | `TradeSession` is generic in shape but `apply` calls `src/world/PikachuFollower`, Gen 1 pokedex fields and Gen 1 trade evolutions; Gold needs `src/core/gen2/TradeAnim.lua`, `src/core/gen2/Evolution.lua` (`EVOLVE_TRADE`) and the Gen 2 pokedex |
|
||||
| Gold LINK menu and trade UI | medium (about a week) | Gold's menus are `src/ui/gen2/`, its text box is Gen 2 chrome, and the Cable Club script needs the receptionist path; `LinkState.lua` draws with Gen 1 `TextBox` throughout |
|
||||
| Gen 2 lockstep battle | large (several weeks) | needs a deterministic-replay entry point into `src/battle/gen2/Battle.lua`, a Gen 2 action/event encoding, and a state hash over a much larger volatile-status set than Gen 1's; the `opts.random` seam exists, nothing else does |
|
||||
| Time Capsule | large | section 6 |
|
||||
| `link_fields` on Gen 2 | small, but blocked | needs the trade session first |
|
||||
|
||||
The order matters: mail, session and UI are the trade half and can ship without
|
||||
the battle half, exactly as the cart's Trade Center and Colosseum are separate
|
||||
rooms. A Gold LINK menu that offers only TRADE is a complete feature. A Gold
|
||||
LINK menu that offers BATTLE before the lockstep battle exists is the failure
|
||||
this document was written to prevent.
|
||||
@@ -0,0 +1,188 @@
|
||||
# Pokemon Gold: Gen 2 import, colour, menus, saving and battles
|
||||
|
||||
Support for a canonical **Pokemon Gold** ROM
|
||||
(SHA-1 `d8b8a3600a465308c9953dfa04f0081c05bdcb94`, 2 MiB). Gen 2 extraction
|
||||
and overworld are separate from Red/Blue/Yellow -- never branched into
|
||||
`RomExtractor.lua` / Gen 1 `Map.lua`.
|
||||
|
||||
## Pipeline
|
||||
|
||||
| Piece | Role |
|
||||
| --- | --- |
|
||||
| `src/import/Rom.lua` (`decompressLz3`) | Gen 2 graphics compression (`home/decompress.asm`) |
|
||||
| `tools/make_gold_manifest.py` → `tools/rom_manifest_gold.json` | pret/pokegold constants + `pokegold.sym` |
|
||||
| `src/import/RomExtractorGen2.lua` | Gen 2 extract (dispatched from `RomImporter` when `version == "gold"`) |
|
||||
| `src/core/GameVersion.lua` | `gold` entry (`gold/` cache, `_gold` saves) |
|
||||
| `src/world/gen2/` | COLL_* world, NPCs, events, roofs |
|
||||
| `src/script/gen2/` | Opcodes + talk-oriented VM over extracted cmds |
|
||||
| `src/core/Game2.lua` | Gold's service owner (the Gen 2 peer of `src/core/Game.lua`): boot cinema, intro menu, world, START menu |
|
||||
| `src/core/gen2/Save.lua` | `save_gold.lua`, beside the Gen 1 saves |
|
||||
| `src/ui/gen2/` | Intro menu, naming keyboard, START menu + every submenu, battle screen |
|
||||
| `src/battle/gen2/` | Damage, stats/experience, HP bar, turn engine, catching, encounters |
|
||||
| `src/render/GbcPalette.lua` | One shader: a 4-shade sheet through a GBC palette |
|
||||
|
||||
## What extracts
|
||||
|
||||
| Area | Status |
|
||||
| --- | --- |
|
||||
| Constants | species/map/tileset/move/type/sprite/environment/palette/fish orders |
|
||||
| Font | `Font` (1bpp ink-on-transparent), `FontExtra` + `Frames` borders at $79–$7E, `FontBattleExtra` |
|
||||
| Tilesets | lz3 GFX → PNG; raw Meta (128×16) + Coll (128×4 COLL_* quads); Anim/PalMap pointers |
|
||||
| Roofs | 5 roof sheets + `MapGroupRoofs` (runtime: outdoor tilesets only) |
|
||||
| Maps | all 368: blocks, connections, warps, coord/bg/object events (+ `scriptKey`) |
|
||||
| Pokemon | `BaseData` (32B), names, front/back pics, plus Unown's 26 letter forms from `UnownPicPointers` |
|
||||
| Items | Names, prices, pockets, held effects, field/battle use, descriptions, TM/HM numbers and the move each teaches |
|
||||
| Marts | `Marts`' 34 shelves in MART_* order plus `BargainShopData`'s own item/price rows (`marts.lua`) |
|
||||
| Overworld sprites | `OverworldSprites` → PNG sheets + `sprites.lua` |
|
||||
| Scripts / text | disassembled cmds + decoded strings (`scripts.lua` / `text.lua`) |
|
||||
| Initial events | `InitializeEventsScript` → `initial_events.lua` |
|
||||
| Title | Tilemap + GBC tint; cloud band; Ho-Oh frames 1–5; trail; copyright splash |
|
||||
| Oak speech | `_OakText1–7`, `PokemonProfPic` / `CalPic`, Marill front; `oak_speech.lua` |
|
||||
| Pokemon / trainer pics | lz3 + column-major → row (`ImageWriter.columnsToRows`) |
|
||||
| Audio | banks dumped to `programs.bin`; Gen 2 ChipSynth driver (`runtime = true`) |
|
||||
| Palettes | `TilesetBGPalette`, `EnvironmentColorsPointers`, `MapObjectPals`, `RoofPals`, mon/trainer/HP-bar/exp-bar palettes, per-tileset `PalMap` |
|
||||
| Moves | `Moves` + names + descriptions, effect names, real percentages |
|
||||
| Type chart | `TypeMatchups` (plus the Foresight rows) and `TypeNames` with physical/special |
|
||||
| Pokemon | Base stats, evolutions and level-up moves (`EvosAttacks`), TM/HM lists, growth rates, egg groups, gender ratio |
|
||||
| Encounters | Grass (three time-of-day slot lists), water, fishing groups, headbutt tree sets |
|
||||
| Trainers | Every class's parties with moves/items, class names, attributes, `TrainerEncounterMusic` |
|
||||
| Pokedex | Entries (kind, height, weight, text) + the New and A-Z orderings |
|
||||
| Landmarks | Town-map positions and names, plus `SpawnPoints` |
|
||||
| Icons | Party-menu mon icons and the species → icon map |
|
||||
| Menu / HUD gfx | Naming-screen chrome, battle HUD border and bar tiles, exp bar |
|
||||
| Intro movie | The three acts' composed backgrounds, sprite sheets and fire frames |
|
||||
| Battle anims | all 428 scripts, 188 objects, 185 framesets, 216 OAM sets, 40 object sheets, the six OBJ palettes |
|
||||
| Field anims | stubs |
|
||||
|
||||
Verified against a real Gold ROM: New Bark Town warps/objects/connections
|
||||
match `pokegold/maps/NewBarkTown.asm`. Retail EVENT_* numeric ids differ
|
||||
from pret’s current `const_def` order -- always take flags from the cart.
|
||||
|
||||
## Play
|
||||
|
||||
Launcher → Gold tab → Play opens `Game2` (`src/core/Game2.lua`):
|
||||
|
||||
- Copyright → GameFreak Presents → GS intro stub → title (colored tilemap,
|
||||
scrolling clouds, Ho-Oh flap, trails) → Oak speech (Marill cry + shrink)
|
||||
→ name pick → `src/world/gen2/World`
|
||||
- Drivers (`POKEPORT_DRIVER`) skip cinema straight to the map
|
||||
- New Bark Town with Johto tileset + roof overlay (outdoors only)
|
||||
- Walk on `COLL_*` permissions; doors / stairs / carpets
|
||||
- Elm's lab: scene walk-in, starter balls (`givepoke` + cutscene moves),
|
||||
Elm phone number, aide Potion on exit (`verbosegiveitem`)
|
||||
- Connected neighbor strips + seamless edge crossings (Route 29 / 27)
|
||||
- Survey zoom (`-` / `=` / wheel / `4`)
|
||||
- Chris + map/neighbor NPCs; `SPRITEMOVEDATA_*` walk / spin / stand
|
||||
- New-game flags hide story NPCs (e.g. lab cop)
|
||||
- A: talk to facing NPC or read `BGEVENT_READ` signs via Gen 2 VM + TextBox
|
||||
|
||||
Escape is START, not quit: quitting is the START menu's QUIT row and the
|
||||
intro menu's EXIT GAME. Title / map music and script SFX/cries use the Gen 2
|
||||
channel driver.
|
||||
|
||||
## Colour
|
||||
|
||||
Gen 2 is CGB-native, so colour is part of a tile's identity rather than a tint
|
||||
over a 4-shade image. `engine/gfx/color.asm` LoadMapPals is ported whole in
|
||||
`src/world/gen2/Palettes.lua`:
|
||||
|
||||
1. the clock hour picks a daytime (`engine/rtc/rtc.asm` TimesOfDay), which a
|
||||
map's own `PALETTE_*` can override (`ReplaceTimeOfDayPals`)
|
||||
2. `EnvironmentColorsPointers[environment][daytime]` names eight entries in the
|
||||
shared `TilesetBGPalette` pool
|
||||
3. outdoors only, `RoofPals[mapGroup]` overwrites `PAL_BG_ROOF` colours 1-2 --
|
||||
one roof tile sheet, a different colour per town
|
||||
4. each tile's slot comes from its tileset's `PalMap`, and each OW sprite's OBJ
|
||||
palette from `MapObjectPals[daytime]` plus its own `PAL_OW_*`
|
||||
|
||||
The map bake runs one shader pass per BG slot (eight per map) rather than one
|
||||
per tile, and re-bakes when the clock rolls into a new daytime.
|
||||
`POKEPORT_GOLD_HOUR=21` pins the hour for screenshots.
|
||||
|
||||
## Still to do
|
||||
|
||||
About fifty of AI_Smart's seventy per-effect handlers are unwritten, fishing
|
||||
and headbutt have engine support but no input path, and Mart, the summary
|
||||
screen, `.sav` interchange, Silver and mod-replaceable Gen 2 screens are all
|
||||
still open.
|
||||
|
||||
## Verify
|
||||
|
||||
```sh
|
||||
python3 tools/make_gold_manifest.py
|
||||
for t in rom_lz3 gen2_world gen2_audio gen2_oak_speech gen2_vm \
|
||||
gen2_palettes gen2_battle gen2_menus gen2_save; do
|
||||
luajit tests/${t}_test.lua
|
||||
done
|
||||
POKEPORT_IMPORT_TRACE=1 POKEPORT_IMPORT_ROM=/path/to/pokegold.gbc \
|
||||
POKEPORT_IMPORT_ONLY=1 POKEPORT_FORCE_IMPORT=1 POKEPORT_GAME=gold love .
|
||||
POKEPORT_GAME=gold love .
|
||||
# copyright → GF presents → GS intro → title → CONTINUE/NEW GAME/OPTION
|
||||
# → Oak → name → the bedroom. START opens the menu; grass starts a battle.
|
||||
|
||||
# Screenshot drivers (need a display):
|
||||
POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/gold_menu_shots.lua love .
|
||||
POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/gold_palette_shots.lua love .
|
||||
POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/gold_battle_smoke.lua love .
|
||||
POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/gold_transition_shots.lua love .
|
||||
POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/gold_teacher_scene.lua love .
|
||||
POKEPORT_GAME=gold POKEPORT_BOOT_CINEMA=1 \
|
||||
POKEPORT_DRIVER=tests/drivers/gold_boot_smoke.lua love .
|
||||
```
|
||||
|
||||
`POKEPORT_IMPORT_TRACE=1` prints each of the 26 stages as it starts, and a
|
||||
headless import that fails now says so and exits non-zero rather than sitting
|
||||
in an error state that looks exactly like a hang.
|
||||
|
||||
A Gold cache without `palettes.lua`, `moves.lua`, `encounters.lua`,
|
||||
`trainers.lua`, `pokedex.lua`, `landmarks.lua`, `icons.lua`, `menu_gfx.lua`,
|
||||
`intro.lua`, `std_scripts.lua` or `battle/hud/*.png` needs a re-import -- as
|
||||
does one whose back pics are still the front pic's size rather than 48x48 (see
|
||||
below), and one whose trainer classes have no `encounterMusic` (the sixth
|
||||
pass added `TrainerEncounterMusic` to the manifest, so the manifest has to be
|
||||
regenerated as well). So does one whose `text.lua` has no `labels` table: that
|
||||
is the by-name seed for the text no script pointer reaches (the Day-Care and
|
||||
breeding block, the mart conversation, the Hall of Fame headers), and it needs
|
||||
a regenerated manifest too, because the seed reads those symbols by name.
|
||||
|
||||
## Traps this port has already fallen into
|
||||
|
||||
Worth knowing before touching the extractor:
|
||||
|
||||
- **Back pics are always 6x6 (48x48).** `BASE_PIC_SIZE`'s low nibble describes
|
||||
the *front* pic only, so decoding a back at it reads the wrong tile count in
|
||||
the wrong number of columns and produces garbage.
|
||||
- **`#`, `<POKE>` and `<PKMN>` have no font tile.** They are compression bytes
|
||||
($54/$24/$4a) that PlaceString expands; the manifest charmap has to expand
|
||||
them or "#DEX" renders as "DEX".
|
||||
- **FishGroups has no row for FISHGROUP_NONE**, so a group's row is its id minus
|
||||
one. Walking the constant list from the top reads every row shifted.
|
||||
- **Trainer groups have no end marker** -- the next group's label follows
|
||||
immediately -- so a party scan needs the class's own member list to bound it.
|
||||
- **Pokedex entries are spread over four banks** and the cart derives the bank
|
||||
arithmetically; take each species' own symbol instead.
|
||||
- **`EvosAttacksPointers` is `dw`, not `dba`**: the blobs share the table's bank.
|
||||
- Item ids past NUM_ITEMS (the TMs and HMs) have no ItemNames row; their name is
|
||||
their TM number.
|
||||
- **`TrainerClassAttributes` rows are SEVEN bytes**, not eight:
|
||||
`NUM_TRAINER_ATTRIBUTES` is `_RS` after three `rb` and two `rw`. An
|
||||
eight-byte stride walks one byte further off with every class, so the AI
|
||||
flags come out as noise for everything past the first trainer -- and it
|
||||
produces plausible values rather than an error, which is how it survived two
|
||||
passes. The row is {item1, item2, baseMoney, aiLo, aiHi, switchLo,
|
||||
switchHi}: the base money is byte THREE.
|
||||
- **`dba_pic` does not store the real bank.** For the three "Pics" sections
|
||||
that sit above the 8-bit-friendly range it writes `$13`, `$14` or `$1f`, and
|
||||
`FixPicBank` (engine/gfx/load_pics.asm) maps those back to `$1f`, `$20` and
|
||||
`$2e`. Any table of pic pointers -- `PokemonPicPointers`,
|
||||
`UnownPicPointers`, `TrainerPicPointers` -- needs that mapping, or the read
|
||||
lands in a completely different bank.
|
||||
- **A pic can run over the top of its bank.** `GetLZByte` bumps the bank and
|
||||
drops back to `$4000` when the read pointer passes `$8000`, so a compressed
|
||||
pic near the top of a bank keeps going into the next one; slicing only to
|
||||
`$8000` stops short of nine of the Unown letters.
|
||||
- **`BattleAnimObjects` rows are SIX bytes**, because `BATTLEANIMOBJ_LENGTH` is
|
||||
`_RS - 1` -- the struct's runtime INDEX byte is not in the table.
|
||||
- **Unown has no `PokemonPicPointers` row of its own**; its twenty-six forms
|
||||
come out of `UnownPicPointers` instead, and letter A stands in for the
|
||||
species.
|
||||
@@ -0,0 +1,126 @@
|
||||
# ASM Walk output format
|
||||
|
||||
Every `section-NN-*.md` in this folder follows the structure below. The goal is a
|
||||
single place a bot author can look at to answer "for this stretch of the
|
||||
walkthrough, which maps, warps, objects, scripts, flags, items and trainers are
|
||||
involved, and where in the pokegold disassembly do they live?"
|
||||
|
||||
Conventions:
|
||||
|
||||
- Disassembly paths are written relative to the pokegold checkout root, e.g.
|
||||
`maps/NewBarkTown.asm`, `data/maps/maps.asm`, `constants/event_flags.asm`.
|
||||
- Port paths are written relative to this repo root, e.g. `src/world/gen2/Map.lua`.
|
||||
- Coordinates are the raw asm values (map-block coordinates as written in the
|
||||
`warp_event` / `object_event` / `bg_event` rows), not screen pixels.
|
||||
- Anything not verified by opening the file is omitted rather than guessed. If a
|
||||
walkthrough claim could not be located in the asm, it goes under
|
||||
"Unresolved / verify by hand".
|
||||
|
||||
---
|
||||
|
||||
# Section NN - <title>
|
||||
|
||||
Source: `../section-NN-<slug>.txt`
|
||||
Maps covered: `MAP_A`, `MAP_B`, ...
|
||||
Badges / key milestones in this section: ...
|
||||
|
||||
## 1. Route order
|
||||
|
||||
Ordered list of map transitions the walkthrough takes, one row per hop.
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 2. Maps
|
||||
|
||||
Repeat this block per map.
|
||||
|
||||
### MAP_<NAME>
|
||||
|
||||
- Script: `maps/<Name>.asm`
|
||||
- Blocks: `maps/<Name>.blk` (if present)
|
||||
- Header: `data/maps/maps.asm` -> tileset, environment, location, music, phone, palette
|
||||
- Dimensions / attributes: `constants/map_constants.asm` (`map_const <NAME>, W, H`)
|
||||
- Connections: north/south/east/west map constants
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
|
||||
**Coord events** (`def_coord_events`) - the scripted trip-wires a bot will hit
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
|
||||
**BG events** (`def_bg_events`) - signs, hidden items
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
|
||||
**Object events** (`def_object_events`) - NPCs, item balls, trainers
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
For each script label the walkthrough actually depends on: label, a short prose
|
||||
summary of what the opcodes do, and the flags/items/battles it touches. Cite the
|
||||
label name so it can be grepped.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
|
||||
Include `EVENT_*` (`constants/event_flags.asm`), `ENGINE_*`
|
||||
(`constants/engine_flags.asm`), and any `SCENE_*` values
|
||||
(`constants/map_setup_constants.asm` / the map's own `object_const_def` block).
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
Table entry in `data/wild/johto_grass.asm` / `johto_water.asm` /
|
||||
`kanto_grass.asm` / `kanto_water.asm`, plus fishing (`data/wild/fish.asm`),
|
||||
headbutt (`data/wild/treemons.asm`) and rock smash where relevant. Note the
|
||||
morn/day/nite split.
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
What actually stops forward progress in this section, and the precise check.
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
|
||||
Cover badge checks, HM field-move checks (`engine/overworld/`), key items,
|
||||
`EVENT_*` guards, and NPC-blocked tiles.
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
The machine-actionable version: an ordered list of steps a scripted playthrough
|
||||
would execute. Each step names the map, the target coordinates or object const,
|
||||
the input intent (talk / walk / use item / battle), the precondition flag and the
|
||||
postcondition flag. Keep it literal enough that someone can turn a row into a
|
||||
driver command without reopening the asm.
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
Where this section's mechanics land in this repo, and what is missing.
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
Anything the walkthrough asserts that could not be pinned to a specific asm
|
||||
location, plus any contradictions found between the two.
|
||||
@@ -0,0 +1,535 @@
|
||||
# Section 00 - Chikorita, Cyndaquil, or Totodile?
|
||||
|
||||
Source: `../section-00-chikorita-cyndaquil-or-totodile.txt`
|
||||
Maps covered: `MAP_PLAYERS_HOUSE_2F`, `MAP_PLAYERS_HOUSE_1F`, `MAP_NEW_BARK_TOWN`, `MAP_ELMS_LAB`
|
||||
Badges / key milestones in this section: no badge. Milestones are the new-game
|
||||
clock set, the #GEAR from Mom (`ENGINE_POKEGEAR`), the first rival encounter,
|
||||
and the starter (`EVENT_GOT_A_POKEMON_FROM_ELM`), which is what un-gates the
|
||||
walk west out of New Bark Town.
|
||||
|
||||
Coordinate note: every `warp_event` / `coord_event` / `bg_event` /
|
||||
`object_event` x,y below is copied verbatim from the map asm. Those are 0-based
|
||||
walk-grid cells (2 per map block, so a `W`-block-wide map runs x = 0..2W-1),
|
||||
which is the same grid `src/world/gen2/Map.lua` calls a cell.
|
||||
|
||||
## 1. Route order
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 0 | (no map) | `engine/menus/intro_menu.asm` `NewGame` / `OakSpeech`, `engine/rtc/timeset.asm` `InitClock` | title screen -> NEW GAME | `InitializeWorld` -> `SPAWN_HOME` | "Choose new game and start… Professor Oak will ask you the time… he asks you what your name is" |
|
||||
| 1 | `MAP_PLAYERS_HOUSE_2F` | `maps/PlayersHouse2F.asm` | spawn `SPAWN_HOME` at (3,3), `data/maps/spawn_points.asm` | warp 1 at (7,0) | "You are in your room at the beginning. Exit the room" |
|
||||
| 2 | `MAP_PLAYERS_HOUSE_1F` | `maps/PlayersHouse1F.asm` | warp 3 at (9,0) | warp 1 (6,7) or warp 2 (7,7) | "you will meet your mom… You get the Pokegear… input the day of the week" |
|
||||
| 3 | `MAP_NEW_BARK_TOWN` | `maps/NewBarkTown.asm` | warp 2 at (13,5) | warp 1 at (6,3) | "Leave your house and head left to Professor Elm's lab. Notice the guy standing left of it" (rival at (3,2)) |
|
||||
| 4 | `MAP_ELMS_LAB` | `maps/ElmsLab.asm` | warp 1 (4,11) / warp 2 (5,11) | warp 1/2 back to New Bark Town | "go into Elm's lab… You get to pick your starter Pokémon" |
|
||||
| 5 | `MAP_NEW_BARK_TOWN` | `maps/NewBarkTown.asm` | warp 1 at (6,3) | west connection to `ROUTE_29` (`data/maps/attributes.asm`) | section ends here; the walk west to Route 29 / Cherrygrove belongs to section 01 |
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_PLAYERS_HOUSE_2F
|
||||
|
||||
- Script: `maps/PlayersHouse2F.asm`
|
||||
- Blocks: `maps/PlayersHouse2F.blk`
|
||||
- Header (`data/maps/maps.asm:478`): `map PlayersHouse2F, TILESET_PLAYERS_ROOM, INDOOR, LANDMARK_NEW_BARK_TOWN, MUSIC_NEW_BARK_TOWN, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions (`constants/map_constants.asm:443`): `map_const PLAYERS_HOUSE_2F, 4, 3` (4x3 blocks = 8x6 cells)
|
||||
- Connections: none (indoor)
|
||||
- Scene var: `wPlayersHouse2FSceneID` is *not* in `data/maps/scenes.asm`; the map has an empty `def_scene_scripts`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 7 | 0 | `PLAYERS_HOUSE_1F` | 3 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
None (the block is empty).
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 2 | 1 | `BGEVENT_UP` | `PlayersHousePCScript` |
|
||||
| 3 | 1 | `BGEVENT_READ` | `PlayersHouseRadioScript` |
|
||||
| 5 | 1 | `BGEVENT_READ` | `PlayersHouseBookshelfScript` |
|
||||
| 6 | 0 | `BGEVENT_IFSET` | `PlayersHousePosterScript` (guarded by `EVENT_PLAYERS_ROOM_POSTER`) |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `PLAYERSHOUSE2F_CONSOLE` | `SPRITE_CONSOLE` | 4 | 2 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `PlayersHouseGameConsoleScript` | `EVENT_PLAYERS_HOUSE_2F_CONSOLE` |
|
||||
| `PLAYERSHOUSE2F_DOLL_1` | `SPRITE_DOLL_1` | 4 | 4 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `PlayersHouseDoll1Script` | `EVENT_PLAYERS_HOUSE_2F_DOLL_1` |
|
||||
| `PLAYERSHOUSE2F_DOLL_2` | `SPRITE_DOLL_2` | 5 | 4 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `PlayersHouseDoll2Script` | `EVENT_PLAYERS_HOUSE_2F_DOLL_2` |
|
||||
| `PLAYERSHOUSE2F_BIG_DOLL` | `SPRITE_BIG_DOLL` | 0 | 1 | `SPRITEMOVEDATA_BIGDOLL` | `OBJECTTYPE_SCRIPT` | `PlayersHouseBigDollScript` | `EVENT_PLAYERS_HOUSE_2F_BIG_DOLL` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `PlayersHouse2FInitializeRoomCallback` (`callback MAPCALLBACK_NEWMAP`): runs
|
||||
`special ToggleDecorationsVisibility`, sets
|
||||
`EVENT_TEMPORARY_UNTIL_MAP_RELOAD_8` (read by `engine/phone/scripts/mom.asm`),
|
||||
then `checkevent EVENT_INITIALIZED_EVENTS` -> if clear,
|
||||
`jumpstd InitializeEventsScript`. This is the one-time world seeding for a new
|
||||
game: `engine/events/std_scripts.asm:438` sets ~70 `EVENT_*` bits including
|
||||
`EVENT_COP_IN_ELMS_LAB` (which *hides* the officer), `EVENT_RIVAL_CHERRYGROVE_CITY`,
|
||||
`EVENT_ROUTE_30_YOUNGSTER_JOEY`, and finally `EVENT_INITIALIZED_EVENTS`
|
||||
(`engine/events/std_scripts.asm:557`) so it never runs again.
|
||||
- `PlayersHouse2FSetUpTileDecorationsCallback` (`callback MAPCALLBACK_TILES`):
|
||||
`special ToggleMaptileDecorations`.
|
||||
- `PlayersHouseRadioScript`: three-branch. Before `EVENT_GOT_A_POKEMON_FROM_ELM`
|
||||
it plays `MUSIC_POKEMON_TALK`, prints four texts and sets
|
||||
`EVENT_LISTENED_TO_INITIAL_RADIO`; after that flag it prints only
|
||||
`PlayersRadioText4`; after `EVENT_GOT_A_POKEMON_FROM_ELM` it becomes the normal
|
||||
`jumpstd Radio1Script`. Purely flavour, no gate.
|
||||
- `PlayersHousePCScript`: `special PlayersHousePC`, `iftrue .Warp` -> `warp NONE, 0, 0`
|
||||
(the "leave the PC by warping in place" reload).
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_INITIALIZED_EVENTS` | `constants/event_flags.asm:63` | read by `PlayersHouse2FInitializeRoomCallback`, set by `InitializeEventsScript` | first-load-only world seed; must be clear on a fresh save |
|
||||
| `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_8` | `constants/event_flags.asm` | set here, read by `engine/phone/scripts/mom.asm:13` | "player is at home right now" |
|
||||
| `EVENT_LISTENED_TO_INITIAL_RADIO` | `constants/event_flags.asm:468` | `PlayersHouseRadioScript` | shortens the radio text on repeat |
|
||||
| `EVENT_PLAYERS_ROOM_POSTER` | `constants/event_flags.asm:378` | `conditional_event` in `PlayersHousePosterScript` | decoration present |
|
||||
|
||||
**Items**: none on this map.
|
||||
|
||||
**Trainers**: none.
|
||||
|
||||
**Wild encounters**: none (indoor).
|
||||
|
||||
### MAP_PLAYERS_HOUSE_1F
|
||||
|
||||
- Script: `maps/PlayersHouse1F.asm`
|
||||
- Blocks: `maps/PlayersHouse1F.blk`
|
||||
- Header (`data/maps/maps.asm:477`): `map PlayersHouse1F, TILESET_PLAYERS_HOUSE, INDOOR, LANDMARK_NEW_BARK_TOWN, MUSIC_NEW_BARK_TOWN, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions (`constants/map_constants.asm:442`): `map_const PLAYERS_HOUSE_1F, 5, 4` (10x8 cells)
|
||||
- Connections: none (indoor)
|
||||
- Scene var: `wPlayersHouse1FSceneID` (`data/maps/scenes.asm:30`). Scene ids come
|
||||
from declaration order in `def_scene_scripts` (`macros/scripts/maps.asm:25`):
|
||||
`SCENE_PLAYERSHOUSE1F_MEET_MOM` = 0, `SCENE_PLAYERSHOUSE1F_NOOP` = 1. A new game
|
||||
zeroes the scene, so scene 0 is live on the first visit.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 6 | 7 | `NEW_BARK_TOWN` | 2 |
|
||||
| 2 | 7 | 7 | `NEW_BARK_TOWN` | 2 |
|
||||
| 3 | 9 | 0 | `PLAYERS_HOUSE_2F` | 1 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
None. The Mom cutscene is a **scene script**, not a coord event: `scene_script
|
||||
PlayersHouse1FMeetMomScene, SCENE_PLAYERSHOUSE1F_MEET_MOM` -> `sdefer MeetMomScript`,
|
||||
so it fires on map entry from the stairs while the scene id is still 0.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 0 | 1 | `BGEVENT_READ` | `PlayersHouse1FStoveScript` |
|
||||
| 1 | 1 | `BGEVENT_READ` | `PlayersHouse1FSinkScript` |
|
||||
| 2 | 1 | `BGEVENT_READ` | `PlayersHouse1FFridgeScript` |
|
||||
| 4 | 1 | `BGEVENT_READ` | `PlayersHouse1FTVScript` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `PLAYERSHOUSE1F_MOM1` | `SPRITE_MOM` | 7 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` (time `-1`) | `OBJECTTYPE_SCRIPT` | `MomScript` | `EVENT_PLAYERS_HOUSE_MOM_1` |
|
||||
| `PLAYERSHOUSE1F_MOM2` | `SPRITE_MOM` | 2 | 2 | `SPRITEMOVEDATA_STANDING_UP` (time `MORN`) | `OBJECTTYPE_SCRIPT` | `MomScript` | `EVENT_PLAYERS_HOUSE_MOM_2` |
|
||||
| `PLAYERSHOUSE1F_MOM3` | `SPRITE_MOM` | 7 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` (time `DAY`) | `OBJECTTYPE_SCRIPT` | `MomScript` | `EVENT_PLAYERS_HOUSE_MOM_2` |
|
||||
| `PLAYERSHOUSE1F_MOM4` | `SPRITE_MOM` | 0 | 2 | `SPRITEMOVEDATA_STANDING_UP` (time `NITE`) | `OBJECTTYPE_SCRIPT` | `MomScript` | `EVENT_PLAYERS_HOUSE_MOM_2` |
|
||||
|
||||
Note the object flags are inverted relative to intuition: a *set* event flag hides
|
||||
the object. Mom1 is the cutscene Mom (visible while `EVENT_PLAYERS_HOUSE_MOM_1`
|
||||
is clear); `MeetMomScript` ends by setting `EVENT_PLAYERS_HOUSE_MOM_1` and
|
||||
clearing `EVENT_PLAYERS_HOUSE_MOM_2`, which swaps in the three time-of-day Moms
|
||||
on the next map load.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `MeetMomScript` (the whole Pokegear beat, `maps/PlayersHouse1F.asm:21`):
|
||||
1. `applymovement PLAYER, PlayersHouseDownstairsMovement` (one `step DOWN`, i.e. off the stair tile).
|
||||
2. `playmusic MUSIC_MOM`, `turnobject PLAYERSHOUSE1F_MOM1, UP`, `showemote EMOTE_SHOCK, PLAYERSHOUSE1F_MOM1, 15`.
|
||||
3. `applymovement PLAYERSHOUSE1F_MOM1, MomWalksToPlayerMovement` (`slow_step RIGHT, RIGHT, UP`: (7,3) -> (9,2), beside the player at (9,1)).
|
||||
4. `writetext ElmsLookingForYouText`, `getstring STRING_BUFFER_4, PokegearName` ("#GEAR"), `scall PlayersHouse1FReceiveItemStd` -> `jumpstd ReceiveItemScript` (`engine/events/std_scripts.asm:656`).
|
||||
5. `setflag ENGINE_POKEGEAR`, `setflag ENGINE_PHONE_CARD`, `addcellnum PHONE_MOM`.
|
||||
6. `setscene SCENE_PLAYERSHOUSE1F_NOOP`, `setevent EVENT_PLAYERS_HOUSE_MOM_1`, `clearevent EVENT_PLAYERS_HOUSE_MOM_2`.
|
||||
7. `special SetDayOfWeek`, then the DST loop: `IsItDSTText` -> `yesorno` -> `special InitialSetDSTFlag` / `special InitialClearDSTFlag`, each confirmed by a second `yesorno` that loops back to `.SetDayOfWeek` on "no".
|
||||
8. Phone-instructions branch (`.KnowPhone` / `.ExplainPhone`, both fall into `.FinishPhone`), then `applymovement PLAYERSHOUSE1F_MOM1, MomWalksBackMovement` and `special RestartMapMusic`.
|
||||
Crucially the #GEAR is **not** an inventory item: it is two engine flags. There
|
||||
is no `giveitem` in this script.
|
||||
- `MomScript` (talking to Mom afterwards): checks, in order,
|
||||
`EVENT_FIRST_TIME_BANKING_WITH_MOM` -> `EVENT_TALKED_TO_MOM_AFTER_MYSTERY_EGG_QUEST`
|
||||
-> `EVENT_GAVE_MYSTERY_EGG_TO_ELM` -> `EVENT_GOT_A_POKEMON_FROM_ELM`; in this
|
||||
section all four are clear so it prints `HurryUpElmIsWaitingText`. The
|
||||
`special BankOfMom` path is not reachable yet.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_POKEGEAR` | `constants/engine_flags.asm:8` | set by `MeetMomScript` | Pokegear menu becomes usable |
|
||||
| `ENGINE_PHONE_CARD` | `constants/engine_flags.asm:6` | set by `MeetMomScript` | Pokegear PHONE card unlocked |
|
||||
| `EVENT_PLAYERS_HOUSE_MOM_1` | `constants/event_flags.asm:1129` | set by `MeetMomScript` | hides the cutscene Mom |
|
||||
| `EVENT_PLAYERS_HOUSE_MOM_2` | `constants/event_flags.asm:1130` | cleared by `MeetMomScript` | reveals the time-of-day Moms |
|
||||
| `SCENE_PLAYERSHOUSE1F_MEET_MOM` (0) / `SCENE_PLAYERSHOUSE1F_NOOP` (1) | `maps/PlayersHouse1F.asm:9-10` | `setscene` at step 6 | scene 0 must be live exactly once |
|
||||
| `PHONE_MOM` | `constants/phone_constants.asm` (via `addcellnum`) | `MeetMomScript` | Mom's number in the Pokegear |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| POKEGEAR (not an item - `ENGINE_POKEGEAR` + `ENGINE_PHONE_CARD`) | Mom cutscene | `MeetMomScript` | `EVENT_PLAYERS_HOUSE_MOM_1` / `SCENE_PLAYERSHOUSE1F_NOOP` |
|
||||
|
||||
**Trainers**: none.
|
||||
|
||||
**Wild encounters**: none (indoor).
|
||||
|
||||
### MAP_NEW_BARK_TOWN
|
||||
|
||||
- Script: `maps/NewBarkTown.asm`
|
||||
- Blocks: `maps/NewBarkTown.blk`
|
||||
- Header (`data/maps/maps.asm:475`): `map NewBarkTown, TILESET_JOHTO, TOWN, LANDMARK_NEW_BARK_TOWN, MUSIC_NEW_BARK_TOWN, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN`
|
||||
- Dimensions (`constants/map_constants.asm:440`): `map_const NEW_BARK_TOWN, 10, 9` (20x18 cells)
|
||||
- Connections (`data/maps/attributes.asm:119-121`): `map_attributes NewBarkTown, NEW_BARK_TOWN, $05`, `connection west, Route29, ROUTE_29, 0`, `connection east, Route27, ROUTE_27, 0`
|
||||
- Scene var: `wNewBarkTownSceneID` (`data/maps/scenes.asm:28`).
|
||||
`SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU` = 0, `SCENE_NEWBARKTOWN_NOOP` = 1.
|
||||
- Fly point / spawn: `NewBarkTownFlypointCallback` sets `ENGINE_FLYPOINT_NEW_BARK`
|
||||
(`constants/engine_flags.asm:79`) on every `MAPCALLBACK_NEWMAP`, and also
|
||||
`clearevent EVENT_FIRST_TIME_BANKING_WITH_MOM`. `SPAWN_NEW_BARK` is
|
||||
(13,6) in `data/maps/spawn_points.asm:27`.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 6 | 3 | `ELMS_LAB` | 1 |
|
||||
| 2 | 13 | 5 | `PLAYERS_HOUSE_1F` | 1 |
|
||||
| 3 | 3 | 11 | `PLAYERS_NEIGHBORS_HOUSE` | 1 |
|
||||
| 4 | 11 | 13 | `ELMS_HOUSE` | 1 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU` (0) | 1 | 8 | `NewBarkTown_TeacherStopsYouScene1` | teacher walks 4x LEFT to (2,8), `follow`s the player and drags them back 4x RIGHT |
|
||||
| `SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU` (0) | 1 | 9 | `NewBarkTown_TeacherStopsYouScene2` | same, 5 steps, teacher ends at (1,8) facing DOWN |
|
||||
|
||||
These two cells are the entire west-exit gate. There is no coord event on the
|
||||
east side.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 8 | 8 | `BGEVENT_READ` | `NewBarkTownSign` |
|
||||
| 11 | 5 | `BGEVENT_READ` | `NewBarkTownPlayersHouseSign` |
|
||||
| 3 | 3 | `BGEVENT_READ` | `NewBarkTownElmsLabSign` |
|
||||
| 9 | 13 | `BGEVENT_READ` | `NewBarkTownElmsHouseSign` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `NEWBARKTOWN_TEACHER` | `SPRITE_TEACHER` | 6 | 8 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `NewBarkTownTeacherScript` | `-1` (always visible) |
|
||||
| `NEWBARKTOWN_FISHER` | `SPRITE_FISHER` | 12 | 9 | `SPRITEMOVEDATA_WALK_UP_DOWN`, `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT` | `NewBarkTownFisherScript` | `-1` |
|
||||
| `NEWBARKTOWN_RIVAL` | `SPRITE_RIVAL` | 3 | 2 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `NewBarkTownRivalScript` | `EVENT_RIVAL_NEW_BARK_TOWN` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `NewBarkTownRivalScript` (the "he'll push you out of the way" beat): no
|
||||
`faceplayer`. Prints `NewBarkTownRivalText1` ("So this is the famous ELM #MON
|
||||
LAB…"), `turnobject NEWBARKTOWN_RIVAL, LEFT`, prints `NewBarkTownRivalText2`,
|
||||
then `follow PLAYER, NEWBARKTOWN_RIVAL` +
|
||||
`applymovement PLAYER, NewBarkTown_RivalPushesYouAwayMovement` (`turn_head UP`,
|
||||
`step DOWN`), `stopfollow`, `turnobject NEWBARKTOWN_RIVAL, DOWN`,
|
||||
`playsound SFX_TACKLE`, `applymovement PLAYER, NewBarkTown_RivalShovesYouOutMovement`
|
||||
(`fix_facing`, `jump_step DOWN`), and the rival steps RIGHT back to (3,2).
|
||||
**It sets no flag** - it is repeatable and skippable. `EVENT_RIVAL_NEW_BARK_TOWN`
|
||||
is only set much later, at `maps/MrPokemonsHouse.asm:122`.
|
||||
- `NewBarkTown_TeacherStopsYouScene1/2`: the "It's dangerous to go out without a
|
||||
#MON!" block. Ends with `special RestartMapMusic` and does **not** change the
|
||||
scene, so it re-fires every time you step on (1,8)/(1,9) until the scene id is
|
||||
changed from Elm's Lab.
|
||||
- `NewBarkTownTeacherScript`: four-way text branch on
|
||||
`EVENT_TALKED_TO_MOM_AFTER_MYSTERY_EGG_QUEST` / `EVENT_GAVE_MYSTERY_EGG_TO_ELM` /
|
||||
`EVENT_GOT_A_POKEMON_FROM_ELM`; flavour only.
|
||||
- `NewBarkTownFlypointCallback`: `setflag ENGINE_FLYPOINT_NEW_BARK` on first entry.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU` (0) | `maps/NewBarkTown.asm:8` | cleared to `SCENE_NEWBARKTOWN_NOOP` by `ElmDirectionsScript`'s `setmapscene NEW_BARK_TOWN, SCENE_NEWBARKTOWN_NOOP` | the west-exit gate |
|
||||
| `EVENT_RIVAL_NEW_BARK_TOWN` | `constants/event_flags.asm:1119` | set at `maps/MrPokemonsHouse.asm:122` | rival object hidden once set; clear (visible) all through this section |
|
||||
| `ENGINE_FLYPOINT_NEW_BARK` | `constants/engine_flags.asm:79` | `NewBarkTownFlypointCallback` | Fly destination registered |
|
||||
| `EVENT_FIRST_TIME_BANKING_WITH_MOM` | `constants/event_flags.asm` | cleared by `NewBarkTownFlypointCallback` | not reachable this section |
|
||||
|
||||
**Items**: none. See "Unresolved" for the Everstone.
|
||||
|
||||
**Trainers**: none. The rival encounter here is a conversation, not a battle.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
- No `def_grass_wildmons NEW_BARK_TOWN` entry exists in `data/wild/johto_grass.asm`.
|
||||
- Water (`data/wild/johto_water.asm:211`): `def_water_wildmons NEW_BARK_TOWN`,
|
||||
`db 6 percent`, `db 20, TENTACOOL` / `db 15, TENTACOOL` / `db 20, TENTACRUEL`.
|
||||
Needs Surf, unreachable this section.
|
||||
- Fishing: the header's `FISHGROUP_OCEAN` selects
|
||||
`data/wild/fish.asm` `.Ocean_Old` (Magikarp 10 / Magikarp 10 / Tentacool 10),
|
||||
`.Ocean_Good`, `.Ocean_Super`. No rod yet.
|
||||
- Headbutt: `data/wild/treemon_maps.asm:28` `treemon_map NEW_BARK_TOWN, TREEMON_SET_CITY`
|
||||
-> `data/wild/treemons.asm:18` `TreeMonSet_City` (Venonat/Abra/Venomoth common,
|
||||
Venonat/Magnemite/Abra/Venomoth rare, all L15). Needs TM02, far later.
|
||||
|
||||
### MAP_ELMS_LAB
|
||||
|
||||
- Script: `maps/ElmsLab.asm`
|
||||
- Blocks: `maps/ElmsLab.blk`
|
||||
- Header (`data/maps/maps.asm:476`): `map ElmsLab, TILESET_LAB, INDOOR, LANDMARK_NEW_BARK_TOWN, MUSIC_PROF_ELM, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions (`constants/map_constants.asm:441`): `map_const ELMS_LAB, 5, 6` (10x12 cells)
|
||||
- Connections: none (indoor)
|
||||
- Scene var: `wElmsLabSceneID` (`data/maps/scenes.asm:29`). Declaration order
|
||||
gives `SCENE_ELMSLAB_MEET_ELM` = 0, `SCENE_ELMSLAB_CANT_LEAVE` = 1,
|
||||
`SCENE_ELMSLAB_NOOP` = 2, `SCENE_ELMSLAB_MEET_OFFICER` = 3,
|
||||
`SCENE_ELMSLAB_UNUSED` = 4, `SCENE_ELMSLAB_AIDE_GIVES_POTION` = 5,
|
||||
`SCENE_ELMSLAB_AIDE_GIVES_POKE_BALLS` = 6 (the last one is a bare
|
||||
`scene_const`, no script row).
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 11 | `NEW_BARK_TOWN` | 1 |
|
||||
| 2 | 5 | 11 | `NEW_BARK_TOWN` | 1 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_ELMSLAB_CANT_LEAVE` (1) | 4 | 6 | `LabTryToLeaveScript` | Elm turns DOWN, `LabWhereGoingText`, `applymovement PLAYER, ElmsLab_CantLeaveMovement` (one `step UP`) |
|
||||
| `SCENE_ELMSLAB_CANT_LEAVE` (1) | 5 | 6 | `LabTryToLeaveScript` | same |
|
||||
| `SCENE_ELMSLAB_MEET_OFFICER` (3) | 4 | 5 | `MeetCopScript` | post-theft, next section |
|
||||
| `SCENE_ELMSLAB_MEET_OFFICER` (3) | 5 | 5 | `MeetCopScript2` | post-theft, next section |
|
||||
| `SCENE_ELMSLAB_AIDE_GIVES_POTION` (5) | 4 | 8 | `AideScript_WalkPotion1` | aide walks 2x RIGHT, gives POTION, walks back |
|
||||
| `SCENE_ELMSLAB_AIDE_GIVES_POTION` (5) | 5 | 8 | `AideScript_WalkPotion2` | aide walks 3x RIGHT, gives POTION, walks back |
|
||||
| `SCENE_ELMSLAB_AIDE_GIVES_POKE_BALLS` (6) | 4 | 8 | `AideScript_WalkBalls1` | 5x POKE_BALL, next section |
|
||||
| `SCENE_ELMSLAB_AIDE_GIVES_POKE_BALLS` (6) | 5 | 8 | `AideScript_WalkBalls2` | 5x POKE_BALL, next section |
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 2 | 1 | `BGEVENT_READ` | `ElmsLabHealingMachine` |
|
||||
| 6 | 1 | `BGEVENT_READ` | `ElmsLabBookshelf` |
|
||||
| 7 | 1 | `BGEVENT_READ` | `ElmsLabBookshelf` |
|
||||
| 8 | 1 | `BGEVENT_READ` | `ElmsLabBookshelf` |
|
||||
| 9 | 1 | `BGEVENT_READ` | `ElmsLabBookshelf` |
|
||||
| 0 | 7 | `BGEVENT_READ` | `ElmsLabTravelTip1` |
|
||||
| 1 | 7 | `BGEVENT_READ` | `ElmsLabTravelTip2` |
|
||||
| 2 | 7 | `BGEVENT_READ` | `ElmsLabTravelTip3` |
|
||||
| 3 | 7 | `BGEVENT_READ` | `ElmsLabTravelTip4` |
|
||||
| 6 | 7 | `BGEVENT_READ` | `ElmsLabBookshelf` |
|
||||
| 7 | 7 | `BGEVENT_READ` | `ElmsLabBookshelf` |
|
||||
| 8 | 7 | `BGEVENT_READ` | `ElmsLabBookshelf` |
|
||||
| 9 | 7 | `BGEVENT_READ` | `ElmsLabBookshelf` |
|
||||
| 9 | 3 | `BGEVENT_READ` | `ElmsLabTrashcan` |
|
||||
| 5 | 0 | `BGEVENT_READ` | `ElmsLabWindow` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ELMSLAB_ELM` | `SPRITE_ELM` | 5 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ProfElmScript` | `-1` |
|
||||
| `ELMSLAB_ELMS_AIDE` | `SPRITE_SCIENTIST` | 2 | 9 | `SPRITEMOVEDATA_SPINRANDOM_SLOW`, `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT` | `ElmsAideScript` | `EVENT_ELMS_AIDE_IN_LAB` |
|
||||
| `ELMSLAB_POKE_BALL1` | `SPRITE_POKE_BALL` | 6 | 3 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `CyndaquilPokeBallScript` | `EVENT_CYNDAQUIL_POKEBALL_IN_ELMS_LAB` |
|
||||
| `ELMSLAB_POKE_BALL2` | `SPRITE_POKE_BALL` | 7 | 3 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `TotodilePokeBallScript` | `EVENT_TOTODILE_POKEBALL_IN_ELMS_LAB` |
|
||||
| `ELMSLAB_POKE_BALL3` | `SPRITE_POKE_BALL` | 8 | 3 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `ChikoritaPokeBallScript` | `EVENT_CHIKORITA_POKEBALL_IN_ELMS_LAB` |
|
||||
| `ELMSLAB_OFFICER` | `SPRITE_OFFICER` | 5 | 3 | `SPRITEMOVEDATA_STANDING_UP`, `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT` | `CopScript` | `EVENT_COP_IN_ELMS_LAB` |
|
||||
|
||||
The ball order on screen is left-to-right **Cyndaquil (6,3), Totodile (7,3),
|
||||
Chikorita (8,3)** - not the Pokedex order the walkthrough's title uses. The
|
||||
officer is hidden all through this section because `InitializeEventsScript`
|
||||
*sets* `EVENT_COP_IN_ELMS_LAB` (`engine/events/std_scripts.asm:469`).
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `ElmsLabMeetElmScene` (scene 0) -> `sdefer ElmsLabWalkUpToElmScript`:
|
||||
`applymovement PLAYER, ElmsLab_WalkUpToElmMovement` (nine `step UP` then
|
||||
`turn_head RIGHT`, i.e. from the door at (4,11) up to (4,2) beside Elm at
|
||||
(5,2)), `turnobject ELMSLAB_ELM, LEFT`, `writetext ElmText_Intro` (the Mr.
|
||||
Pokemon errand speech), then `setscene SCENE_ELMSLAB_CANT_LEAVE`.
|
||||
- `CyndaquilPokeBallScript` / `TotodilePokeBallScript` / `ChikoritaPokeBallScript`:
|
||||
identical shape. `checkevent EVENT_GOT_A_POKEMON_FROM_ELM` -> if set, just
|
||||
`LookAtElmPokeBallScript`. Otherwise `turnobject ELMSLAB_ELM, DOWN`,
|
||||
`reanchormap`, `pokepic <SPECIES>`, `cry <SPECIES>`, `closepokepic`,
|
||||
`writetext Take<Species>Text`, `yesorno`; `iffalse DidntChooseStarterScript`
|
||||
(fully cancellable, nothing is written). On yes:
|
||||
`disappear ELMSLAB_POKE_BALL<n>`, `setevent EVENT_GOT_<SPECIES>_FROM_ELM`,
|
||||
`ChoseStarterText`, `getmonname STRING_BUFFER_3`, `ReceivedStarterText`,
|
||||
`playsound SFX_CAUGHT_MON`, then **`givepoke <SPECIES>, 5, BERRY`** - level 5,
|
||||
holding a BERRY. Finally an `applymovement` that walks the player to (5,3),
|
||||
directly below Elm, and `sjump ElmDirectionsScript`:
|
||||
- Cyndaquil: `readvar VAR_FACING` / `ifequal RIGHT, ElmDirectionsScript` (skips the walk if you took it from the left side), else `AfterCyndaquilMovement` = LEFT, UP.
|
||||
- Totodile: `AfterTotodileMovement` = LEFT, LEFT, UP.
|
||||
- Chikorita: `AfterChikoritaMovement` = LEFT, LEFT, LEFT, UP.
|
||||
- `ElmDirectionsScript` - the one script that opens the section up:
|
||||
`turnobject PLAYER, UP`, three Elm texts, `addcellnum PHONE_ELM`,
|
||||
`playsound SFX_REGISTER_PHONE_NUMBER`, then
|
||||
`setevent EVENT_GOT_A_POKEMON_FROM_ELM`,
|
||||
`setevent EVENT_RIVAL_CHERRYGROVE_CITY`,
|
||||
`setscene SCENE_ELMSLAB_AIDE_GIVES_POTION`,
|
||||
`setmapscene NEW_BARK_TOWN, SCENE_NEWBARKTOWN_NOOP`.
|
||||
That last opcode is what removes the teacher gate.
|
||||
- `AideScript_GivePotion` (reached by the (4,8)/(5,8) coord events once the scene
|
||||
is 5): `writetext AideText_GiveYouPotion`, `verbosegiveitem POTION`,
|
||||
`AideText_AlwaysBusy`, `setscene SCENE_ELMSLAB_NOOP`. One-shot, gated by the
|
||||
scene id rather than an event flag.
|
||||
- `LabTryToLeaveScript`: the "you cannot walk out yet" push-back described above.
|
||||
- `ElmsLabHealingMachine` (bg_event at (2,1)): before
|
||||
`EVENT_GOT_A_POKEMON_FROM_ELM` it just prints text; after, it offers a
|
||||
`yesorno` -> `special HealParty`, `setval HEALMACHINE_ELMS_LAB`,
|
||||
`special HealMachineAnim`. Free healing point for a bot from this section on.
|
||||
- `ProfElmScript`: long check chain. In this section every branch falls through
|
||||
to `writetext ElmText_LetYourMonBattleIt` (after the starter) or
|
||||
`ElmDescribesMrPokemonScript`. The `ElmGiveEverstoneScript` arm needs
|
||||
`EVENT_SHOWED_TOGEPI_TO_ELM`; the Master Ball arm needs `ENGINE_RISINGBADGE`;
|
||||
the S.S. Ticket arm needs `EVENT_BEAT_ELITE_FOUR`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_GOT_A_POKEMON_FROM_ELM` | `constants/event_flags.asm:35` | set by `ElmDirectionsScript`; read by the ball scripts, `MomScript`, `NewBarkTownTeacherScript`, `PlayersHouseRadioScript`, `ElmsLabHealingMachine` | the section's terminal flag |
|
||||
| `EVENT_GOT_CYNDAQUIL_FROM_ELM` | `constants/event_flags.asm:36` | `CyndaquilPokeBallScript` | which starter (Totodile/Chikorita variants are the next two consts) |
|
||||
| `EVENT_CYNDAQUIL_POKEBALL_IN_ELMS_LAB` | `constants/event_flags.asm:993` | `disappear ELMSLAB_POKE_BALL1` | ball object removed |
|
||||
| `EVENT_TOTODILE_POKEBALL_IN_ELMS_LAB` | `constants/event_flags.asm:994` | `disappear ELMSLAB_POKE_BALL2` | ball object removed |
|
||||
| `EVENT_CHIKORITA_POKEBALL_IN_ELMS_LAB` | `constants/event_flags.asm:995` | `disappear ELMSLAB_POKE_BALL3` | ball object removed |
|
||||
| `EVENT_COP_IN_ELMS_LAB` | `constants/event_flags.asm:1188` | set by `InitializeEventsScript`, cleared at `maps/MrPokemonsHouse.asm:127` | officer hidden this section |
|
||||
| `EVENT_ELMS_AIDE_IN_LAB` | `constants/event_flags.asm:1187` | cleared at `maps/VioletPokecenter1F.asm:31`, set at `engine/phone/scripts/elm.asm:85` | aide visible (flag clear) this section |
|
||||
| `EVENT_RIVAL_CHERRYGROVE_CITY` | `constants/event_flags.asm` | set by `InitializeEventsScript` and again by `ElmDirectionsScript` | Cherrygrove rival still hidden |
|
||||
| `EVENT_GOT_EVERSTONE_FROM_ELM` | `constants/event_flags.asm:95` | `ElmGiveEverstoneScript` | **not reachable in this section** |
|
||||
| `SCENE_ELMSLAB_*` | `maps/ElmsLab.asm:11-17` | `setscene` / `setmapscene` | drives every coord event on the map |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `POTION` | walk onto (4,8) or (5,8) on the way out after picking a starter | `AideScript_GivePotion` (`verbosegiveitem POTION`) | `SCENE_ELMSLAB_AIDE_GIVES_POTION` -> `SCENE_ELMSLAB_NOOP` |
|
||||
| starter, L5, holding `BERRY` | `givepoke` in the chosen ball script | `CyndaquilPokeBallScript` / `TotodilePokeBallScript` / `ChikoritaPokeBallScript` | `EVENT_GOT_<SPECIES>_FROM_ELM` + `EVENT_GOT_A_POKEMON_FROM_ELM` |
|
||||
| `EVERSTONE` | Elm, **after** showing him a hatched Togepi | `ElmGiveEverstoneScript` (`maps/ElmsLab.asm:341`) | `EVENT_GOT_EVERSTONE_FROM_ELM` |
|
||||
| `POKE_BALL` x5 | aide, after the Mystery Egg hand-off | `AideScript_GiveYouBalls` | `SCENE_ELMSLAB_AIDE_GIVES_POKE_BALLS` -> `SCENE_ELMSLAB_NOOP` |
|
||||
|
||||
**Trainers**: none. No `loadtrainer` / `startbattle` anywhere in
|
||||
`maps/ElmsLab.asm`, `maps/NewBarkTown.asm`, `maps/PlayersHouse1F.asm` or
|
||||
`maps/PlayersHouse2F.asm`.
|
||||
|
||||
**Wild encounters**: none (indoor).
|
||||
|
||||
**Starter data** (`data/pokemon/evos_attacks.asm`, checked against the
|
||||
walkthrough's claims - all three match)
|
||||
|
||||
| species | evolves | L5 known moves | later moves the FAQ names |
|
||||
|---|---|---|---|
|
||||
| `CHIKORITA` | `EVOLVE_LEVEL, 16, BAYLEEF`; Bayleef `EVOLVE_LEVEL, 32, MEGANIUM` | TACKLE (1), GROWL (1) | RAZOR_LEAF 8, REFLECT 12, POISONPOWDER 15, SYNTHESIS 22, BODY_SLAM 29, LIGHT_SCREEN 36, SAFEGUARD 43, SOLARBEAM 50 |
|
||||
| `CYNDAQUIL` | `EVOLVE_LEVEL, 14, QUILAVA`; Quilava `EVOLVE_LEVEL, 36, TYPHLOSION` | TACKLE (1), LEER (1) | SMOKESCREEN 6, EMBER 12, QUICK_ATTACK 19, FLAME_WHEEL 27, SWIFT 36, FLAMETHROWER 46 |
|
||||
| `TOTODILE` | `EVOLVE_LEVEL, 18, CROCONAW`; Croconaw `EVOLVE_LEVEL, 30, FERALIGATR` | SCRATCH (1), LEER (1) | RAGE 7, WATER_GUN 13, BITE 20, SCARY_FACE 27, SLASH 35, SCREECH 43, HYDRO_PUMP 52 |
|
||||
|
||||
Note the FAQ's move lists are aspirational (they mix level-up moves with HM03 /
|
||||
HM04). At the moment you receive it the starter knows exactly its two level-1
|
||||
moves; Cyndaquil does **not** have Ember yet at L5.
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Clock must be set before the world exists | `engine/menus/intro_menu.asm:494` `OakSpeech` -> `farcall InitClock` (`engine/rtc/timeset.asm:4`); default hour is 10 (`ld a, 10 ; default hour = 10 AM`) | hour then minute then a `YesNoBox` confirm each | answering "yes" to both confirmations |
|
||||
| Player must be named | `engine/menus/intro_menu.asm` `NamePlayer` -> `ShowPlayerNamingChoices` / `StorePlayerName` | pick a preset or type a name | any accepted name |
|
||||
| Cannot walk out of Elm's Lab before the errand speech ends | `maps/ElmsLab.asm` coord events (4,6) and (5,6) under `SCENE_ELMSLAB_CANT_LEAVE`, script `LabTryToLeaveScript` | scene id must leave 1 | `ElmDirectionsScript`'s `setscene SCENE_ELMSLAB_AIDE_GIVES_POTION` - i.e. take a starter |
|
||||
| Cannot leave New Bark Town west toward Route 29 | `maps/NewBarkTown.asm` coord events (1,8) and (1,9) under `SCENE_NEWBARKTOWN_TEACHER_STOPS_YOU`, scripts `NewBarkTown_TeacherStopsYouScene1/2` | scene id must leave 0 | `ElmDirectionsScript`'s `setmapscene NEW_BARK_TOWN, SCENE_NEWBARKTOWN_NOOP` |
|
||||
| Aide's Potion is scene-gated, not flag-gated | `maps/ElmsLab.asm` coord events (4,8)/(5,8), `AideScript_GivePotion` | be on scene 5 and step on x=4 or 5, y=8 | script itself does `setscene SCENE_ELMSLAB_NOOP` |
|
||||
| Everstone (listed by the FAQ under New Bark Town) | `maps/ElmsLab.asm:63,338-347` `ElmCheckEverstone` / `ElmGiveEverstoneScript` | `EVENT_SHOWED_TOGEPI_TO_ELM`, which needs a hatched Togepi in the party (`special FindPartyMonThatSpeciesYourTrainerID`) | many sections later |
|
||||
|
||||
No HM field-move gate applies in this section: nothing under
|
||||
`engine/overworld/` (cut/surf/strength/whirlpool/waterfall/flash/fly) is
|
||||
consulted between the bedroom and the starter.
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
| # | Map | Target | Intent | Precondition | Postcondition |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | - (main menu) | NEW GAME | menu select | fresh save | `NewGame` runs `ResetWRAM` |
|
||||
| 2 | - (`InitClock`) | hour, then minute, then yes/no per field | menu | in `OakSpeech` | RTC base written; default hour 10 |
|
||||
| 3 | - (`NamePlayer`) | any name | naming screen | after `OakText6` | `wPlayerName` set |
|
||||
| 4 | `PLAYERS_HOUSE_2F` | spawn (3,3) | - | `SPAWN_HOME` | `MAPCALLBACK_NEWMAP` runs `InitializeEventsScript` once; `EVENT_INITIALIZED_EVENTS` set |
|
||||
| 5 | `PLAYERS_HOUSE_2F` | warp 1 at (7,0) | walk onto | - | now on `PLAYERS_HOUSE_1F` warp 3 (9,0) |
|
||||
| 6 | `PLAYERS_HOUSE_1F` | (scene 0 auto-fires) | wait / mash A through `MeetMomScript` | `wPlayersHouse1FSceneID` == 0 | `ENGINE_POKEGEAR`, `ENGINE_PHONE_CARD`, `PHONE_MOM`, `EVENT_PLAYERS_HOUSE_MOM_1`, scene -> 1 |
|
||||
| 6a | `PLAYERS_HOUSE_1F` | DST `yesorno` pair | answer, then confirm | inside `MeetMomScript` | answering "no" to the confirm loops back to `.SetDayOfWeek` - a bot must confirm, not just answer |
|
||||
| 7 | `PLAYERS_HOUSE_1F` | warp 1 (6,7) or warp 2 (7,7) | walk onto | - | `NEW_BARK_TOWN` at warp 2 (13,5) |
|
||||
| 8 | `NEW_BARK_TOWN` | object `NEWBARKTOWN_RIVAL` at (3,2) | talk (optional) | rival visible while `EVENT_RIVAL_NEW_BARK_TOWN` clear | pushed one cell DOWN + a `jump_step DOWN`; **no flag set**, skippable |
|
||||
| 9 | `NEW_BARK_TOWN` | warp 1 at (6,3) | walk onto | avoid (1,8)/(1,9) until step 12 | `ELMS_LAB` at warp 1 (4,11) |
|
||||
| 10 | `ELMS_LAB` | (scene 0 auto-fires) | wait / mash A | `wElmsLabSceneID` == 0 | player auto-walked to (4,2); scene -> 1 (`CANT_LEAVE`) |
|
||||
| 11 | `ELMS_LAB` | ball object: (6,3) Cyndaquil / (7,3) Totodile / (8,3) Chikorita | face + A, answer **yes** to `yesorno` | `EVENT_GOT_A_POKEMON_FROM_ELM` clear | starter L5 w/ BERRY in party; `EVENT_GOT_<SP>_FROM_ELM`; ball disappears; auto-walk to (5,3); `ElmDirectionsScript` runs -> `EVENT_GOT_A_POKEMON_FROM_ELM`, `PHONE_ELM`, `ELMS_LAB` scene -> 5, `NEW_BARK_TOWN` scene -> 1 |
|
||||
| 12 | `ELMS_LAB` | (4,8) or (5,8) | walk onto on the way out | scene == 5 | `POTION` in bag; scene -> 2 (`NOOP`) |
|
||||
| 13 | `ELMS_LAB` | bg_event (2,1) | face UP + A -> yes (optional) | `EVENT_GOT_A_POKEMON_FROM_ELM` set | party healed (`special HealParty`) |
|
||||
| 14 | `ELMS_LAB` | warp 1 (4,11) or warp 2 (5,11) | walk onto | - | `NEW_BARK_TOWN` (6,3) |
|
||||
| 15 | `NEW_BARK_TOWN` | west edge past (1,8)/(1,9) | walk west | `NEW_BARK_TOWN` scene == 1 | crosses the `connection west, Route29` into `ROUTE_29` - section 01 |
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Title -> NEW GAME -> Oak speech -> name pick -> naming screen -> bedroom | `src/ui/gen2/TitleState.lua`, `src/ui/gen2/MainMenu.lua`, `src/ui/gen2/OakSpeech.lua`, `src/ui/gen2/NamePick.lua`, `src/ui/gen2/NamingScreen.lua`; asserted end-to-end by `tests/drivers/gold_boot_smoke.lua` | implemented |
|
||||
| `InitClock` (the "what time is it?" / minute screens) | none - `src/ui/gen2/OakSpeech.lua:3` says outright "Gender select / InitClock are Crystal-later" | **missing** (bot step 2 has no screen to drive) |
|
||||
| `SPAWN_HOME` start at `PLAYERS_HOUSE_2F` (3,3) | `src/world/gen2/World.lua:228-232` (`START_MAP = "PLAYERS_HOUSE_2F"`, cites `data/maps/spawn_points.asm`) | implemented |
|
||||
| Map load, warps, collision, connections | `src/world/gen2/Map.lua`, `src/world/gen2/World.lua`; `tests/drivers/gold_walk_smoke.lua`, `tests/drivers/gold_warp_scene.lua` (asserts the New Bark door lands on `ELMS_LAB`) | implemented |
|
||||
| Scene scripts + coord events (the mechanism every gate in this section uses) | `src/world/gen2/World.lua:5011-5035` (`coordEvents` scan filtered by `self:scene()`, `sceneScripts` lookup), extracted by `src/import/RomExtractorGen2.lua:804-977` | implemented |
|
||||
| `MAPCALLBACK_NEWMAP` / `MAPCALLBACK_TILES`, `ToggleDecorationsVisibility`, `ToggleMaptileDecorations` | `src/script/gen2/Specials.lua:489-505`, `tests/drivers/gold_map_callbacks.lua`, `tests/gen2_map_callbacks_test.lua` | implemented |
|
||||
| `MeetMomScript` (emote, follow-free walk-in, `yesorno` under a prompt, mid-scene object swap) | driven by the VM; regression driver `tests/drivers/gold_mom_scene.lua` exists specifically for this scene; the Mom object-swap timing is commented at `src/world/gen2/World.lua:558-568,730` | implemented |
|
||||
| `special SetDayOfWeek` / `InitialSetDSTFlag` / `InitialClearDSTFlag` | `src/script/gen2/Specials.lua:836-860` | partial - the flags/RTC fields are written from the host clock, but no day-of-week picker UI is drawn, so the player never chooses |
|
||||
| Teacher west-exit gate | `tests/drivers/gold_teacher_scene.lua` (asserts `follow` drags the player back and she stands beside them) | implemented |
|
||||
| Rival push-out (`follow` + `jump_step` + `SFX_TACKLE`) | VM ops `follow`/`stopfollow` (`src/script/gen2/Opcodes.lua:117-118`), movement in `src/script/gen2/Movement.lua` | partial - opcodes present, no driver covering `NewBarkTownRivalScript` specifically |
|
||||
| Starter pick: `pokepic`, `cry`, `yesorno`, `disappear`, `givepoke SP, 5, BERRY` | `src/script/gen2/Opcodes.lua:91-92,51`; `src/script/gen2/Vm.lua:386-394` (`pokepic` -> `showPicFn`), `:439-445` (`givepoke` -> `givePokeFn` with species/level/held item) | partial - all opcodes implemented, but no driver or test in `tests/drivers/gold_*.lua` walks the Elm's Lab starter choice end to end |
|
||||
| `verbosegiveitem POTION` (aide) | `src/script/gen2/Vm.lua:490-510` (received text, SFX, "put in the ITEM POCKET") | implemented |
|
||||
| `addcellnum PHONE_MOM` / `PHONE_ELM`, Pokegear phone card | `src/script/gen2/Vm.lua:611`, `src/core/gen2/Phone.lua:137,146`, `src/ui/gen2/Pokegear.lua` | implemented |
|
||||
| `special HealParty` / `HealMachineAnim` (lab healing machine) | `src/script/gen2/Specials.lua:456` | implemented |
|
||||
| `special PlayersHousePC` + `warp NONE, 0, 0` | `src/script/gen2/Specials.lua:467-487`, `src/ui/gen2/PcMenu.lua` | implemented |
|
||||
| `special NameRival` (fires next section, from `CopScript`) | `src/script/gen2/Specials.lua:825-828` -> `World:nameRival` (`src/world/gen2/World.lua:4931`) | implemented |
|
||||
| Event-flag bitfield semantics (set flag = object hidden) | `src/world/gen2/Events.lua` (header comment cites `CheckObjectFlag` in `map_objects_2.asm`) | implemented |
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
- **Everstone.** The FAQ lists "Everstone" under "Items found in New Bark Town".
|
||||
There is no Everstone item ball, hidden item or bg_event on `MAP_NEW_BARK_TOWN`,
|
||||
`MAP_ELMS_LAB`, `MAP_PLAYERS_HOUSE_1F` or `MAP_PLAYERS_HOUSE_2F`. The only New
|
||||
Bark Town Everstone is `ElmGiveEverstoneScript` (`maps/ElmsLab.asm:338-347`),
|
||||
which requires `EVENT_SHOWED_TOGEPI_TO_ELM` - a hatched Togepi shown to Elm,
|
||||
many sections later. Treat the FAQ line as forward-looking, not as an item to
|
||||
collect here.
|
||||
- **"Pokegear" as an item.** The FAQ says "You get the Pokegear". In the asm it is
|
||||
`setflag ENGINE_POKEGEAR` + `setflag ENGINE_PHONE_CARD`, with the item name
|
||||
faked through `getstring STRING_BUFFER_4, PokegearName` ("#GEAR") into the
|
||||
standard `ReceiveItemScript` text. Nothing enters the bag - a bot must not
|
||||
check inventory for it.
|
||||
- **"Professor Oak will ask you the time. First answer what hour it is and what
|
||||
minute it is."** `InitClock` (`engine/rtc/timeset.asm`) is reached from
|
||||
`OakSpeech` *before* any Oak text is printed (`farcall InitClock` is the first
|
||||
instruction of `OakSpeech`), so the clock screen actually comes first and Oak's
|
||||
greeting second. The FAQ's ordering is the reverse.
|
||||
- **"even if you don't have the GBA on, the time will keep running"** - an RTC
|
||||
hardware claim, nothing in the disassembly to cite; ignore.
|
||||
- **East exit toward Route 27.** `data/maps/attributes.asm:121` gives New Bark Town
|
||||
a `connection east, Route27, ROUTE_27, 0`, and unlike the west side there is
|
||||
*no* coord event guarding it. Whether a bot can actually walk east before
|
||||
getting Surf depends on the shoreline tiles in `maps/NewBarkTown.blk`, which I
|
||||
did not decode. Verify by hand before relying on the west gate as the only exit
|
||||
block.
|
||||
- **Teacher gate coverage.** Only (1,8) and (1,9) carry coord events. If the map's
|
||||
west connection strip is reachable at any other y, the gate has a hole. I read
|
||||
the coord table but not the collision data, so this is unverified.
|
||||
- **Rival battle.** The FAQ says only that he pushes you; confirmed - there is no
|
||||
`loadtrainer`/`startbattle` in `NewBarkTownRivalScript`. The first rival battle
|
||||
is not in this section.
|
||||
@@ -0,0 +1,777 @@
|
||||
# Section 02 - Sprout Tower and Violet City Gym
|
||||
|
||||
Source: `../section-02-sprout-tower-and-violet-city-gym.txt`
|
||||
Maps covered: `MAP_SPROUT_TOWER_1F`, `MAP_SPROUT_TOWER_2F`, `MAP_SPROUT_TOWER_3F`,
|
||||
`MAP_VIOLET_CITY`, `MAP_VIOLET_POKECENTER_1F`, `MAP_VIOLET_GYM`
|
||||
Badges / key milestones in this section: HM05 Flash (`EVENT_GOT_HM05_FLASH`),
|
||||
the Sprout Tower rival cutscene (`EVENT_RIVAL_SPROUT_TOWER`), ZEPHYRBADGE
|
||||
(`ENGINE_ZEPHYRBADGE`, `EVENT_BEAT_FALKNER`), TM31 Mud-Slap
|
||||
(`EVENT_GOT_TM31_MUD_SLAP`).
|
||||
|
||||
All map constants below use the `MAP_*` spelling that both
|
||||
`constants/map_constants.asm` and this port's map ids use; the port's
|
||||
`World:setMap` takes exactly the bare constant name (`"SPROUT_TOWER_1F"`), which
|
||||
is what `tools/rom_manifest_gold.json` -> `constants.mapGroups` stores.
|
||||
|
||||
---
|
||||
|
||||
## 1. Route order
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `MAP_SPROUT_TOWER_1F` | `maps/SproutTower1F.asm` | `MAP_VIOLET_CITY` warp 7 at (23,5) -> 1F warp 1 (9,15) | 1F warp 3 (6,4) -> 2F warp 1 | "Head up the stairs to Floor 2." |
|
||||
| 2 | `MAP_SPROUT_TOWER_2F` | `maps/SproutTower2F.asm` | 2F warp 1 (6,4) | 2F warp 3 (17,3) -> 1F warp 5 | Sage Nico at (14,4), then "down the stairs" |
|
||||
| 3 | `MAP_SPROUT_TOWER_1F` | `maps/SproutTower1F.asm` | 1F warp 5 (17,3) | 1F warp 4 (2,6) -> 2F warp 2 | Parlyz Heal ball at (16,7), then Sage Chow at (2,1) |
|
||||
| 4 | `MAP_SPROUT_TOWER_2F` | `maps/SproutTower2F.asm` | 2F warp 2 (2,6) | 2F warp 4 (10,14) -> 3F warp 1 | X Defend ball at (3,1), Sage Edmond at (3,15) |
|
||||
| 5 | `MAP_SPROUT_TOWER_3F` | `maps/SproutTower3F.asm` | 3F warp 1 (10,14) | 3F warp 1 (10,14) -> 2F warp 4 | Potion, Jin/Neal/Troy, rival cutscene, Sage Li, HM05, Escape Rope |
|
||||
| 6 | `MAP_SPROUT_TOWER_2F` -> `MAP_SPROUT_TOWER_1F` | as above | 2F warp 4 / 1F warps 3-5 | 1F warp 1 (9,15) or warp 2 (10,15) -> `MAP_VIOLET_CITY` warp 7 | "Get out of the tower" (Escape Rope also legal: the tower is `DUNGEON`) |
|
||||
| 7 | `MAP_VIOLET_CITY` | `maps/VioletCity.asm` | city warp 7 (23,5) | city warp 5 (31,25) | "go back to the Pokemon Center of Violet City" |
|
||||
| 8 | `MAP_VIOLET_POKECENTER_1F` | `maps/VioletPokecenter1F.asm` | PC warp 1 (3,7) | PC warp 1/2 (3,7)/(4,7) | Heal at the nurse before the gym |
|
||||
| 9 | `MAP_VIOLET_CITY` | `maps/VioletCity.asm` | city warp 5 (31,25) | city warp 2 (18,17) | "head northwest in the city to the Violet City Gym" |
|
||||
| 10 | `MAP_VIOLET_GYM` | `maps/VioletGym.asm` | gym warp 1 (4,15) | gym warp 1/2 (4,15)/(5,15) -> `MAP_VIOLET_CITY` warp 2 | Abe, Rod, Falkner, ZEPHYRBADGE + TM31 |
|
||||
| 11 | `MAP_VIOLET_CITY` | `maps/VioletCity.asm` | city warp 2 (18,17) | (section ends) | Section closes with the badge in hand |
|
||||
|
||||
Spills into the next section: beating Falkner runs `specialphonecall
|
||||
SPECIALCALL_ASSISTANT` (`maps/VioletGym.asm`), whose Elm phone script
|
||||
(`engine/phone/scripts/elm.asm`, `.assistant`) clears
|
||||
`EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER` so the aide appears in
|
||||
`MAP_VIOLET_POKECENTER_1F` with the Togepi egg, and sets
|
||||
`SCENE_ROUTE32_OFFER_SLOWPOKETAIL` when the egg is taken. That whole beat
|
||||
belongs to the next section; it is listed here only because the trigger lives
|
||||
in this section's gym script.
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_SPROUT_TOWER_1F
|
||||
|
||||
- Script: `maps/SproutTower1F.asm`
|
||||
- Blocks: `maps/SproutTower1F.blk` (`data/maps/blocks.asm` -> `SproutTower1F_Blocks`)
|
||||
- Header: `data/maps/maps.asm:79` -> `TILESET_TOWER`, `DUNGEON`,
|
||||
`LANDMARK_SPROUT_TOWER`, `MUSIC_SPROUT_TOWER`, phone `FALSE`, `PALETTE_DAY`,
|
||||
`FISHGROUP_SHORE`
|
||||
- Dimensions / attributes: `constants/map_constants.asm:66`
|
||||
(`map_const SPROUT_TOWER_1F, 10, 8`) = 20x16 cells;
|
||||
`data/maps/attributes.asm:398` (`map_attributes SproutTower1F, SPROUT_TOWER_1F, $00`)
|
||||
- Connections: none (indoor/dungeon)
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 9 | 15 | `VIOLET_CITY` | 7 |
|
||||
| 2 | 10 | 15 | `VIOLET_CITY` | 7 |
|
||||
| 3 | 6 | 4 | `SPROUT_TOWER_2F` | 1 |
|
||||
| 4 | 2 | 6 | `SPROUT_TOWER_2F` | 2 |
|
||||
| 5 | 17 | 3 | `SPROUT_TOWER_2F` | 3 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
None (`def_coord_events` is empty).
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 7 | 15 | `BGEVENT_READ` | `SproutTower1FStatue` |
|
||||
| 12 | 15 | `BGEVENT_READ` | `SproutTower1FStatue` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `SPROUTTOWER1F_SAGE1` | `SPRITE_SAGE` | 7 | 4 | `SPRITEMOVEDATA_STANDING_DOWN`, radius 0/0, pal `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT`, sight 0 | `SproutTower1FSage1Script` | -1 |
|
||||
| `SPROUTTOWER1F_SAGE2` | `SPRITE_SAGE` | 6 | 7 | `SPRITEMOVEDATA_WANDER`, radius 1/1, pal `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT`, sight 0 | `SproutTower1FSage2Script` | -1 |
|
||||
| `SPROUTTOWER1F_GRANNY` | `SPRITE_GRANNY` | 11 | 12 | `SPRITEMOVEDATA_STANDING_LEFT`, radius 0/0 | `OBJECTTYPE_SCRIPT`, sight 0 | `SproutTower1FGrannyScript` | -1 |
|
||||
| `SPROUTTOWER1F_TEACHER` | `SPRITE_TEACHER` | 9 | 9 | `SPRITEMOVEDATA_STANDING_UP`, radius 0/0 | `OBJECTTYPE_SCRIPT`, sight 0 | `SproutTower1FTeacherScript` | -1 |
|
||||
| `SPROUTTOWER1F_SAGE3` | `SPRITE_SAGE` | 2 | 1 | `SPRITEMOVEDATA_STANDING_RIGHT`, radius 0/0, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 1 | `TrainerSageChow` | -1 |
|
||||
| `SPROUTTOWER1F_POKE_BALL` | `SPRITE_POKE_BALL` | 16 | 7 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL`, sight 0 | `SproutTower1FParlyzHeal` | `EVENT_SPROUT_TOWER_1F_PARLYZ_HEAL` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `TrainerSageChow` (`42:400e`): `trainer SAGE, CHOW, EVENT_BEAT_SAGE_CHOW,
|
||||
SageChowSeenText, SageChowBeatenText, 0, .Script`. Loss text is `0`. The
|
||||
after-battle arm `.Script` (`42:401a`) is `endifjustbattled` then a single
|
||||
`writetext SageChowAfterBattleText`.
|
||||
- `SproutTower1FParlyzHeal` (`42:4022`): a bare `itemball PARLYZ_HEAL` - two raw
|
||||
bytes, **not** bytecode. Pickup is engine-side (`FindItemInBallScript`), gated
|
||||
on `EVENT_SPROUT_TOWER_1F_PARLYZ_HEAL`.
|
||||
- Sage1/Sage2/Granny/Teacher are all `jumptextfaceplayer` one-liners; nothing to
|
||||
gate on.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BEAT_SAGE_CHOW` (1041) | `constants/event_flags.asm:518` | `TrainerSageChow` header word | Set once beaten; suppresses re-battle and the eyesight trigger |
|
||||
| `EVENT_SPROUT_TOWER_1F_PARLYZ_HEAL` (1607) | `constants/event_flags.asm:1000` | object-event flag on `SPROUTTOWER1F_POKE_BALL` | Set = ball is gone (`CheckObjectFlag`, `engine/overworld/map_objects_2.asm`) |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `PARLYZ_HEAL` | Poke Ball object at (16,7) | `SproutTower1FParlyzHeal` (`itemball PARLYZ_HEAL`) | `EVENT_SPROUT_TOWER_1F_PARLYZ_HEAL` |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `SAGE`, `CHOW` | `SAGE` (38) | `CHOW` (1) | `SageGroup` "CHOW" (`0e:7324` group base): L3 Bellsprout x3, `TRAINERTYPE_NORMAL` | `TrainerSageChow` | none |
|
||||
|
||||
Prize money: `data/trainers/attributes.asm` "Sage" base reward `8`;
|
||||
`ComputeTrainerReward` x last party level (3) x4 in `WinTrainerBattle` = 96.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
None. `data/wild/johto_grass.asm` has **no** `def_grass_wildmons SPROUT_TOWER_1F`
|
||||
entry; only 2F and 3F have tables. 1F has no grass/water/fish/headbutt data.
|
||||
|
||||
---
|
||||
|
||||
### MAP_SPROUT_TOWER_2F
|
||||
|
||||
- Script: `maps/SproutTower2F.asm`
|
||||
- Blocks: `maps/SproutTower2F.blk`
|
||||
- Header: `data/maps/maps.asm:80` -> `TILESET_TOWER`, `DUNGEON`,
|
||||
`LANDMARK_SPROUT_TOWER`, `MUSIC_SPROUT_TOWER`, `FALSE`, `PALETTE_DAY`,
|
||||
`FISHGROUP_SHORE`
|
||||
- Dimensions / attributes: `constants/map_constants.asm:67`
|
||||
(`map_const SPROUT_TOWER_2F, 10, 8`) = 20x16 cells;
|
||||
`data/maps/attributes.asm:399`
|
||||
- Connections: none
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 6 | 4 | `SPROUT_TOWER_1F` | 3 |
|
||||
| 2 | 2 | 6 | `SPROUT_TOWER_1F` | 4 |
|
||||
| 3 | 17 | 3 | `SPROUT_TOWER_1F` | 5 |
|
||||
| 4 | 10 | 14 | `SPROUT_TOWER_3F` | 1 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
None.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 12 | 15 | `BGEVENT_READ` | `SproutTower2FStatue` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `SPROUTTOWER2F_SAGE1` | `SPRITE_SAGE` | 14 | 4 | `SPRITEMOVEDATA_SPINRANDOM_FAST`, radius 0/0, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerSageNico` | -1 |
|
||||
| `SPROUTTOWER2F_SAGE2` | `SPRITE_SAGE` | 3 | 15 | `SPRITEMOVEDATA_STANDING_UP`, radius 0/0, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerSageEdmond` | -1 |
|
||||
| `SPROUTTOWER2F_POKE_BALL` | `SPRITE_POKE_BALL` | 3 | 1 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL`, sight 0 | `SproutTower2FXDefend` | `EVENT_SPROUT_TOWER_2F_X_DEFEND` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `TrainerSageNico` (`42:4280`) and `TrainerSageEdmond` (`42:4294`): identical
|
||||
shape to Chow - `trainer` header, `.Script` = `endifjustbattled` + one
|
||||
`writetext`.
|
||||
- `SproutTower2FXDefend` (`42:42ab`): `itemball X_DEFEND`. Note the item is
|
||||
**X Defend**, not X Accuracy.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BEAT_SAGE_NICO` (1042) | `constants/event_flags.asm:519` | `TrainerSageNico` | Beaten once |
|
||||
| `EVENT_BEAT_SAGE_EDMOND` (1047) | `constants/event_flags.asm:524` | `TrainerSageEdmond` | Beaten once |
|
||||
| `EVENT_SPROUT_TOWER_2F_X_DEFEND` (1608) | `constants/event_flags.asm:1001` | object-event flag | Ball taken |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `X_DEFEND` | Poke Ball object at (3,1) | `SproutTower2FXDefend` | `EVENT_SPROUT_TOWER_2F_X_DEFEND` |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `SAGE`, `NICO` | `SAGE` | `NICO` (2) | `SageGroup` "NICO": L3 Bellsprout x3 | `TrainerSageNico` | none |
|
||||
| `SAGE`, `EDMOND` | `SAGE` | `EDMOND` (7) | `SageGroup` "EDMOND": L3 Bellsprout x3 | `TrainerSageEdmond` | none |
|
||||
|
||||
Both pay 8 x 3 x 4 = 96.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
`data/wild/johto_grass.asm:5` `def_grass_wildmons SPROUT_TOWER_2F`, rates
|
||||
`2 percent / 2 percent / 2 percent` (morn/day/nite):
|
||||
|
||||
- morn: L3 Rattata, L4 Rattata, L5 Rattata, L3 Rattata, L6 Rattata, L5 Rattata, L5 Rattata
|
||||
- day: identical to morn
|
||||
- nite: L3 Gastly, L4 Gastly, L5 Gastly, L3 Rattata, L6 Gastly, L5 Rattata, L5 Rattata
|
||||
|
||||
No water/fish/headbutt entry for this map.
|
||||
|
||||
---
|
||||
|
||||
### MAP_SPROUT_TOWER_3F
|
||||
|
||||
- Script: `maps/SproutTower3F.asm`
|
||||
- Blocks: `maps/SproutTower3F.blk`
|
||||
- Header: `data/maps/maps.asm:81` -> `TILESET_TOWER`, `DUNGEON`,
|
||||
`LANDMARK_SPROUT_TOWER`, `MUSIC_SPROUT_TOWER`, `FALSE`, `PALETTE_DAY`,
|
||||
`FISHGROUP_SHORE`
|
||||
- Dimensions / attributes: `constants/map_constants.asm:68`
|
||||
(`map_const SPROUT_TOWER_3F, 10, 8`) = 20x16 cells;
|
||||
`data/maps/attributes.asm:400`
|
||||
- Connections: none
|
||||
- Scene variable: `data/maps/scenes.asm:47` -> `wSproutTower3FSceneID`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 10 | 14 | `SPROUT_TOWER_2F` | 4 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_SPROUTTOWER3F_RIVAL_ENCOUNTER` (0) | 11 | 9 | `SproutTower3FRivalScene` | The elder/rival cutscene; ends with `setscene SCENE_SPROUTTOWER3F_NOOP` |
|
||||
|
||||
The two `SCENE_SPROUTTOWER3F_*` constants are generated inline by the
|
||||
`scene_script` macro (`macros/scripts/maps.asm`, `scene_const`) in
|
||||
`maps/SproutTower3F.asm:12-13`: index 0 = `SCENE_SPROUTTOWER3F_RIVAL_ENCOUNTER`,
|
||||
index 1 = `SCENE_SPROUTTOWER3F_NOOP`. Both scene *scripts* are bare `end`
|
||||
stubs; the scene id exists only to arm/disarm the coord event. The scene starts
|
||||
at 0 on a new game, so the trip-wire is live on first entry.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 8 | 1 | `BGEVENT_READ` | `SproutTower3FStatue` |
|
||||
| 11 | 1 | `BGEVENT_READ` | `SproutTower3FStatue` |
|
||||
| 9 | 0 | `BGEVENT_READ` | `SproutTower3FPainting` |
|
||||
| 10 | 0 | `BGEVENT_READ` | `SproutTower3FPainting` |
|
||||
| 5 | 15 | `BGEVENT_READ` | `SproutTower3FStatue` |
|
||||
| 14 | 15 | `BGEVENT_READ` | `SproutTower3FStatue` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `SPROUTTOWER3F_SAGE1` | `SPRITE_SAGE` | 8 | 13 | `SPRITEMOVEDATA_STANDING_RIGHT`, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSageJin` | -1 |
|
||||
| `SPROUTTOWER3F_SAGE2` | `SPRITE_SAGE` | 8 | 8 | `SPRITEMOVEDATA_STANDING_DOWN`, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerSageTroy` | -1 |
|
||||
| `SPROUTTOWER3F_SAGE3` | `SPRITE_SAGE` | 10 | 2 | `SPRITEMOVEDATA_STANDING_DOWN`, pal `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT`, sight 0 | `SageLiScript` | -1 |
|
||||
| `SPROUTTOWER3F_SAGE4` | `SPRITE_SAGE` | 11 | 11 | `SPRITEMOVEDATA_STANDING_LEFT`, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSageNeal` | -1 |
|
||||
| `SPROUTTOWER3F_POKE_BALL1` | `SPRITE_POKE_BALL` | 6 | 14 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `SproutTower3FPotion` | `EVENT_SPROUT_TOWER_3F_POTION` |
|
||||
| `SPROUTTOWER3F_POKE_BALL2` | `SPRITE_POKE_BALL` | 14 | 1 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `SproutTower3FEscapeRope` | `EVENT_SPROUT_TOWER_3F_ESCAPE_ROPE` |
|
||||
| `SPROUTTOWER3F_RIVAL` | `SPRITE_RIVAL` | 10 | 4 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT`, sight 0 | `ObjectEvent` (the shared ROM0 stub) | `EVENT_RIVAL_SPROUT_TOWER` |
|
||||
|
||||
Object-flag polarity, confirmed in `engine/overworld/map_objects_2.asm`
|
||||
(`CheckObjectFlag`): the object is **masked when its event flag is SET**.
|
||||
`EVENT_RIVAL_SPROUT_TOWER` therefore starts *clear* (rival visible) and the
|
||||
cutscene's `disappear SPROUTTOWER3F_RIVAL` sets it
|
||||
(`Script_disappear` -> `ApplyEventActionAppearDisappear`, `b = 1`,
|
||||
`engine/overworld/scripting.asm:887`).
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `SproutTower3FRivalScene` (`42:444b`), fired by the coord event at (11,9):
|
||||
`turnobject PLAYER, UP`; `showemote EMOTE_SHOCK, PLAYER, 15`;
|
||||
`special FadeOutMusic`; two rounds of `playsound SFX_TACKLE` +
|
||||
`playsound SFX_ELEVATOR` + `earthquake 79` + `pause 15`;
|
||||
`applymovement PLAYER, SproutTower3FPlayerApproachesRivalMovement` (four
|
||||
`step UP`, so the player ends at (11,5));
|
||||
`applymovement SPROUTTOWER3F_RIVAL, SproutTower3FRivalApproachesElderMovement`
|
||||
(one `step UP`, rival (10,4) -> (10,3));
|
||||
`writetext SproutTowerElderLecturesRivalText`;
|
||||
`showemote EMOTE_SHOCK, SPROUTTOWER3F_RIVAL, 15`;
|
||||
`turnobject SPROUTTOWER3F_RIVAL, DOWN`;
|
||||
`applymovement SPROUTTOWER3F_RIVAL, SproutTower3FRivalLeavesElderMovement`
|
||||
(`step RIGHT`, `step DOWN`); `playmusic MUSIC_RIVAL_ENCOUNTER`;
|
||||
two more `writetext`s; `playsound SFX_WARP_TO`; `special FadeOutToBlack`;
|
||||
`special ReloadSpritesNoPalettes`; `disappear SPROUTTOWER3F_RIVAL`;
|
||||
`waitsfx`; `special FadeInFromBlack`; `setscene SCENE_SPROUTTOWER3F_NOOP`;
|
||||
`special RestartMapMusic`; `end`.
|
||||
**No battle.** This is a pure cutscene - the rival is not fought here.
|
||||
- `SageLiScript` (`42:44aa`): `faceplayer`, `opentext`,
|
||||
`checkevent EVENT_GOT_HM05_FLASH` / `iftrue .GotFlash`. First-time arm:
|
||||
`writetext SageLiSeenText`, `winlosstext SageLiBeatenText, 0`,
|
||||
`loadtrainer SAGE, LI`, `startbattle`, `reloadmapafterbattle`,
|
||||
`writetext SageLiTakeThisFlashText`, `promptbutton`,
|
||||
`verbosegiveitem HM_FLASH`, `setevent EVENT_GOT_HM05_FLASH`,
|
||||
`setevent EVENT_BEAT_SAGE_LI`, `writetext SageLiFlashExplanationText`, `end`.
|
||||
`.GotFlash` (`42:44d5`) is a single `writetext SageLiAfterBattleText`.
|
||||
Note the flag order: the HM is given *before* both events are set, and
|
||||
`verbosegiveitem`'s failure return is **not** checked here (unlike Falkner's
|
||||
TM), so a full bag at this point loses the HM.
|
||||
- `TrainerSageJin` (`42:44db`), `TrainerSageTroy` (`42:44ef`),
|
||||
`TrainerSageNeal` (`42:4503`): standard `trainer` headers + `endifjustbattled`
|
||||
after-battle text.
|
||||
- `SproutTower3FPotion` (`42:451d`) = `itemball POTION`;
|
||||
`SproutTower3FEscapeRope` (`42:451f`) = `itemball ESCAPE_ROPE`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BEAT_SAGE_JIN` (1043) | `constants/event_flags.asm:520` | `TrainerSageJin` | Beaten |
|
||||
| `EVENT_BEAT_SAGE_TROY` (1044) | `constants/event_flags.asm:521` | `TrainerSageTroy` | Beaten |
|
||||
| `EVENT_BEAT_SAGE_NEAL` (1048) | `constants/event_flags.asm:525` | `TrainerSageNeal` | Beaten |
|
||||
| `EVENT_BEAT_SAGE_LI` (1049) | `constants/event_flags.asm:526` | set by `SageLiScript` after the battle | Set only on the HM-giving pass; Li is an `OBJECTTYPE_SCRIPT`, not a trainer object, so this flag does not gate re-battle - `EVENT_GOT_HM05_FLASH` does |
|
||||
| `EVENT_GOT_HM05_FLASH` (20) | `constants/event_flags.asm:27` | read+written by `SageLiScript` | The real "tower cleared" flag |
|
||||
| `EVENT_RIVAL_SPROUT_TOWER` (1732) | `constants/event_flags.asm:1126` | object-event flag; set by `disappear` in the cutscene; also `setevent` in `maps/HallOfFame.asm:35` | Clear = rival still standing at (10,4) |
|
||||
| `EVENT_SPROUT_TOWER_3F_POTION` (1609) | `constants/event_flags.asm:1002` | object-event flag | Ball taken |
|
||||
| `EVENT_SPROUT_TOWER_3F_ESCAPE_ROPE` (1610) | `constants/event_flags.asm:1003` | object-event flag | Ball taken |
|
||||
| `SCENE_SPROUTTOWER3F_RIVAL_ENCOUNTER` = 0 | `maps/SproutTower3F.asm:12` (macro-generated) | `wSproutTower3FSceneID` (`data/maps/scenes.asm:47`) | Coord event live |
|
||||
| `SCENE_SPROUTTOWER3F_NOOP` = 1 | `maps/SproutTower3F.asm:13` | `setscene` at the end of the cutscene; `setmapscene` in `maps/HallOfFame.asm:39` | Coord event dead |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `POTION` | Poke Ball at (6,14) | `SproutTower3FPotion` | `EVENT_SPROUT_TOWER_3F_POTION` |
|
||||
| `ESCAPE_ROPE` | Poke Ball at (14,1) | `SproutTower3FEscapeRope` | `EVENT_SPROUT_TOWER_3F_ESCAPE_ROPE` |
|
||||
| `HM_FLASH` (HM05) | `verbosegiveitem` from Sage Li after his battle | `SageLiScript` | `EVENT_GOT_HM05_FLASH` |
|
||||
|
||||
`HM_FLASH` is the fifth `add_hm` in `constants/item_constants.asm`
|
||||
(CUT, FLY, SURF, STRENGTH, **FLASH**, WHIRLPOOL, WATERFALL), i.e. HM05.
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `SAGE`, `JIN` | `SAGE` | `JIN` (3) | `SageGroup` "JIN": L6 Bellsprout | `TrainerSageJin` | none |
|
||||
| `SAGE`, `TROY` | `SAGE` | `TROY` (4) | `SageGroup` "TROY": L7 Bellsprout, L7 Hoothoot | `TrainerSageTroy` | none |
|
||||
| `SAGE`, `NEAL` | `SAGE` | `NEAL` (8) | `SageGroup` "NEAL": L6 Bellsprout | `TrainerSageNeal` | none |
|
||||
| `SAGE`, `LI` | `SAGE` | `LI` (9) | `SageGroup` "LI": L7 Bellsprout, L7 Bellsprout, L10 Hoothoot | `SageLiScript` (`loadtrainer`, not an `OBJECTTYPE_TRAINER`) | none |
|
||||
|
||||
Prize money (base 8 x last party level x 4): Jin 192, Neal 192, Troy 224,
|
||||
Li 320. All `TRAINERTYPE_NORMAL` (no explicit moves; movesets come from level-up
|
||||
data). Class DVs `dn 9, 8, 8, 8` (`data/trainers/dvs.asm:61`).
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
`data/wild/johto_grass.asm:33` `def_grass_wildmons SPROUT_TOWER_3F` - byte for
|
||||
byte identical to the 2F table: rates 2/2/2 percent, morn+day all Rattata L3-L6,
|
||||
nite Gastly L3-L6 mixed with Rattata L3/L5/L5. No water/fish/headbutt entry.
|
||||
|
||||
---
|
||||
|
||||
### MAP_VIOLET_CITY
|
||||
|
||||
- Script: `maps/VioletCity.asm`
|
||||
- Blocks: `maps/VioletCity.blk` (`data/maps/blocks.asm:532`)
|
||||
- Header: `data/maps/maps.asm:251` -> `TILESET_JOHTO`, `TOWN`,
|
||||
`LANDMARK_VIOLET_CITY`, `MUSIC_VIOLET_CITY`, phone `FALSE`, `PALETTE_AUTO`,
|
||||
`FISHGROUP_POND`
|
||||
- Dimensions / attributes: `constants/map_constants.asm:230`
|
||||
(`map_const VIOLET_CITY, 20, 18`) = 40x36 cells;
|
||||
`data/maps/attributes.asm:127` (`map_attributes VioletCity, VIOLET_CITY, $05`)
|
||||
- Connections (`data/maps/attributes.asm:127-130`):
|
||||
south `Route32`/`ROUTE_32` offset 0, west `Route36`/`ROUTE_36` offset 0,
|
||||
east `Route31`/`ROUTE_31` offset 9. No north connection.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 9 | 17 | `VIOLET_MART` | 2 |
|
||||
| 2 | 18 | 17 | `VIOLET_GYM` | 1 |
|
||||
| 3 | 30 | 17 | `EARLS_POKEMON_ACADEMY` | 1 |
|
||||
| 4 | 3 | 15 | `VIOLET_NICKNAME_SPEECH_HOUSE` | 1 |
|
||||
| 5 | 31 | 25 | `VIOLET_POKECENTER_1F` | 1 |
|
||||
| 6 | 21 | 29 | `VIOLET_KYLES_HOUSE` | 1 |
|
||||
| 7 | 23 | 5 | `SPROUT_TOWER_1F` | 1 |
|
||||
| 8 | 39 | 24 | `ROUTE_31_VIOLET_GATE` | 1 |
|
||||
| 9 | 39 | 25 | `ROUTE_31_VIOLET_GATE` | 2 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
None.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 24 | 20 | `BGEVENT_READ` | `VioletCitySign` |
|
||||
| 15 | 17 | `BGEVENT_READ` | `VioletGymSign` |
|
||||
| 24 | 8 | `BGEVENT_READ` | `SproutTowerSign` |
|
||||
| 27 | 17 | `BGEVENT_READ` | `EarlsPokemonAcademySign` |
|
||||
| 32 | 25 | `BGEVENT_READ` | `VioletCityPokecenterSign` (`jumpstd PokecenterSignScript`) |
|
||||
| 10 | 17 | `BGEVENT_READ` | `VioletCityMartSign` (`jumpstd MartSignScript`) |
|
||||
| 37 | 14 | `BGEVENT_ITEM` | `VioletCityHiddenHyperPotion` = `hiddenitem HYPER_POTION, EVENT_VIOLET_CITY_HIDDEN_HYPER_POTION` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `VIOLETCITY_EARL` | `SPRITE_FISHER` | 13 | 16 | `SPRITEMOVEDATA_SPINRANDOM_SLOW`, pal `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT` | `VioletCityEarlScript` | `EVENT_VIOLET_CITY_EARL` |
|
||||
| `VIOLETCITY_LASS` | `SPRITE_LASS` | 28 | 28 | `SPRITEMOVEDATA_WANDER`, radius 2/2, pal `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT` | `VioletCityLassScript` | -1 |
|
||||
| `VIOLETCITY_SUPER_NERD` | `SPRITE_SUPER_NERD` | 24 | 14 | `SPRITEMOVEDATA_WANDER`, radius 1/2, pal `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT` | `VioletCitySuperNerdScript` | -1 |
|
||||
| `VIOLETCITY_GRAMPS` | `SPRITE_GRAMPS` | 17 | 20 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT`, radius 1/0 | `OBJECTTYPE_SCRIPT` | `VioletCityGrampsScript` | -1 |
|
||||
| `VIOLETCITY_YOUNGSTER` | `SPRITE_YOUNGSTER` | 5 | 18 | `SPRITEMOVEDATA_SPINRANDOM_SLOW`, pal `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT` | `VioletCityYoungsterScript` | -1 |
|
||||
| `VIOLETCITY_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 14 | 29 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `VioletCityFruitTree` = `fruittree FRUITTREE_VIOLET_CITY` | -1 |
|
||||
| `VIOLETCITY_POKE_BALL1` | `SPRITE_POKE_BALL` | 4 | 1 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `VioletCityPPUp` | `EVENT_VIOLET_CITY_PP_UP` |
|
||||
| `VIOLETCITY_POKE_BALL2` | `SPRITE_POKE_BALL` | 35 | 5 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `VioletCityRareCandy` | `EVENT_VIOLET_CITY_RARE_CANDY` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `VioletCityFlypointCallback` (`48:4c6d`), registered as
|
||||
`callback MAPCALLBACK_NEWMAP`: a single `setflag ENGINE_FLYPOINT_VIOLET`
|
||||
then `endcallback`. Entering the map at all unlocks Fly to Violet.
|
||||
- `VioletCityEarlScript`: spins, `faceplayer`,
|
||||
`writetext Text_EarlAsksIfYouBeatFalkner`, `yesorno`. Answering **yes** is
|
||||
the `.PointlessJump` arm (one line of text, no state change). Answering **no**
|
||||
is `.FollowEarl`: `playmusic MUSIC_SHOW_ME_AROUND`,
|
||||
`follow VIOLETCITY_EARL, PLAYER`, a long
|
||||
`VioletCityFollowEarl_MovementData` walk to the academy door,
|
||||
`stopfollow`, `disappear VIOLETCITY_EARL`,
|
||||
`clearevent EVENT_EARLS_ACADEMY_EARL`. A bot that does not want to be
|
||||
dragged across the map should answer **yes**, or simply not talk to him.
|
||||
- The four remaining townsfolk are `jumptextfaceplayer` one-liners.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_FLYPOINT_VIOLET` (66) | `constants/engine_flags.asm:81` | `VioletCityFlypointCallback` | Fly destination unlocked on first map load |
|
||||
| `EVENT_VIOLET_CITY_EARL` (1738) | `constants/event_flags.asm:1132` | object-event flag; set by `disappear` in `.FollowEarl` | Clear = Earl still outside |
|
||||
| `EVENT_VIOLET_CITY_PP_UP` (1603) | `constants/event_flags.asm:996` | object-event flag | Ball taken |
|
||||
| `EVENT_VIOLET_CITY_RARE_CANDY` (1604) | `constants/event_flags.asm:997` | object-event flag | Ball taken |
|
||||
| `EVENT_VIOLET_CITY_HIDDEN_HYPER_POTION` (176) | `constants/event_flags.asm:186` | `hiddenitem` operand at bg_event (37,14) | Hidden item taken |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `PP_UP` | Poke Ball at (4,1) | `VioletCityPPUp` | `EVENT_VIOLET_CITY_PP_UP` |
|
||||
| `RARE_CANDY` | Poke Ball at (35,5) | `VioletCityRareCandy` | `EVENT_VIOLET_CITY_RARE_CANDY` |
|
||||
| `HYPER_POTION` | Hidden, A-press or Itemfinder on (37,14) | `VioletCityHiddenHyperPotion` | `EVENT_VIOLET_CITY_HIDDEN_HYPER_POTION` |
|
||||
| berry | `fruittree FRUITTREE_VIOLET_CITY` at (14,29) | `VioletCityFruitTree` | daily reset, not an `EVENT_*` |
|
||||
|
||||
None of these four are mentioned by the walkthrough section; they are listed
|
||||
because a bot walking this map will pass them.
|
||||
|
||||
**Trainers**
|
||||
|
||||
None on the overworld map.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
- Grass: no `def_grass_wildmons VIOLET_CITY` entry in `data/wild/johto_grass.asm`.
|
||||
- Water (`data/wild/johto_water.asm:225`): rate `2 percent`, L20 Poliwag,
|
||||
L15 Poliwag, L20 Poliwhirl - Surf only, i.e. not reachable in this section.
|
||||
- Fishing group `FISHGROUP_POND` (`data/maps/maps.asm:251`), table in
|
||||
`data/wild/fish.asm`.
|
||||
|
||||
---
|
||||
|
||||
### MAP_VIOLET_POKECENTER_1F
|
||||
|
||||
- Script: `maps/VioletPokecenter1F.asm`
|
||||
- Blocks: shared `VioletPokecenter1F_Blocks` label in `data/maps/blocks.asm:351`
|
||||
(same blockset as the other Pokecenter 1Fs; no dedicated `.blk`)
|
||||
- Header: `data/maps/maps.asm:256` -> `TILESET_POKECENTER`, `INDOOR`,
|
||||
`LANDMARK_VIOLET_CITY`, `MUSIC_POKEMON_CENTER`, `FALSE`, `PALETTE_DAY`,
|
||||
`FISHGROUP_SHORE`
|
||||
- Dimensions / attributes: `constants/map_constants.asm:235`
|
||||
(`map_const VIOLET_POKECENTER_1F, 5, 4`) = 10x8 cells;
|
||||
`data/maps/attributes.asm:538`
|
||||
- Connections: none
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 7 | `VIOLET_CITY` | 5 |
|
||||
| 2 | 4 | 7 | `VIOLET_CITY` | 5 |
|
||||
| 3 | 0 | 7 | `POKECENTER_2F` | 1 |
|
||||
|
||||
**Coord events** / **BG events**: both empty.
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `VIOLETPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `VioletPokecenterNurse` (`jumpstd PokecenterNurseScript`) | -1 |
|
||||
| `VIOLETPOKECENTER1F_SUPER_NERD` | `SPRITE_SUPER_NERD` | 7 | 6 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT`, radius 1/0, pal `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT` | `VioletPokecenter1FSuperNerdScript` | -1 |
|
||||
| `VIOLETPOKECENTER1F_GENTLEMAN` | `SPRITE_GENTLEMAN` | 1 | 4 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `VioletPokecenter1FGentlemanScript` | -1 |
|
||||
| `VIOLETPOKECENTER1F_YOUNGSTER` | `SPRITE_YOUNGSTER` | 8 | 1 | `SPRITEMOVEDATA_STANDING_DOWN`, pal `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT` | `VioletPokecenter1FYoungsterScript` | -1 |
|
||||
| `VIOLETPOKECENTER1F_ELMS_AIDE` | `SPRITE_SCIENTIST` | 4 | 3 | `SPRITEMOVEDATA_STANDING_DOWN`, pal `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT` | `VioletPokecenter1F_ElmsAideScript` | `EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `VioletPokecenterNurse` -> `jumpstd PokecenterNurseScript`: the only beat the
|
||||
walkthrough uses here ("go back to the Pokemon Center to heal").
|
||||
- `VioletPokecenter1F_ElmsAideScript` is **not** reachable during this section:
|
||||
`EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER` is set at new game
|
||||
(`engine/events/std_scripts.asm:468`), which masks the object, and is only
|
||||
cleared by Elm's `.assistant` phone call
|
||||
(`engine/phone/scripts/elm.asm:84`) queued by beating Falkner. It gives
|
||||
`giveegg TOGEPI, EGG_LEVEL` and belongs to the next section.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_ELMS_AIDE_IN_VIOLET_POKEMON_CENTER` (1792) | `constants/event_flags.asm:1186` | set in `engine/events/std_scripts.asm:468`, cleared in `engine/phone/scripts/elm.asm:84` | Set = aide hidden. Stays set for the whole of this section |
|
||||
|
||||
**Items / Trainers / Wild encounters**: none.
|
||||
|
||||
---
|
||||
|
||||
### MAP_VIOLET_GYM
|
||||
|
||||
- Script: `maps/VioletGym.asm`
|
||||
- Blocks: `maps/VioletGym.blk` (`data/maps/blocks.asm:734`)
|
||||
- Header: `data/maps/maps.asm:253` -> `TILESET_ELITE_FOUR_ROOM`, `INDOOR`,
|
||||
`LANDMARK_VIOLET_CITY`, `MUSIC_GYM`, phone `TRUE`, `PALETTE_DAY`,
|
||||
`FISHGROUP_SHORE`
|
||||
- Dimensions / attributes: `constants/map_constants.asm:232`
|
||||
(`map_const VIOLET_GYM, 5, 8`) = 10x16 cells;
|
||||
`data/maps/attributes.asm:535`
|
||||
- Connections: none
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 15 | `VIOLET_CITY` | 2 |
|
||||
| 2 | 5 | 15 | `VIOLET_CITY` | 2 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
None. There is no scripted trip-wire in the gym; Abe and Rod fire off eyesight
|
||||
alone.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 3 | 13 | `BGEVENT_READ` | `VioletGymStatue` |
|
||||
| 6 | 13 | `BGEVENT_READ` | `VioletGymStatue` |
|
||||
|
||||
`VioletGymStatue` branches on `checkflag ENGINE_ZEPHYRBADGE`: unbeaten ->
|
||||
`jumpstd GymStatue1Script`; beaten -> `gettrainername STRING_BUFFER_4, FALKNER,
|
||||
FALKNER1` + `jumpstd GymStatue2Script`. Reading it is a cheap way for a bot to
|
||||
confirm the badge landed.
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `VIOLETGYM_FALKNER` | `SPRITE_FALKNER` | 5 | 1 | `SPRITEMOVEDATA_STANDING_DOWN`, radius 0/0, pal `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT`, sight 0 | `VioletGymFalknerScript` | -1 |
|
||||
| `VIOLETGYM_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 7 | 6 | `SPRITEMOVEDATA_STANDING_LEFT`, radius 2/0, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerBirdKeeperRod` | -1 |
|
||||
| `VIOLETGYM_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 2 | 10 | `SPRITEMOVEDATA_STANDING_RIGHT`, radius 2/0, pal `PAL_NPC_BLUE` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerBirdKeeperAbe` | -1 |
|
||||
| `VIOLETGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 13 | `SPRITEMOVEDATA_STANDING_DOWN`, radius 0/0, pal `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT`, sight 0 | `VioletGymGuideScript` | -1 |
|
||||
|
||||
Walking in at (4,15)/(5,15) and heading north, **Abe** (facing right at y=10) is
|
||||
met before **Rod** (facing left at y=6), which is the order the walkthrough
|
||||
gives even though it names Abe first and Rod second.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `VioletGymFalknerScript` (`56:412f`):
|
||||
`faceplayer`, `opentext`, `checkevent EVENT_BEAT_FALKNER` /
|
||||
`iftrue .FightDone`. First pass: `writetext FalknerIntroText`,
|
||||
`winlosstext FalknerWinLossText, 0`, `loadtrainer FALKNER, FALKNER1`,
|
||||
`startbattle`, `reloadmapafterbattle`, `setevent EVENT_BEAT_FALKNER`,
|
||||
`writetext ReceivedZephyrBadgeText`, `playsound SFX_GET_BADGE`, `waitsfx`,
|
||||
**`setflag ENGINE_ZEPHYRBADGE`**, `readvar VAR_BADGES`,
|
||||
`scall VioletGymActivateRockets`.
|
||||
Then `.FightDone` (`56:4159`): `checkevent EVENT_GOT_TM31_MUD_SLAP` /
|
||||
`iftrue .SpeechAfterTM`; otherwise
|
||||
`setevent EVENT_BEAT_BIRD_KEEPER_ROD`, `setevent EVENT_BEAT_BIRD_KEEPER_ABE`
|
||||
(the two gym trainers are force-flagged so they never re-trigger),
|
||||
`setmapscene ELMS_LAB, SCENE_ELMSLAB_NOOP`,
|
||||
`specialphonecall SPECIALCALL_ASSISTANT`,
|
||||
`writetext FalknerZephyrBadgeText`, `promptbutton`,
|
||||
`verbosegiveitem TM_MUD_SLAP`, `iffalse .NoRoomForMudSlap`,
|
||||
`setevent EVENT_GOT_TM31_MUD_SLAP`, `writetext FalknerTMMudSlapText`.
|
||||
Unlike Sage Li, this one **does** check `verbosegiveitem`'s return, so a full
|
||||
bag leaves `EVENT_GOT_TM31_MUD_SLAP` clear and the TM is offered again on the
|
||||
next talk.
|
||||
- `VioletGymActivateRockets` (`56:4185`): `ifequal 7, .RadioTowerRockets`,
|
||||
`ifequal 6, .GoldenrodRockets` against the `VAR_BADGES` count. With one badge
|
||||
this is a no-op; it exists so that a late Violet clear still arms the Rocket
|
||||
arcs.
|
||||
- `TrainerBirdKeeperRod` (`56:4194`), `TrainerBirdKeeperAbe` (`56:41a8`):
|
||||
standard `trainer` headers + `endifjustbattled` after-battle text.
|
||||
- `VioletGymGuideScript`: branches on `EVENT_BEAT_FALKNER`; pure text.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BEAT_FALKNER` (1213) | `constants/event_flags.asm:706` | read+written by `VioletGymFalknerScript`; read by `VioletGymGuideScript` | The gym-cleared flag |
|
||||
| `ENGINE_ZEPHYRBADGE` (26) | `constants/engine_flags.asm:38` | `setflag` in `VioletGymFalknerScript`; `checkflag` in `VioletGymStatue`; `CheckBadge` in `engine/events/overworld.asm` | The badge bit. Gates out-of-battle Flash and the Gen 2 attack boost (`engine/battle/core.asm:6566`) |
|
||||
| `EVENT_GOT_TM31_MUD_SLAP` (8) | `constants/event_flags.asm:14` | `VioletGymFalknerScript` | TM31 collected |
|
||||
| `EVENT_BEAT_BIRD_KEEPER_ROD` (1019) | `constants/event_flags.asm:494` | `TrainerBirdKeeperRod`; also force-set by `VioletGymFalknerScript.FightDone` | Beaten |
|
||||
| `EVENT_BEAT_BIRD_KEEPER_ABE` (1020) | `constants/event_flags.asm:495` | `TrainerBirdKeeperAbe`; also force-set by `.FightDone` | Beaten |
|
||||
| `SCENE_ELMSLAB_NOOP` | `maps/ElmsLab.asm` (macro-generated) | `setmapscene ELMS_LAB, ...` in `.FightDone` | Disarms the lab's remaining scene script |
|
||||
| `SPECIALCALL_ASSISTANT` | `constants/phone_constants.asm:47` | `specialphonecall` in `.FightDone`; consumed by `ElmPhoneCallerScript` (`engine/phone/scripts/elm.asm:67`) | Queues the Togepi-egg phone call |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| ZEPHYRBADGE | Automatic on beating Falkner | `VioletGymFalknerScript` (`setflag ENGINE_ZEPHYRBADGE`) | `ENGINE_ZEPHYRBADGE` |
|
||||
| `TM_MUD_SLAP` (TM31) | `verbosegiveitem` after the badge text | `VioletGymFalknerScript.FightDone` | `EVENT_GOT_TM31_MUD_SLAP` |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `BIRD_KEEPER`, `ROD` | `BIRD_KEEPER` (18) | `ROD` (1) | `BirdKeeperGroup` "ROD" (`0e:5f05` group base): L7 Pidgey, L7 Pidgey, `TRAINERTYPE_NORMAL` | `TrainerBirdKeeperRod` | none |
|
||||
| `BIRD_KEEPER`, `ABE` | `BIRD_KEEPER` | `ABE` (2) | `BirdKeeperGroup` "ABE": L9 Spearow, `TRAINERTYPE_NORMAL` | `TrainerBirdKeeperAbe` | none |
|
||||
| `FALKNER`, `FALKNER1` | `FALKNER` (1) | `FALKNER1` (1) | `FalknerGroup` (`0e:59c2`), `TRAINERTYPE_MOVES`: L7 Pidgey (TACKLE, MUD_SLAP), L9 Pidgeotto (TACKLE, MUD_SLAP, GUST) | `VioletGymFalknerScript` (`loadtrainer`) | listed in `data/trainers/leaders.asm:8` |
|
||||
|
||||
Prize money: Bird Keeper base 6 (`data/trainers/attributes.asm`) -> Rod
|
||||
6 x 7 x 4 = 168, Abe 6 x 9 x 4 = 216. Falkner base 25 -> 25 x 9 x 4 = 900.
|
||||
Falkner DVs `dn 9, 10, 7, 7` (`data/trainers/dvs.asm:5`); Bird Keeper
|
||||
`dn 9, 8, 8, 8` (line 28).
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
None (indoor gym).
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Sprout Tower 3F rival cutscene fires before you can reach Sage Li | `maps/SproutTower3F.asm` `coord_event 11, 9, SCENE_SPROUTTOWER3F_RIVAL_ENCOUNTER, SproutTower3FRivalScene` | Standing on (11,9) while `wSproutTower3FSceneID` == 0 | The scene's own `setscene SCENE_SPROUTTOWER3F_NOOP`. Not a hard block - the tile is on the only path north |
|
||||
| HM05 Flash | `maps/SproutTower3F.asm:SageLiScript` | Beat `SAGE, LI` (`loadtrainer` + `startbattle`) | `verbosegiveitem HM_FLASH` + `setevent EVENT_GOT_HM05_FLASH` |
|
||||
| Using Flash outside battle | `engine/events/overworld.asm` `FlashFunction.CheckUseFlash` (`03:48f1`): `ld de, ENGINE_ZEPHYRBADGE` / `farcall CheckBadge` / `jr c, .nozephyrbadge` | `ENGINE_ZEPHYRBADGE` set **and** `wTimeOfDayPalset == DARKNESS_PALSET` | Beat Falkner. Note the second half: even with the badge, Flash is refused on any map that is not a dark cave (`.notadarkcave` -> `FieldMoveFailed`) |
|
||||
| ZEPHYRBADGE attack boost / obedience | `engine/battle/core.asm:6566` (the `and` mask that starts `(1 << ZEPHYRBADGE)`) | badge bit | Beat Falkner |
|
||||
| Gym trainers Abe and Rod | `maps/VioletGym.asm` object events, `OBJECTTYPE_TRAINER` sight 3 on shared rows | eyesight only; they do **not** physically block the aisle | Beating them, or `EVENT_BEAT_BIRD_KEEPER_*` being force-set by `VioletGymFalknerScript.FightDone` |
|
||||
| Falkner re-battle | `maps/VioletGym.asm:VioletGymFalknerScript` `checkevent EVENT_BEAT_FALKNER` / `iftrue .FightDone` | none | one-shot |
|
||||
| Sage Li re-battle | `maps/SproutTower3F.asm:SageLiScript` `checkevent EVENT_GOT_HM05_FLASH` / `iftrue .GotFlash` | none | one-shot, keyed on the HM flag rather than on `EVENT_BEAT_SAGE_LI` |
|
||||
| Sprout Tower is a `DUNGEON` | `data/maps/maps.asm:79-81` | - | `ESCAPE_ROPE` and Dig work here, which is what the walkthrough's "you may use that to get out of the tower" relies on |
|
||||
|
||||
Nothing in this section is gated on a field move, a key item, or an NPC standing
|
||||
on a tile. The only hard requirement is winning battles.
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
Coordinates are asm map cells. Port driver form:
|
||||
`world:setMap("<MAP_CONST>", x, y, facing)`, as in
|
||||
`tests/drivers/gold_walk_smoke.lua:58`.
|
||||
|
||||
| # | Map | Target | Intent | Precondition | Postcondition |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `SPROUT_TOWER_1F` | warp 1 (9,15) | enter from `VIOLET_CITY` (23,5) | - | on 1F |
|
||||
| 2 | `SPROUT_TOWER_1F` | (6,4) | walk onto warp 3 | - | on `SPROUT_TOWER_2F` at (6,4) |
|
||||
| 3 | `SPROUT_TOWER_2F` | `SPROUTTOWER2F_SAGE1` at (14,4), sight 3 wide facing spin | step into his line, battle | `EVENT_BEAT_SAGE_NICO` clear | `EVENT_BEAT_SAGE_NICO` set |
|
||||
| 4 | `SPROUT_TOWER_2F` | (17,3) | walk onto warp 3 | - | on `SPROUT_TOWER_1F` at (17,3) |
|
||||
| 5 | `SPROUT_TOWER_1F` | ball at (16,7) | face + A | `EVENT_SPROUT_TOWER_1F_PARLYZ_HEAL` clear | `PARLYZ_HEAL` in bag, flag set |
|
||||
| 6 | `SPROUT_TOWER_1F` | `SPROUTTOWER1F_SAGE3` at (2,1), facing RIGHT, sight 1 | step to (3,1), battle | `EVENT_BEAT_SAGE_CHOW` clear | `EVENT_BEAT_SAGE_CHOW` set |
|
||||
| 7 | `SPROUT_TOWER_1F` | (2,6) | walk onto warp 4 | - | on `SPROUT_TOWER_2F` at (2,6) |
|
||||
| 8 | `SPROUT_TOWER_2F` | ball at (3,1) | face + A | `EVENT_SPROUT_TOWER_2F_X_DEFEND` clear | `X_DEFEND` in bag, flag set |
|
||||
| 9 | `SPROUT_TOWER_2F` | `SPROUTTOWER2F_SAGE2` at (3,15), facing UP, sight 4 | approach from above, battle | `EVENT_BEAT_SAGE_EDMOND` clear | `EVENT_BEAT_SAGE_EDMOND` set |
|
||||
| 10 | `SPROUT_TOWER_2F` | (10,14) | walk onto warp 4 | - | on `SPROUT_TOWER_3F` at (10,14) |
|
||||
| 11 | `SPROUT_TOWER_3F` | ball at (6,14) | face + A | `EVENT_SPROUT_TOWER_3F_POTION` clear | `POTION` in bag, flag set |
|
||||
| 12 | `SPROUT_TOWER_3F` | `SPROUTTOWER3F_SAGE1` at (8,13), facing RIGHT, sight 3 | battle | `EVENT_BEAT_SAGE_JIN` clear | `EVENT_BEAT_SAGE_JIN` set |
|
||||
| 13 | `SPROUT_TOWER_3F` | `SPROUTTOWER3F_SAGE4` at (11,11), facing LEFT, sight 3 | battle | `EVENT_BEAT_SAGE_NEAL` clear | `EVENT_BEAT_SAGE_NEAL` set |
|
||||
| 14 | `SPROUT_TOWER_3F` | `SPROUTTOWER3F_SAGE2` at (8,8), facing DOWN, sight 2 | battle | `EVENT_BEAT_SAGE_TROY` clear | `EVENT_BEAT_SAGE_TROY` set |
|
||||
| 15 | `SPROUT_TOWER_3F` | (11,9) | walk onto the coord event | scene id == `SCENE_SPROUTTOWER3F_RIVAL_ENCOUNTER` (0) | cutscene runs; player ends at (11,5); `EVENT_RIVAL_SPROUT_TOWER` set; scene id -> 1 |
|
||||
| 16 | `SPROUT_TOWER_3F` | `SPROUTTOWER3F_SAGE3` at (10,2) | face + A, battle `SAGE, LI` | `EVENT_GOT_HM05_FLASH` clear; **bag must have room for an HM** | `HM_FLASH`, `EVENT_GOT_HM05_FLASH`, `EVENT_BEAT_SAGE_LI` |
|
||||
| 17 | `SPROUT_TOWER_3F` | ball at (14,1) | face + A | `EVENT_SPROUT_TOWER_3F_ESCAPE_ROPE` clear | `ESCAPE_ROPE` in bag, flag set |
|
||||
| 18 | `SPROUT_TOWER_3F` | (10,14) | warp 1, then 2F warp 1/2/3 down to 1F, then 1F warp 1 (9,15) | - | back on `VIOLET_CITY` at (23,5) |
|
||||
| 19 | `VIOLET_CITY` | (31,25) | walk onto warp 5 | - | on `VIOLET_POKECENTER_1F` at (3,7) |
|
||||
| 20 | `VIOLET_POKECENTER_1F` | `VIOLETPOKECENTER1F_NURSE` at (3,1) | face + A, accept heal | - | party healed |
|
||||
| 21 | `VIOLET_POKECENTER_1F` | (3,7) | warp 1 | - | back on `VIOLET_CITY` at (31,25) |
|
||||
| 22 | `VIOLET_CITY` | (18,17) | walk onto warp 2 | - | on `VIOLET_GYM` at (4,15) |
|
||||
| 23 | `VIOLET_GYM` | `VIOLETGYM_YOUNGSTER2` at (2,10), facing RIGHT, sight 3 | enter his row, battle Abe | `EVENT_BEAT_BIRD_KEEPER_ABE` clear | flag set |
|
||||
| 24 | `VIOLET_GYM` | `VIOLETGYM_YOUNGSTER1` at (7,6), facing LEFT, sight 3 | enter his row, battle Rod | `EVENT_BEAT_BIRD_KEEPER_ROD` clear | flag set |
|
||||
| 25 | `VIOLET_GYM` | (4,15) -> `VIOLET_CITY` -> (31,25) PC | optional re-heal before the leader | - | party healed |
|
||||
| 26 | `VIOLET_GYM` | `VIOLETGYM_FALKNER` at (5,1) | face + A, battle | `EVENT_BEAT_FALKNER` clear | `EVENT_BEAT_FALKNER`, `ENGINE_ZEPHYRBADGE`, Rod/Abe flags force-set, `SCENE_ELMSLAB_NOOP`, `SPECIALCALL_ASSISTANT` queued |
|
||||
| 27 | `VIOLET_GYM` | same NPC, second talk if bag was full | face + A | `EVENT_GOT_TM31_MUD_SLAP` clear | `TM_MUD_SLAP`, `EVENT_GOT_TM31_MUD_SLAP` |
|
||||
| 28 | `VIOLET_GYM` | bg_event (3,13) or (6,13) | face + A (verification only) | - | `GymStatue2Script` text = badge confirmed |
|
||||
|
||||
Optional pickups on the way through, none of which the walkthrough mentions:
|
||||
`VIOLET_CITY` PP Up (4,1), Rare Candy (35,5), hidden Hyper Potion (37,14),
|
||||
berry tree (14,29).
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Map load, block grid, warps for all six maps (data-driven from the ROM) | `src/world/gen2/Map.lua`, `src/import/RomExtractorGen2.lua` (`readMapGroupEntry`, `mapNameByIds`) | implemented - ids are the same `MAP_*` names, see `tools/rom_manifest_gold.json` -> `constants.mapGroups` |
|
||||
| `MAPCALLBACK_NEWMAP` -> `setflag ENGINE_FLYPOINT_VIOLET` | `src/world/gen2/World.lua` (callback dispatch), driver `tests/drivers/gold_map_callbacks.lua` | implemented |
|
||||
| Fly point 66 = Violet | `src/world/gen2/FieldMoves.lua:346` (`{ landmark = "LANDMARK_VIOLET_CITY", spawn = "SPAWN_VIOLET", flag = 66 }`) | implemented |
|
||||
| Script VM for every `maps/*.asm` script in this section | `src/script/gen2/Vm.lua`, `src/script/gen2/Opcodes.lua` | implemented - scripts are disassembled from the ROM, not hand-ported, so `SageLiScript` / `VioletGymFalknerScript` run as-is |
|
||||
| `coord_event` + scene ids (the 3F rival trip-wire) | `src/world/gen2/World.lua:5013` (coord event scan), `:1183` / `:5026` (scene scripts) | implemented |
|
||||
| Cutscene opcodes used by `SproutTower3FRivalScene` (`showemote`, `earthquake`, `applymovement`, `turnobject`, `disappear`, `setscene`, `playmusic`) | `src/script/gen2/Opcodes.lua:122/125`, `src/world/gen2/World.lua:1006/1528/647`, `src/script/gen2/Movement.lua` | implemented |
|
||||
| `special FadeOutMusic` / `FadeOutToBlack` / `ReloadSpritesNoPalettes` / `FadeInFromBlack` / `RestartMapMusic` | `src/script/gen2/Specials.lua` | implemented |
|
||||
| Object masking by event flag (rival, item balls) | `src/world/gen2/Events.lua` (`objectVisible`), documented against `CheckObjectFlag` | implemented |
|
||||
| Trainer objects: eyesight, approach walk, seen text, battle, beat flag | `src/world/gen2/Trainers.lua`, `src/world/gen2/World.lua:5205-5253`, driver `tests/drivers/gold_trainer_smoke.lua` | implemented |
|
||||
| `loadtrainer` / `startbattle` for Sage Li and Falkner | `src/script/gen2/Vm.lua`, `src/battle/gen2/Battle.lua` | implemented |
|
||||
| Gym leader battle music | `src/battle/gen2/BattleMusic.lua:24` (`FALKNER = true`) | implemented |
|
||||
| Prize money (Falkner 900 = 25 x 9 x 4) | `src/battle/gen2/Prize.lua`, `tests/gen2_prize_test.lua` | implemented |
|
||||
| `setflag ENGINE_ZEPHYRBADGE` (id 26) and `readvar VAR_BADGES` | `src/script/gen2/Vm.lua:670`, `src/world/gen2/World.lua:117/1240`, `tests/gen2_world_test.lua:1783` | implemented |
|
||||
| Flash badge gate (`FlashFunction.CheckUseFlash`) | `src/world/gen2/FieldMoves.lua:105` (`FLASH = "ZEPHYR"`), `tests/gen2_world_test.lua:755-781` | implemented, including the "refused before the darkness check" ordering |
|
||||
| `verbosegiveitem` (HM05, TM31) incl. the `iffalse` no-room arm | `src/script/gen2/Vm.lua:490-500` | implemented |
|
||||
| Hidden item at Violet City (37,14) | `src/world/gen2/HiddenItems.lua`, `src/world/gen2/World.lua:5285` | implemented |
|
||||
| Berry tree `fruittree FRUITTREE_VIOLET_CITY` | `src/script/gen2/CallAsm.lua:75-77`, `src/core/gen2/Apricorns.lua:363` | implemented |
|
||||
| `specialphonecall SPECIALCALL_ASSISTANT` | `src/script/gen2/Opcodes.lua:161`, `src/core/gen2/Phone.lua:390` | implemented |
|
||||
| `giveegg TOGEPI` (Elm's aide, next section's payoff) | `src/script/gen2/Vm.lua:453`, driver `tests/drivers/gold_egg_hatch.lua` | implemented |
|
||||
| Earl's `follow` / `stopfollow` escort | `src/script/gen2/Vm.lua:982` | implemented |
|
||||
| **Poke Ball item pickup (`OBJECTTYPE_ITEMBALL`)** - Parlyz Heal, X Defend, Potion, Escape Rope, PP Up, Rare Candy | `src/import/RomExtractorGen2.lua:2969` writes `obj.itemball`; **nothing in `src/` reads it**, and `World:interact()` (`src/world/gen2/World.lua:5257`) has no itemball branch (trainer / strength boulder / `scriptKey` / bg sign / hidden item only). `src/script/gen2/CallAsm.lua:550` stubs `TryReceiveItem` out on purpose | **missing** - the balls render and are masked correctly, but pressing A on one does nothing. Every item ball in this section is unobtainable in the port today |
|
||||
| Hand-ported per-map Lua for Sprout Tower / Violet Gym | none - by design | n/a (the port runs the ROM's own bytecode) |
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
1. **"X Accuracy" in the Sprout Tower item list.** The walkthrough's header
|
||||
lists Escape Rope, HM05, Parlyz Heal, Potion, **X Accuracy**, and the prose
|
||||
says "grab that X Defense above you". The asm has exactly one such ball,
|
||||
`SproutTower2FXDefend` = `itemball X_DEFEND` at 2F (3,1). There is no
|
||||
`X_ACCURACY` anywhere in `maps/SproutTower*.asm`. The header list is wrong;
|
||||
the prose is right.
|
||||
|
||||
2. **"Go up to the item to the right of the monk. In reality, it is a...
|
||||
Level 3 Rattata"** and the follow-up "Go back to the Rattata you faced
|
||||
before, and now it's an Escape Rope." Nothing in `maps/SproutTower3F.asm`
|
||||
supports this. The object at (14,1) is a plain
|
||||
`object_event ... OBJECTTYPE_ITEMBALL, 0, SproutTower3FEscapeRope,
|
||||
EVENT_SPROUT_TOWER_3F_ESCAPE_ROPE` whose script label is a bare
|
||||
`itemball ESCAPE_ROPE`. There is no `wildbattle`, no `loadwildmon`, no
|
||||
second object at that cell, and no branch on `EVENT_GOT_HM05_FLASH` anywhere
|
||||
near it. This looks like the walkthrough importing the Gen 1 Rocket Hideout
|
||||
Voltorb trick. **Treat the ball at (14,1) as an ordinary Escape Rope.**
|
||||
|
||||
3. **"Pokemon found in Sprout Tower: Rattata, Bellsprout, Gastly."**
|
||||
`data/wild/johto_grass.asm` gives Rattata (morn/day) and Gastly + Rattata
|
||||
(nite) for `SPROUT_TOWER_2F` and `SPROUT_TOWER_3F`, and **no table at all**
|
||||
for `SPROUT_TOWER_1F`. Bellsprout is not a Sprout Tower wild encounter; the
|
||||
Bellsprouts are the sages' party members.
|
||||
|
||||
4. **Sage Li's party order.** The walkthrough lists L7 Bellsprout, L10 Hoothoot,
|
||||
L7 Bellsprout. `data/trainers/parties.asm` `SageGroup` "LI" is
|
||||
`db 7, BELLSPROUT / db 7, BELLSPROUT / db 10, HOOTHOOT` - the Hoothoot is
|
||||
last, not second. The 320G figure the walkthrough quotes only works with the
|
||||
asm order, since `ComputeTrainerReward` uses the level of the **last** party
|
||||
row (8 x 10 x 4 = 320).
|
||||
|
||||
5. **Walkthrough trainer order in the gym.** The text says "Head up to fight
|
||||
your trainer battles first. Bird Keeper Abe ... Head up the path to the
|
||||
second trainer. Bird Keeper Rod." Geometrically that is correct (Abe at
|
||||
y=10 is south of Rod at y=6), but be aware the *object const* order in the
|
||||
file is Rod first (`VIOLETGYM_YOUNGSTER1`), Abe second
|
||||
(`VIOLETGYM_YOUNGSTER2`), which is the opposite of the walk order. Do not
|
||||
index trainers by object const when following the prose.
|
||||
|
||||
6. **"You'll need the 1st badge to use it [Flash]".** True but incomplete:
|
||||
`FlashFunction.CheckUseFlash` (`engine/events/overworld.asm`, `03:48f1`)
|
||||
requires `ENGINE_ZEPHYRBADGE` **and** `wTimeOfDayPalset == DARKNESS_PALSET`.
|
||||
With the badge, on any non-dark map, the move still fails through
|
||||
`.notadarkcave` -> `FieldMoveFailed`. A bot should not treat "have badge" as
|
||||
"Flash will work here".
|
||||
|
||||
7. **EXP values.** The walkthrough quotes per-Pokemon EXP figures (54, 108, 126,
|
||||
217, ...). Those are computed from base experience and level at runtime
|
||||
(`engine/battle/experience.asm` / `data/pokemon/base_stats/`), not stored
|
||||
anywhere as a per-trainer constant, so they were not verified against a
|
||||
single asm line here. The **money** figures were: all seven quoted values
|
||||
match `base reward x last party level x 4`.
|
||||
|
||||
8. **"you should be around an hour into the game now"** and the video link -
|
||||
prose, nothing to resolve.
|
||||
@@ -0,0 +1,907 @@
|
||||
# Section 04 - Slowpoke Well & Azalea Town Gym
|
||||
|
||||
Source: `../section-04-slowpoke-well-azalea-town-gym.txt`
|
||||
Maps covered: `MAP_AZALEA_TOWN`, `MAP_KURTS_HOUSE`, `MAP_AZALEA_POKECENTER_1F`,
|
||||
`MAP_SLOWPOKE_WELL_B1F`, `MAP_SLOWPOKE_WELL_B2F` (off-route, Surf-only),
|
||||
`MAP_AZALEA_GYM`. `MAP_AZALEA_MART` is referenced for its stock only
|
||||
(`data/items/marts.asm` `MartAzalea`); its map file was not transcribed.
|
||||
Badges / key milestones in this section: **HIVEBADGE** (`ENGINE_HIVEBADGE`),
|
||||
`EVENT_CLEARED_SLOWPOKE_WELL`, `EVENT_BEAT_BUGSY`, `TM_FURY_CUTTER` (TM49),
|
||||
`LURE_BALL`, `SUPER_POTION`, `WHT_APRICORN`, hidden `FULL_HEAL`. Clearing the
|
||||
well is what unlocks the Gym door and arms the Azalea rival battle for the next
|
||||
section.
|
||||
|
||||
Coordinate convention throughout: the raw asm x/y from
|
||||
`warp_event` / `coord_event` / `bg_event` / `object_event`, i.e. walk cells
|
||||
(two 8x8 tiles per side, two cells per map block). `macros/scripts/maps.asm`
|
||||
adds the +4 border internally; the numbers below are the pre-border values as
|
||||
written in the map file.
|
||||
|
||||
---
|
||||
|
||||
## 1. Route order
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `MAP_AZALEA_TOWN` | `maps/AzaleaTown.asm` | west edge of Route 33 (`connection east, Route33, ROUTE_33, 0` in `data/maps/attributes.asm`) | warp 4, `warp_event 9, 5, KURTS_HOUSE, 1` | Rocket guard is standing on the well; head west then north to Kurt's house |
|
||||
| 2 | `MAP_KURTS_HOUSE` | `maps/KurtsHouse.asm` | warp 1 (`3, 7`) | warps 1/2 (`3, 7` / `4, 7`) back to `AZALEA_TOWN` warp 4 | `Kurt1` first-visit arm: Kurt storms off to the well and sets `EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET`, which hides the guard |
|
||||
| 3 | `MAP_AZALEA_POKECENTER_1F` | `maps/AzaleaPokecenter1F.asm` | `AZALEA_TOWN` warp 1 (`15, 9`) | warps 1/2 (`3, 7` / `4, 7`) | "heal at the Pokemon Center" |
|
||||
| 4 | `MAP_AZALEA_TOWN` | `maps/AzaleaTown.asm` | Pokecenter warp | warp 6, `warp_event 31, 7, SLOWPOKE_WELL_B1F, 1` | walk east to the now-unguarded well |
|
||||
| 5 | `MAP_SLOWPOKE_WELL_B1F` | `maps/SlowpokeWellB1F.asm` | warp 1 (`17, 15`) | **scripted** `warp KURTS_HOUSE, 3, 3` at the end of `TrainerGruntM1.Script` | four Rocket grunts, Super Potion, tailless Slowpoke |
|
||||
| 6 | `MAP_KURTS_HOUSE` | `maps/KurtsHouse.asm` | script warp to `3, 3` | warps 1/2 | `Kurt1.ClearedSlowpokeWell` -> free `LURE_BALL` |
|
||||
| 7 | `MAP_AZALEA_TOWN` | `maps/AzaleaTown.asm` | Kurt's House warp | fruit tree object at `8, 2`, then warp 1 (Pokecenter) | `WHT_APRICORN` from `WhiteApricornTree` / `FRUITTREE_AZALEA_TOWN`, then heal |
|
||||
| 8 | `MAP_AZALEA_GYM` | `maps/AzaleaGym.asm` | `AZALEA_TOWN` warp 5, `warp_event 10, 15, AZALEA_GYM, 1` | warps 1/2 (`4, 15` / `5, 15`) | five gym trainers + Bugsy -> HIVEBADGE + TM49 |
|
||||
|
||||
Off-route in this section: `MAP_SLOWPOKE_WELL_B2F` (`maps/SlowpokeWellB2F.asm`)
|
||||
hangs off `SLOWPOKE_WELL_B1F` warp 2 at `7, 11`, but the only land approach to
|
||||
that ladder is across water (see the derived grid under B1F) - it is a Surf
|
||||
return trip, not part of this visit, and holds the King's Rock and TM Rain Dance.
|
||||
|
||||
Spills into the next section: `AzaleaTown_MapEvents` has
|
||||
`coord_event 5, 10` / `coord_event 5, 11` for `SCENE_AZALEATOWN_RIVAL_BATTLE`.
|
||||
`TrainerGruntM1.Script` arms that scene with
|
||||
`setmapscene AZALEA_TOWN, SCENE_AZALEATOWN_RIVAL_BATTLE`, so the rival ambush
|
||||
fires the first time you walk west toward the Ilex Forest gate. That battle and
|
||||
Ilex Forest belong to section 05.
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_AZALEA_TOWN
|
||||
|
||||
- Script: `maps/AzaleaTown.asm`
|
||||
- Blocks: `maps/AzaleaTown.blk`
|
||||
- Header: `data/maps/maps.asm:232` -> `TILESET_JOHTO_MODERN`, `TOWN`,
|
||||
`LANDMARK_AZALEA_TOWN`, `MUSIC_AZALEA_TOWN`, phone `FALSE`, `PALETTE_AUTO`,
|
||||
`FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:213` -> `map_const AZALEA_TOWN, 20, 9`
|
||||
(20x9 blocks = 40x18 walk cells)
|
||||
- Connections (`data/maps/attributes.asm:132`): `map_attributes AzaleaTown, AZALEA_TOWN, $05`,
|
||||
`connection west, Route34, ROUTE_34, -18`, `connection east, Route33, ROUTE_33, 0`
|
||||
- Scene var: `data/maps/scenes.asm:37` -> `scene_var AZALEA_TOWN, wAzaleaTownSceneID`
|
||||
- Spawn / fly landing: `data/maps/spawn_points.asm:31` -> `spawn AZALEA_TOWN, 15, 10`;
|
||||
flypoint row `data/maps/flypoints.asm:8` -> `db LANDMARK_AZALEA_TOWN, SPAWN_AZALEA`
|
||||
- Map scripts: `def_scene_scripts` = `AzaleaTownNoop1Scene` (`SCENE_AZALEATOWN_NOOP` = 0),
|
||||
`AzaleaTownNoop2Scene` (`SCENE_AZALEATOWN_RIVAL_BATTLE` = 1);
|
||||
`def_callbacks` = `callback MAPCALLBACK_NEWMAP, AzaleaTownFlypointCallback`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 15 | 9 | `AZALEA_POKECENTER_1F` | 1 |
|
||||
| 2 | 21 | 13 | `CHARCOAL_KILN` | 1 |
|
||||
| 3 | 21 | 5 | `AZALEA_MART` | 2 |
|
||||
| 4 | 9 | 5 | `KURTS_HOUSE` | 1 |
|
||||
| 5 | 10 | 15 | `AZALEA_GYM` | 1 |
|
||||
| 6 | 31 | 7 | `SLOWPOKE_WELL_B1F` | 1 |
|
||||
| 7 | 2 | 10 | `ILEX_FOREST_AZALEA_GATE` | 3 |
|
||||
| 8 | 2 | 11 | `ILEX_FOREST_AZALEA_GATE` | 4 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_AZALEATOWN_RIVAL_BATTLE` | 5 | 10 | `AzaleaTownRivalBattleScene1` | rival ambush, approach from the east; next section |
|
||||
| `SCENE_AZALEATOWN_RIVAL_BATTLE` | 5 | 11 | `AzaleaTownRivalBattleScene2` | same battle, second entry row; next section |
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 19 | 9 | `BGEVENT_READ` | `AzaleaTownSign` |
|
||||
| 10 | 9 | `BGEVENT_READ` | `KurtsHouseSign` |
|
||||
| 14 | 15 | `BGEVENT_READ` | `AzaleaGymSign` |
|
||||
| 29 | 7 | `BGEVENT_READ` | `SlowpokeWellSign` |
|
||||
| 19 | 13 | `BGEVENT_READ` | `CharcoalKilnSign` |
|
||||
| 16 | 9 | `BGEVENT_READ` | `AzaleaTownPokecenterSign` (`jumpstd PokecenterSignScript`) |
|
||||
| 22 | 5 | `BGEVENT_READ` | `AzaleaTownMartSign` (`jumpstd MartSignScript`) |
|
||||
| 3 | 9 | `BGEVENT_READ` | `AzaleaTownIlextForestSign` |
|
||||
| 31 | 6 | `BGEVENT_ITEM` | `AzaleaTownHiddenFullHeal` -> `hiddenitem FULL_HEAL, EVENT_AZALEA_TOWN_HIDDEN_FULL_HEAL` |
|
||||
|
||||
**Object events** (`def_object_events`) - `object_const_def` is `const_def 2`, so
|
||||
the first const is 2 and row N carries const N+1.
|
||||
|
||||
| const | sprite | x | y | movement (radius x,y / hours / pal) | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 2 `AZALEATOWN_AZALEA_ROCKET1` | `SPRITE_AZALEA_ROCKET` | 31 | 9 | `STANDING_DOWN` 0,0 / -1,-1 / 0 | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownRocket1Script` | `EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET` |
|
||||
| 3 `AZALEATOWN_GRAMPS` | `SPRITE_GRAMPS` | 21 | 9 | `WANDER` 1,2 / -1,-1 / 0 | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownGrampsScript` | -1 |
|
||||
| 4 `AZALEATOWN_TEACHER` | `SPRITE_TEACHER` | 15 | 13 | `WALK_UP_DOWN` 0,2 / -1,-1 / `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownTeacherScript` | -1 |
|
||||
| 5 `AZALEATOWN_YOUNGSTER` | `SPRITE_YOUNGSTER` | 7 | 9 | `WALK_LEFT_RIGHT` 1,0 / -1,-1 / `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownYoungsterScript` | -1 |
|
||||
| 6 `AZALEATOWN_SLOWPOKE1` | `SPRITE_SLOWPOKE` | 8 | 17 | `STILL` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownSlowpokeScript` | `EVENT_AZALEA_TOWN_SLOWPOKES` |
|
||||
| 7 `AZALEATOWN_SLOWPOKE2` | `SPRITE_SLOWPOKE` | 18 | 9 | `STILL` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownSlowpokeScript` | `EVENT_AZALEA_TOWN_SLOWPOKES` |
|
||||
| 8 `AZALEATOWN_SLOWPOKE3` | `SPRITE_SLOWPOKE` | 29 | 9 | `STILL` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownSlowpokeScript` | `EVENT_AZALEA_TOWN_SLOWPOKES` |
|
||||
| 9 `AZALEATOWN_SLOWPOKE4` | `SPRITE_SLOWPOKE` | 15 | 15 | `STILL` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownSlowpokeScript` | `EVENT_AZALEA_TOWN_SLOWPOKES` |
|
||||
| 10 `AZALEATOWN_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 8 | 2 | `STILL` | `OBJECTTYPE_SCRIPT`, 0 | `WhiteApricornTree` | -1 |
|
||||
| 11 `AZALEATOWN_RIVAL` | `SPRITE_AZALEA_ROCKET` | 11 | 10 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT`, 0 | `ObjectEvent` | `EVENT_RIVAL_AZALEA_TOWN` |
|
||||
| 12 `AZALEATOWN_AZALEA_ROCKET3` | `SPRITE_AZALEA_ROCKET` | 10 | 16 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaTownRocket2Script` | `EVENT_SLOWPOKE_WELL_ROCKETS` |
|
||||
|
||||
`SPRITE_AZALEA_ROCKET` (`constants/sprite_constants.asm:156`, id `$f6`) is a
|
||||
variable sprite. `engine/events/std_scripts.asm:539` seeds it as
|
||||
`variablesprite SPRITE_AZALEA_ROCKET, SPRITE_ROCKET`; `TrainerGruntM1.Script`
|
||||
in `maps/SlowpokeWellB1F.asm` re-points it with
|
||||
`variablesprite SPRITE_AZALEA_ROCKET, SPRITE_RIVAL`, which is why const 11 draws
|
||||
as the rival even though the row names the Rocket slot.
|
||||
|
||||
**Derived walkability** (from `maps/AzaleaTown.blk` + `data/tilesets/johto_modern_collision.asm`
|
||||
+ `data/collision/collision_permissions.asm`; `.` land, `#` wall, `h` ledge hop,
|
||||
`W` warp tile, columns are x, rows are y)
|
||||
|
||||
```
|
||||
0123456789012345678901234567890123456789
|
||||
0 ###############################.........
|
||||
1 ######....#####################.........
|
||||
2 ######....#####################.........
|
||||
3 ######....##############################
|
||||
4 ######..################################
|
||||
5 ######..#W###########W######......######
|
||||
6 ######hhh.hh########....####..###.######
|
||||
7 #########.##########....####.##W#.######
|
||||
8 ##...#......######............#.#.######
|
||||
9 ######....#.###W##.#..............######
|
||||
10 ##W.........................hhh.hh######
|
||||
11 ##W.....................#######.########
|
||||
12 ######..######......########......######
|
||||
13 ######..######.....##W######............
|
||||
14 ######..######..........####............
|
||||
15 ######..##W####.........####............
|
||||
16 ######..............####################
|
||||
17 ######..............####################
|
||||
```
|
||||
|
||||
Two chokepoints matter: `31, 9` is the only walkable cell that reaches `31, 8`
|
||||
and then the well ladder at `31, 7`, and `10, 16` is the only walkable cell
|
||||
adjacent to the Gym door at `10, 15` (`9, 15`, `11, 15` and `10, 14` are all
|
||||
wall). The two Rocket objects sit exactly on those cells.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `AzaleaTownFlypointCallback` - `MAPCALLBACK_NEWMAP`; `setflag ENGINE_FLYPOINT_AZALEA`,
|
||||
`endcallback`. Entering the map once is enough to register the fly point.
|
||||
- `AzaleaTownRocket1Script` / `AzaleaTownRocket2Script` -
|
||||
`jumptextfaceplayer AzaleaTownRocket1Text` / `...Rocket2Text`. Pure flavour;
|
||||
neither one gates anything by script. They block by standing on tiles.
|
||||
- `AzaleaTownGrampsScript` - `checkevent EVENT_CLEARED_SLOWPOKE_WELL` /
|
||||
`iftrue .ClearedWell`, then one of `AzaleaTownGrampsTextBefore` /
|
||||
`AzaleaTownGrampsTextAfter`. Cheap read-only probe of well state.
|
||||
- `WhiteApricornTree` - `fruittree FRUITTREE_AZALEA_TOWN`.
|
||||
`constants/script_constants.asm:226` gives `FRUITTREE_AZALEA_TOWN ; 14` (hex),
|
||||
and `data/items/fruit_trees.asm:23` is `db WHT_APRICORN ; AZALEA_TOWN`.
|
||||
Once-per-day, tracked by the fruit-tree daily bit, not by an `EVENT_*`.
|
||||
- `AzaleaTownHiddenFullHeal` - `hiddenitem FULL_HEAL, EVENT_AZALEA_TOWN_HIDDEN_FULL_HEAL`;
|
||||
reached by facing the wall cell `31, 6` from `31, 7`/`31, 5` neighbours.
|
||||
- `AzaleaTownRivalBattleScene1` / `...Scene2` / `AzaleaTownRivalBattleScript` -
|
||||
next section. For completeness they `setevent EVENT_RIVAL_AZALEA_TOWN`,
|
||||
branch on `EVENT_GOT_TOTODILE_FROM_ELM` / `EVENT_GOT_CHIKORITA_FROM_ELM`,
|
||||
`loadtrainer RIVAL1, RIVAL1_2_TOTODILE|RIVAL1_2_CHIKORITA|RIVAL1_2_CYNDAQUIL`,
|
||||
and end with `setscene SCENE_AZALEATOWN_NOOP`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_FLYPOINT_AZALEA` | `constants/engine_flags.asm:82` | set by `AzaleaTownFlypointCallback` | Fly destination unlocked on first entry |
|
||||
| `EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET` | `constants/event_flags.asm:1180` | set by `Kurt1` (first-visit arm) and read by `KurtsGranddaughter.Lonely` | **set = the well guard at `31, 9` is hidden**; this is the well gate |
|
||||
| `EVENT_SLOWPOKE_WELL_ROCKETS` | `constants/event_flags.asm:1182` | set by the `disappear` calls in `TrainerGruntM1.Script` | **set = the Rocket at `10, 16` is hidden**; this is the Gym gate |
|
||||
| `EVENT_AZALEA_TOWN_SLOWPOKES` | `constants/event_flags.asm:1179` | `clearevent` in `TrainerGruntM1.Script` | cleared = the four town Slowpoke appear |
|
||||
| `EVENT_CLEARED_SLOWPOKE_WELL` | `constants/event_flags.asm:52` | set in `TrainerGruntM1.Script`; read by `AzaleaTownGrampsScript`, `Kurt1`, `KurtsGranddaughter`, `KurtsHouseKurtCallback` | the section-4 milestone flag |
|
||||
| `EVENT_RIVAL_AZALEA_TOWN` | `constants/event_flags.asm:1121` | set by `AzaleaTownRivalBattleScript` | next section |
|
||||
| `EVENT_AZALEA_TOWN_HIDDEN_FULL_HEAL` | `constants/event_flags.asm:187` | `hiddenitem` | one-shot hidden item |
|
||||
| `SCENE_AZALEATOWN_NOOP` = 0, `SCENE_AZALEATOWN_RIVAL_BATTLE` = 1 | generated by `scene_script` in `macros/scripts/maps.asm:25` (`scene_const` + `EXPORT`) | `setscene` / `setmapscene` | scene id in `wAzaleaTownSceneID` |
|
||||
|
||||
Object visibility semantics, since everything above depends on it:
|
||||
`engine/overworld/scripting.asm:879` `Script_appear` calls
|
||||
`ApplyEventActionAppearDisappear` with `b = 0 ; clear`, and `:887`
|
||||
`Script_disappear` with `b = 1 ; set`. **An object's `EVENT_*` flag SET means the
|
||||
object is hidden.**
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `WHT_APRICORN` | talk to the tree object at `8, 2` | `WhiteApricornTree` -> `fruittree FRUITTREE_AZALEA_TOWN`, `data/items/fruit_trees.asm:23` | none (daily respawn) |
|
||||
| `FULL_HEAL` | hidden, face `31, 6` | `AzaleaTownHiddenFullHeal` bg_event | `EVENT_AZALEA_TOWN_HIDDEN_FULL_HEAL` |
|
||||
|
||||
Azalea Mart stock (`data/items/marts.asm:71`, `MartAzalea`, 9 items):
|
||||
`CHARCOAL`, `POKE_BALL`, `POTION`, `SUPER_POTION`, `ESCAPE_ROPE`, `REPEL`,
|
||||
`ANTIDOTE`, `PARLYZ_HEAL`, `FLOWER_MAIL`.
|
||||
|
||||
**Trainers**
|
||||
|
||||
None on this map in this section (the rival at const 11 is section 05).
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
`AZALEA_TOWN` has no entry in `data/wild/johto_grass.asm` or
|
||||
`data/wild/johto_water.asm`. Fishing group is `FISHGROUP_SHORE`
|
||||
(`data/maps/maps.asm:232`); headbutt set is
|
||||
`data/wild/treemon_maps.asm:31` -> `treemon_map AZALEA_TOWN, TREEMON_SET_FOREST`.
|
||||
|
||||
---
|
||||
|
||||
### MAP_KURTS_HOUSE
|
||||
|
||||
- Script: `maps/KurtsHouse.asm`
|
||||
- Blocks: `maps/KurtsHouse.blk`
|
||||
- Header: `data/maps/maps.asm:229` -> `TILESET_TRADITIONAL_HOUSE`, `INDOOR`,
|
||||
`LANDMARK_AZALEA_TOWN`, `MUSIC_AZALEA_TOWN`, phone `FALSE`, `PALETTE_DAY`,
|
||||
`FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:210` -> `map_const KURTS_HOUSE, 8, 4`
|
||||
- Connections: none (indoor)
|
||||
- Map scripts: no scene scripts; `callback MAPCALLBACK_OBJECTS, KurtsHouseKurtCallback`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 7 | `AZALEA_TOWN` | 4 |
|
||||
| 2 | 4 | 7 | `AZALEA_TOWN` | 4 |
|
||||
|
||||
**Coord events** (`def_coord_events`) - none.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 6 | 1 | `BGEVENT_READ` | `KurtsHouseRadio` (`jumpstd Radio2Script`) |
|
||||
| 8 | 0 | `BGEVENT_READ` | `KurtsHouseOakPhoto` |
|
||||
| 9 | 0 | `BGEVENT_READ` | `KurtsHouseOakPhoto` |
|
||||
| 5 | 1 | `BGEVENT_READ` | `KurtsHouseBookshelf` (`jumpstd DifficultBookshelfScript`) |
|
||||
| 2 | 1 | `BGEVENT_READ` | `KurtsHouseBookshelf` |
|
||||
| 3 | 1 | `BGEVENT_READ` | `KurtsHouseBookshelf` |
|
||||
| 4 | 1 | `BGEVENT_READ` | `KurtsHouseCelebiStatue` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 2 `KURTSHOUSE_KURT1` | `SPRITE_KURT` | 3 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, 0 | `Kurt1` | `EVENT_KURTS_HOUSE_KURT_1` |
|
||||
| 3 `KURTSHOUSE_TWIN` | `SPRITE_TWIN` | 5 | 3 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT`, 0 | `KurtsGranddaughter` | -1 |
|
||||
| 4 `KURTSHOUSE_SLOWPOKE` | `SPRITE_SLOWPOKE` | 6 | 3 | `STILL` | `OBJECTTYPE_SCRIPT`, 0 | `KurtsHouseSlowpoke` | `EVENT_KURTS_HOUSE_SLOWPOKE` |
|
||||
| 5 `KURTSHOUSE_KURT2` | `SPRITE_KURT` | 14 | 3 | `STANDING_UP` | `OBJECTTYPE_SCRIPT`, 0 | `Kurt1` | `EVENT_KURTS_HOUSE_KURT_2` |
|
||||
|
||||
Both Kurt objects run the same `Kurt1` script; the callback picks which one is
|
||||
visible.
|
||||
|
||||
**Derived walkability** (`maps/KurtsHouse.blk` + `data/tilesets/traditional_house_collision.asm`)
|
||||
|
||||
```
|
||||
0123456789012345
|
||||
0 ################
|
||||
1 #######...######
|
||||
2 #...........####
|
||||
3 .......##...#...
|
||||
4 .......##...#...
|
||||
5 ................
|
||||
6 ................
|
||||
7 ...WW...........
|
||||
```
|
||||
|
||||
The post-well script warp lands you on `3, 3`, one cell south of `KURTSHOUSE_KURT1`
|
||||
at `3, 2`, who faces down. Talk immediately, no walking required.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `KurtsHouseKurtCallback` (`MAPCALLBACK_OBJECTS`) -
|
||||
`checkevent EVENT_CLEARED_SLOWPOKE_WELL` / `iffalse .Done`;
|
||||
then `checkflag ENGINE_KURT_MAKING_BALLS` / `iftrue .MakingBalls`.
|
||||
Not making balls: `disappear KURTSHOUSE_KURT2`, `appear KURTSHOUSE_KURT1`.
|
||||
Making balls: `disappear KURTSHOUSE_KURT1`, `appear KURTSHOUSE_KURT2`
|
||||
(he moves to the workshop at `14, 3`).
|
||||
- `Kurt1` - three arms, tested in this order:
|
||||
1. `checkevent EVENT_KURT_GAVE_YOU_LURE_BALL` / `iftrue .GotLureBall`
|
||||
2. `checkevent EVENT_CLEARED_SLOWPOKE_WELL` / `iftrue .ClearedSlowpokeWell`
|
||||
3. fallthrough = **first visit**: `writetext KurtsHouseKurtMakingBallsMustWaitText`,
|
||||
`special FadeOutMusic`, `setevent EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET`,
|
||||
`readvar VAR_FACING` / `ifequal UP, .RunAround`, `turnobject PLAYER, DOWN`,
|
||||
`playsound SFX_FLY`,
|
||||
`applymovement KURTSHOUSE_KURT1, KurtsHouseKurtExitHouseMovement`
|
||||
(5x `big_step DOWN`; the `.RunAround` variant is
|
||||
`KurtsHouseKurtGoAroundPlayerThenExitHouseMovement`, `big_step RIGHT` then
|
||||
5x `big_step DOWN`), `disappear KURTSHOUSE_KURT1`, `special RestartMapMusic`.
|
||||
**This single `setevent` is what removes the well guard.**
|
||||
- `.ClearedSlowpokeWell`: `writetext KurtsHouseKurtHonoredToMakeBallsText`,
|
||||
`promptbutton`, `verbosegiveitem LURE_BALL`, `iffalse .NoRoomForBall`,
|
||||
`setevent EVENT_KURT_GAVE_YOU_LURE_BALL`, then falls into `.GotLureBall`.
|
||||
- `.GotLureBall`: the apricorn loop. Checks `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_2`,
|
||||
`EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1`, then the seven
|
||||
`EVENT_GAVE_KURT_<colour>_APRICORN` in order RED, BLU, YLW, GRN, WHT, BLK, PNK;
|
||||
if none, `checkitem` the seven apricorns, `special SelectApricornForKurt`,
|
||||
set the matching event, `setevent EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1`,
|
||||
`setflag ENGINE_KURT_MAKING_BALLS`. WHT -> `verbosegiveitem FAST_BALL`
|
||||
(`.GiveFastBall`) the next day. Only one apricorn is held at a time, which
|
||||
is exactly the reader comment quoted in the walkthrough.
|
||||
- `KurtsGranddaughter` - branch order `EVENT_FAST_SHIP_FIRST_TIME`,
|
||||
`EVENT_CLEARED_SLOWPOKE_WELL`, `EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET`. Read-only.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_KURTS_HOUSE_KURT_1` | `constants/event_flags.asm:1248` | `disappear`/`appear` in `Kurt1` + callback | set = house Kurt hidden (he is at the well) |
|
||||
| `EVENT_KURTS_HOUSE_KURT_2` | `constants/event_flags.asm:1249` | callback; also `setevent` in `engine/events/std_scripts.asm:531` | set = workshop Kurt hidden |
|
||||
| `EVENT_KURTS_HOUSE_SLOWPOKE` | `constants/event_flags.asm:1183` | `clearevent` in `TrainerGruntM1.Script` | cleared = the family Slowpoke is back |
|
||||
| `EVENT_KURT_GAVE_YOU_LURE_BALL` | `constants/event_flags.asm:62` | set in `Kurt1.ClearedSlowpokeWell` | free ball already taken |
|
||||
| `EVENT_GAVE_KURT_WHT_APRICORN` | `constants/event_flags.asm:263` | `Kurt1.Wht` / cleared in `.GiveFastBall` | apricorn currently in Kurt's hands |
|
||||
| `ENGINE_KURT_MAKING_BALLS` | `constants/engine_flags.asm:97` (`wDailyFlags1`) | `setflag` in `.GaveKurtApricorns`, read by the callback and every `.Give*Ball` arm | the day-long wait; cleared by the daily reset, not by a script |
|
||||
| `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1` / `_2` | `constants/event_flags.asm:5` / `:6` | `Kurt1` | per-visit conversation state, wiped on map reload |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `LURE_BALL` | talk to Kurt after clearing the well | `Kurt1.ClearedSlowpokeWell` -> `verbosegiveitem LURE_BALL` | `EVENT_KURT_GAVE_YOU_LURE_BALL` |
|
||||
| `FAST_BALL` | give Kurt the `WHT_APRICORN`, return a day later | `Kurt1.GiveFastBall` -> `verbosegiveitem FAST_BALL` | clears `EVENT_GAVE_KURT_WHT_APRICORN` |
|
||||
|
||||
**Trainers** - none. **Wild encounters** - none (indoor).
|
||||
|
||||
---
|
||||
|
||||
### MAP_AZALEA_POKECENTER_1F
|
||||
|
||||
- Script: `maps/AzaleaPokecenter1F.asm`
|
||||
- Blocks: not present in `maps/` (shared Pokecenter layout)
|
||||
- Header: `data/maps/maps.asm:226` -> `TILESET_POKECENTER`, `INDOOR`,
|
||||
`LANDMARK_AZALEA_TOWN`, `MUSIC_POKEMON_CENTER`, phone `FALSE`, `PALETTE_DAY`,
|
||||
`FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:207` -> `map_const AZALEA_POKECENTER_1F, 5, 4`
|
||||
- Map scripts: `def_scene_scripts` = `AzaleaPokecenter1FNoopScene` (marked `; unusable`)
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 7 | `AZALEA_TOWN` | 1 |
|
||||
| 2 | 4 | 7 | `AZALEA_TOWN` | 1 |
|
||||
| 3 | 0 | 7 | `POKECENTER_2F` | 1 |
|
||||
|
||||
**Coord events** / **BG events** - none.
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 2 `AZALEAPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaPokecenter1FNurseScript` (`jumpstd PokecenterNurseScript`) | -1 |
|
||||
| 3 `AZALEAPOKECENTER1F_GENTLEMAN` | `SPRITE_GENTLEMAN` | 9 | 6 | `WALK_UP_DOWN` 0,1 | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaPokecenter1FGentlemanScript` | -1 |
|
||||
| 4 `AZALEAPOKECENTER1F_FISHING_GURU` | `SPRITE_FISHING_GURU` | 6 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaPokecenter1FFishingGuruScript` | -1 |
|
||||
| 5 `AZALEAPOKECENTER1F_POKEFAN_F` | `SPRITE_POKEFAN_F` | 1 | 4 | `WANDER` 1,2 | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaPokecenter1FPokefanFScript` | -1 |
|
||||
|
||||
Healing is `jumpstd PokecenterNurseScript`; no flags are touched.
|
||||
|
||||
---
|
||||
|
||||
### MAP_SLOWPOKE_WELL_B1F
|
||||
|
||||
- Script: `maps/SlowpokeWellB1F.asm`
|
||||
- Blocks: `maps/SlowpokeWellB1F.blk`
|
||||
- Header: `data/maps/maps.asm:110` -> `TILESET_CAVE`, `CAVE`,
|
||||
`LANDMARK_SLOWPOKE_WELL`, `MUSIC_DARK_CAVE`, phone `TRUE`, `PALETTE_NITE`,
|
||||
`FISHGROUP_LAKE`
|
||||
- Dimensions: `constants/map_constants.asm:97` -> `map_const SLOWPOKE_WELL_B1F, 10, 9`
|
||||
(20x18 walk cells)
|
||||
- Connections: none
|
||||
- Map scripts: `def_scene_scripts` and `def_callbacks` are both empty
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 17 | 15 | `AZALEA_TOWN` | 6 |
|
||||
| 2 | 7 | 11 | `SLOWPOKE_WELL_B2F` | 1 |
|
||||
|
||||
**Coord events** / **BG events** - none. Nothing trips automatically on this map;
|
||||
every beat is a trainer sight-line or an A press.
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement / pal | type, sight | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 2 `SLOWPOKEWELLB1F_ROCKET1` | `SPRITE_ROCKET` | 14 | 8 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, 1 | `TrainerGruntM29` | `EVENT_SLOWPOKE_WELL_ROCKETS` |
|
||||
| 3 `SLOWPOKEWELLB1F_ROCKET2` | `SPRITE_ROCKET` | 5 | 2 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, 1 | `TrainerGruntM1` | `EVENT_SLOWPOKE_WELL_ROCKETS` |
|
||||
| 4 `SLOWPOKEWELLB1F_ROCKET3` | `SPRITE_ROCKET` | 5 | 6 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, 2 | `TrainerGruntM2` | `EVENT_SLOWPOKE_WELL_ROCKETS` |
|
||||
| 5 `SLOWPOKEWELLB1F_ROCKET_GIRL` | `SPRITE_ROCKET_GIRL` | 13 | 2 | `STANDING_DOWN` / `PAL_NPC_RED` | `OBJECTTYPE_TRAINER`, 2 | `TrainerGruntF1` | `EVENT_SLOWPOKE_WELL_ROCKETS` |
|
||||
| 6 `SLOWPOKEWELLB1F_SLOWPOKE1` | `SPRITE_SLOWPOKE` | 7 | 4 | `STILL` / `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT`, 0 | `SlowpokeWellB1FSlowpokeWithMailScript` | `EVENT_SLOWPOKE_WELL_SLOWPOKES` |
|
||||
| 7 `SLOWPOKEWELLB1F_SLOWPOKE2` | `SPRITE_SLOWPOKE` | 6 | 2 | `STILL` / `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT`, 0 | `SlowpokeWellB1FTaillessSlowpokeScript` | `EVENT_SLOWPOKE_WELL_SLOWPOKES` |
|
||||
| 8 `SLOWPOKEWELLB1F_KURT` | `SPRITE_KURT` | 16 | 14 | `STANDING_UP` | `OBJECTTYPE_SCRIPT`, 0 | `SlowpokeWellB1FKurtScript` | `EVENT_SLOWPOKE_WELL_KURT` |
|
||||
| 9 `SLOWPOKEWELLB1F_BOULDER` | `SPRITE_BOULDER` | 3 | 2 | `STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT`, 0 | `SlowpokeWellB1FBoulder` (`jumpstd StrengthBoulderScript`) | -1 |
|
||||
| 10 `SLOWPOKEWELLB1F_POKE_BALL` | `SPRITE_POKE_BALL` | 10 | 3 | `STILL` | `OBJECTTYPE_ITEMBALL`, 0 | `SlowpokeWellB1FSuperPotion` (`itemball SUPER_POTION`) | `EVENT_SLOWPOKE_WELL_B1F_SUPER_POTION` |
|
||||
|
||||
**Derived walkability** (`maps/SlowpokeWellB1F.blk` + `data/tilesets/cave_collision.asm`
|
||||
+ `data/collision/collision_permissions.asm`; `~` = water, `L` = ladder/warp)
|
||||
|
||||
```
|
||||
01234567890123456789
|
||||
1 .################...
|
||||
2 .#......#......##...
|
||||
3 .#..#...#......##...
|
||||
4 .#.#....#......##...
|
||||
5 .#.#....###.##.##...
|
||||
6 .#..#.......#...#...
|
||||
7 .##.##########..#...
|
||||
8 .#..#####....#..#...
|
||||
9 .#.######....#..#...
|
||||
10 .#.##........#..#...
|
||||
11 .#.##..L.#####..###.
|
||||
12 .#~~#....#~~~~....#.
|
||||
13 .#~~###.##~~~~....#.
|
||||
14 .#~~~~~~##~~~~....#.
|
||||
15 .#~~~~~~##~~~~...L#.
|
||||
```
|
||||
|
||||
Route through the well, matching the walkthrough beat for beat:
|
||||
`17, 15` (entry ladder) -> Kurt at `16, 14` -> north up the `14-15` column to
|
||||
`14, 8`/`15, 8` where `TrainerGruntM29` sees you -> continue north to the top
|
||||
room `9..14, 2..4`, where `TrainerGruntF1` at `13, 2` faces down with sight 2 ->
|
||||
Super Potion ball at `10, 3` -> back east/down through the gap at `11, 5` into
|
||||
the long row-6 corridor -> west to `TrainerGruntM2` at `5, 6` (faces right,
|
||||
sight 2) -> Slowpoke with mail at `7, 4`, tailless Slowpoke at `6, 2` ->
|
||||
`TrainerGruntM1` at `5, 2` (faces down, sight 1).
|
||||
|
||||
The B2F ladder at `7, 11` sits in a chamber walled off along row 7 and reachable
|
||||
only from `7, 13`, whose only non-wall neighbour outside the chamber is the water
|
||||
at `7, 14`. **B2F is Surf-only**; the water is also the only route down from the
|
||||
land at `2, 11`.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `SlowpokeWellB1FKurtScript` - `jumptextfaceplayer SlowpokeWellB1FKurtText`.
|
||||
Flavour only; Kurt does not gate anything here.
|
||||
- `TrainerGruntM29` - `trainer GRUNTM, GRUNTM_29, EVENT_BEAT_ROCKET_GRUNTM_29, GruntM29SeenText, GruntM29BeatenText, 0, .Script`;
|
||||
`.Script` is `endifjustbattled` + `GruntM29AfterBattleText`. Talking again is
|
||||
the "they're chopping off Slowpoke tails" line the walkthrough mentions.
|
||||
- `TrainerGruntF1` - `trainer GRUNTF, GRUNTF_1, EVENT_BEAT_ROCKET_GRUNTF_1, ...`,
|
||||
same `endifjustbattled` shape.
|
||||
- `TrainerGruntM2` - `trainer GRUNTM, GRUNTM_2, EVENT_BEAT_ROCKET_GRUNTM_2, ...`,
|
||||
same shape.
|
||||
- `TrainerGruntM1` - **the section's payload.**
|
||||
`trainer GRUNTM, GRUNTM_1, EVENT_BEAT_ROCKET_GRUNTM_1, GruntM1SeenText, GruntM1BeatenText, 0, .Script`.
|
||||
`.Script` has no `endifjustbattled`, so it runs immediately after the win:
|
||||
`writetext TrainerGruntM1WhenTalkText`, `special FadeOutToBlack`,
|
||||
`special ReloadSpritesNoPalettes`,
|
||||
`disappear` on consts 2/3/4/5 (all four share `EVENT_SLOWPOKE_WELL_ROCKETS`,
|
||||
so this is the flag that clears the Gym door in town), `pause 15`,
|
||||
`special FadeInFromBlack`, `disappear SLOWPOKEWELLB1F_KURT`,
|
||||
`moveobject SLOWPOKEWELLB1F_KURT, 11, 6`, `appear SLOWPOKEWELLB1F_KURT`,
|
||||
`applymovement SLOWPOKEWELLB1F_KURT, KurtSlowpokeWellVictoryMovementData`
|
||||
(4x `step LEFT`, `step UP`, 3x `step_sleep 8`, `step LEFT`, 2x `step UP`,
|
||||
3x `step_sleep 8`, `turn_head LEFT`), `turnobject PLAYER, RIGHT`,
|
||||
`writetext KurtLeaveSlowpokeWellText`, then the flag block:
|
||||
```
|
||||
setevent EVENT_CLEARED_SLOWPOKE_WELL
|
||||
variablesprite SPRITE_AZALEA_ROCKET, SPRITE_RIVAL
|
||||
setmapscene AZALEA_TOWN, SCENE_AZALEATOWN_RIVAL_BATTLE
|
||||
clearevent EVENT_ILEX_FOREST_APPRENTICE
|
||||
clearevent EVENT_ILEX_FOREST_FARFETCHD_1
|
||||
setevent EVENT_CHARCOAL_KILN_FARFETCH_D
|
||||
setevent EVENT_CHARCOAL_KILN_APPRENTICE
|
||||
setevent EVENT_SLOWPOKE_WELL_SLOWPOKES
|
||||
setevent EVENT_SLOWPOKE_WELL_KURT
|
||||
clearevent EVENT_AZALEA_TOWN_SLOWPOKES
|
||||
clearevent EVENT_KURTS_HOUSE_SLOWPOKE
|
||||
clearevent EVENT_KURTS_HOUSE_KURT_1
|
||||
```
|
||||
then `special FadeOutToWhite`, `special HealParty`, `pause 15`,
|
||||
`warp KURTS_HOUSE, 3, 3`, `end`. Note the free party heal, and note that the
|
||||
Ilex Forest Farfetch'd quest is armed here, not in Ilex Forest.
|
||||
- `SlowpokeWellB1FSlowpokeWithMailScript` - `faceplayer`, `cry SLOWPOKE`,
|
||||
`SlowpokeWellB1FSlowpokeWithMailText`, `yesorno` / `iftrue .ReadMail` ->
|
||||
`SlowpokeWellB1FSlowpokeMailText`. No flag, no item, purely optional.
|
||||
- `SlowpokeWellB1FSuperPotion` - `itemball SUPER_POTION`, guarded by
|
||||
`EVENT_SLOWPOKE_WELL_B1F_SUPER_POTION`.
|
||||
- `SlowpokeWellB1FBoulder` - `jumpstd StrengthBoulderScript`; needs Strength,
|
||||
which is not obtainable in this section.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BEAT_ROCKET_GRUNTM_29` | `constants/event_flags.asm:792` | `TrainerGruntM29` | grunt 1 beaten |
|
||||
| `EVENT_BEAT_ROCKET_GRUNTF_1` | `constants/event_flags.asm:796` | `TrainerGruntF1` | grunt 2 beaten |
|
||||
| `EVENT_BEAT_ROCKET_GRUNTM_2` | `constants/event_flags.asm:765` | `TrainerGruntM2` | grunt 3 beaten |
|
||||
| `EVENT_BEAT_ROCKET_GRUNTM_1` | `constants/event_flags.asm:764` | `TrainerGruntM1` | grunt 4 (leader) beaten |
|
||||
| `EVENT_SLOWPOKE_WELL_ROCKETS` | `constants/event_flags.asm:1182` | set via the four `disappear` calls | hides the well grunts **and** the Azalea Gym blocker |
|
||||
| `EVENT_SLOWPOKE_WELL_SLOWPOKES` | `constants/event_flags.asm:1181` | `setevent` in `TrainerGruntM1.Script` | hides the two well Slowpoke |
|
||||
| `EVENT_SLOWPOKE_WELL_KURT` | `constants/event_flags.asm:1250` | `setevent` in `TrainerGruntM1.Script` | hides well Kurt |
|
||||
| `EVENT_CLEARED_SLOWPOKE_WELL` | `constants/event_flags.asm:52` | set here | section milestone |
|
||||
| `EVENT_SLOWPOKE_WELL_B1F_SUPER_POTION` | `constants/event_flags.asm:1027` | itemball | Super Potion taken |
|
||||
| `EVENT_ILEX_FOREST_APPRENTICE` / `EVENT_ILEX_FOREST_FARFETCHD_1` | `constants/event_flags.asm:1173` / `:1163` | `clearevent` here | Farfetch'd quest NPCs become visible in Ilex Forest (section 05) |
|
||||
| `EVENT_CHARCOAL_KILN_FARFETCH_D` / `EVENT_CHARCOAL_KILN_APPRENTICE` | `constants/event_flags.asm:1175` / `:1176` | `setevent` here | they leave the Charcoal Kiln |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `SUPER_POTION` | ball at `10, 3` | `SlowpokeWellB1FSuperPotion` | `EVENT_SLOWPOKE_WELL_B1F_SUPER_POTION` |
|
||||
|
||||
**Trainers**
|
||||
|
||||
Money = `base reward` x level of the last enemy mon x 4. The x4 is
|
||||
`engine/battle/read_trainer_party.asm:300` `ComputeTrainerReward`
|
||||
(`base * wCurPartyLevel`) followed by the `ld c, 4` add loop and two
|
||||
unconditional `call .DoubleReward` at `engine/battle/core.asm:2340-2361`.
|
||||
`GRUNTM` / `GRUNTF` base reward is 10 (`data/trainers/attributes.asm:185`, `:395`).
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `GRUNTM_29` | `GRUNTM` | `constants/trainer_constants.asm:289` | `GruntMGroup` "GRUNT@", `TRAINERTYPE_NORMAL`: L9 `RATTATA`, L9 `RATTATA` | `TrainerGruntM29` | no (360 = 10x9x4) |
|
||||
| `GRUNTF_1` | `GRUNTF` | `constants/trainer_constants.asm:648` | `GruntFGroup` "GRUNT@", `TRAINERTYPE_NORMAL`: L9 `ZUBAT`, L11 `EKANS` | `TrainerGruntF1` | no (440 = 10x11x4) |
|
||||
| `GRUNTM_2` | `GRUNTM` | `constants/trainer_constants.asm:262` | `GruntMGroup` "GRUNT@", `TRAINERTYPE_NORMAL`: L7 `RATTATA`, L9 `ZUBAT`, L9 `ZUBAT` | `TrainerGruntM2` | no (360 = 10x9x4) |
|
||||
| `GRUNTM_1` | `GRUNTM` | `constants/trainer_constants.asm:261` | `GruntMGroup` "GRUNT@", `TRAINERTYPE_NORMAL`: L14 `KOFFING` | `TrainerGruntM1` | no (560 = 10x14x4) |
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
- Grass/cave, `data/wild/johto_grass.asm:582` `def_grass_wildmons SLOWPOKE_WELL_B1F`,
|
||||
`db 2 percent, 2 percent, 2 percent` - **identical morn/day/nite lists**:
|
||||
L5 `ZUBAT`, L6 `ZUBAT`, L7 `ZUBAT`, L6 `SLOWPOKE`, L8 `ZUBAT`, L8 `SLOWPOKE`,
|
||||
L8 `SLOWPOKE`.
|
||||
- Water, `data/wild/johto_water.asm:33` `def_water_wildmons SLOWPOKE_WELL_B1F`,
|
||||
`db 2 percent`: L15 `SLOWPOKE`, L20 `SLOWPOKE`, L10 `SLOWPOKE`.
|
||||
- Fishing: `FISHGROUP_LAKE` (`data/wild/fish.asm:14`) ->
|
||||
`.Lake_Old` L10 `MAGIKARP`/`MAGIKARP`/`GOLDEEN`, `.Lake_Good` L20
|
||||
`MAGIKARP`/`GOLDEEN`/`GOLDEEN` + `time_group 4`, `.Lake_Super` L40
|
||||
`GOLDEEN`/`time_group 5`/`MAGIKARP`/`SEAKING`.
|
||||
- Headbutt: `data/wild/treemon_maps.asm:47` -> `treemon_map SLOWPOKE_WELL_B1F, TREEMON_SET_ROCK`
|
||||
(rock smash set; no headbutt trees underground).
|
||||
|
||||
---
|
||||
|
||||
### MAP_SLOWPOKE_WELL_B2F (off-route, Surf-gated)
|
||||
|
||||
- Script: `maps/SlowpokeWellB2F.asm`
|
||||
- Blocks: `maps/SlowpokeWellB2F.blk`
|
||||
- Header: `data/maps/maps.asm:111` -> `TILESET_CAVE`, `CAVE`,
|
||||
`LANDMARK_SLOWPOKE_WELL`, `MUSIC_DARK_CAVE`, phone `TRUE`, `PALETTE_NITE`,
|
||||
`FISHGROUP_LAKE`
|
||||
- Dimensions: `constants/map_constants.asm:98` -> `map_const SLOWPOKE_WELL_B2F, 10, 9`
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 9 | 11 | `SLOWPOKE_WELL_B1F` | 2 |
|
||||
|
||||
**Coord events** / **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 2 `SLOWPOKEWELLB2F_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 5 | 4 | `WANDER` 1,2 | `OBJECTTYPE_SCRIPT`, 1 | `SlowpokeWellB2FGymGuideScript` | -1 |
|
||||
| 3 `SLOWPOKEWELLB2F_POKE_BALL` | `SPRITE_POKE_BALL` | 15 | 5 | `STILL` | `OBJECTTYPE_ITEMBALL`, 0 | `SlowpokeWellB2FTMRainDance` (`itemball TM_RAIN_DANCE`) | `EVENT_SLOWPOKE_WELL_B2F_TM_RAIN_DANCE` |
|
||||
|
||||
`SlowpokeWellB2FGymGuideScript`: `checkevent EVENT_GOT_KINGS_ROCK_IN_SLOWPOKE_WELL`
|
||||
/ `iftrue .GotKingsRock`, else `verbosegiveitem KINGS_ROCK`, `iffalse .NoRoom`,
|
||||
`setevent EVENT_GOT_KINGS_ROCK_IN_SLOWPOKE_WELL`
|
||||
(`constants/event_flags.asm:124`).
|
||||
|
||||
Wild: `data/wild/johto_grass.asm:610` L19-23 `ZUBAT`/`SLOWPOKE`/`GOLBAT`
|
||||
(same three time slots); `data/wild/johto_water.asm:40` is `4 percent` in Gold
|
||||
(`IF DEF(_GOLD)`) / `2 percent` in Silver, L15 `SLOWPOKE`, L20 `SLOWPOKE`,
|
||||
L20 `SLOWBRO`.
|
||||
|
||||
---
|
||||
|
||||
### MAP_AZALEA_GYM
|
||||
|
||||
- Script: `maps/AzaleaGym.asm`
|
||||
- Blocks: `maps/AzaleaGym.blk`
|
||||
- Header: `data/maps/maps.asm:230` -> `TILESET_ELITE_FOUR_ROOM`, `INDOOR`,
|
||||
`LANDMARK_AZALEA_TOWN`, `MUSIC_GYM`, phone `TRUE`, `PALETTE_DAY`,
|
||||
`FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:211` -> `map_const AZALEA_GYM, 5, 8`
|
||||
(10x16 walk cells)
|
||||
- Map scripts: `def_scene_scripts` and `def_callbacks` are both empty
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 15 | `AZALEA_TOWN` | 5 |
|
||||
| 2 | 5 | 15 | `AZALEA_TOWN` | 5 |
|
||||
|
||||
**Coord events** - none.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 3 | 13 | `BGEVENT_READ` | `AzaleaGymStatue` |
|
||||
| 6 | 13 | `BGEVENT_READ` | `AzaleaGymStatue` |
|
||||
|
||||
`AzaleaGymStatue`: `checkflag ENGINE_HIVEBADGE` / `iftrue .Beaten`;
|
||||
unbeaten -> `jumpstd GymStatue1Script`, beaten ->
|
||||
`gettrainername STRING_BUFFER_4, BUGSY, BUGSY1` + `jumpstd GymStatue2Script`.
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement / pal | type, sight | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 2 `AZALEAGYM_BUGSY` | `SPRITE_BUGSY` | 5 | 7 | `SPINRANDOM_SLOW` / `PAL_NPC_GREEN` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaGymBugsyScript` | -1 |
|
||||
| 3 `AZALEAGYM_BUG_CATCHER1` | `SPRITE_BUG_CATCHER` | 5 | 3 | `SPINRANDOM_FAST` / `PAL_NPC_BROWN` | `OBJECTTYPE_TRAINER`, 2 | `TrainerBugCatcherBenny` | -1 |
|
||||
| 4 `AZALEAGYM_BUG_CATCHER2` | `SPRITE_BUG_CATCHER` | 8 | 8 | `STANDING_DOWN` / `PAL_NPC_BROWN` | `OBJECTTYPE_TRAINER`, 3 | `TrainerBugCatcherAl` | -1 |
|
||||
| 5 `AZALEAGYM_BUG_CATCHER3` | `SPRITE_BUG_CATCHER` | 0 | 2 | `STANDING_DOWN` / `PAL_NPC_BROWN` | `OBJECTTYPE_TRAINER`, 3 | `TrainerBugCatcherJosh` | -1 |
|
||||
| 6 `AZALEAGYM_TWIN1` | `SPRITE_TWIN` | 4 | 10 | `STANDING_DOWN` / `PAL_NPC_RED` | `OBJECTTYPE_TRAINER`, 1 | `TrainerTwinsAmyandmay1` | -1 |
|
||||
| 7 `AZALEAGYM_TWIN2` | `SPRITE_TWIN` | 5 | 10 | `STANDING_DOWN` / `PAL_NPC_RED` | `OBJECTTYPE_TRAINER`, 1 | `TrainerTwinsAmyandmay2` | -1 |
|
||||
| 8 `AZALEAGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 13 | `STANDING_DOWN` / `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT`, 0 | `AzaleaGymGuideScript` | -1 |
|
||||
|
||||
None of the gym objects carry an event flag, so they are always drawn; being
|
||||
"beaten" is entirely the `trainer` macro's own `EVENT_BEAT_*` check.
|
||||
|
||||
**Derived walkability** (`maps/AzaleaGym.blk` + `data/tilesets/elite_four_room_collision.asm`)
|
||||
|
||||
```
|
||||
0123456789
|
||||
0 ##########
|
||||
1 ##########
|
||||
2 ..........
|
||||
3 ..........
|
||||
4 ..##..##..
|
||||
5 ..#....#..
|
||||
6 .#..##..#.
|
||||
7 .#......#.
|
||||
8 ..#....#..
|
||||
9 ..##..##..
|
||||
10 ..........
|
||||
11 ..........
|
||||
12 ##.#..#.##
|
||||
13 ##.#..#.##
|
||||
14 ###.....##
|
||||
15 ###.WW..##
|
||||
```
|
||||
|
||||
It is a spiral. Entry `4, 15` / `5, 15` -> `4, 14`..`7, 14` -> the two
|
||||
entry stalks at columns 4-5 and 7 -> the row 10/11 ring -> up either side
|
||||
column (0-1 or 8-9) -> row 2/3 -> in at `4, 4` / `5, 4` -> `4, 5`..`6, 5` ->
|
||||
`3, 6` -> `3, 7` -> Bugsy at `5, 7`.
|
||||
|
||||
Derived from the grid plus the sight fields: only the **twins** sit on the
|
||||
straight-line path (`4, 11` and `5, 11` are their sight cells), and even they
|
||||
are dodgeable by taking the column-7 stalk from `7, 14` up to `7, 11`. Josh's
|
||||
sight covers `0, 3`-`0, 5` so the left column is his; Al's covers `8, 9`-`8, 11`
|
||||
so the column-8 approach is his; column 9 is clear all the way from row 11 to
|
||||
row 3. Benny spins (`SPINRANDOM_FAST`, sight 2) at `5, 3`, so crossing row 2/3
|
||||
near column 5 is a coin flip rather than a guarantee.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `AzaleaGymBugsyScript` -
|
||||
`faceplayer`, `opentext`, `checkevent EVENT_BEAT_BUGSY` / `iftrue .FightDone`;
|
||||
otherwise `BugsyText_INeverLose`,
|
||||
`winlosstext BugsyText_ResearchIncomplete, 0` (no loss text - losing is a
|
||||
whiteout), `loadtrainer BUGSY, BUGSY1`, `startbattle`, `reloadmapafterbattle`,
|
||||
`setevent EVENT_BEAT_BUGSY`, `Text_ReceivedHiveBadge`,
|
||||
`playsound SFX_GET_BADGE`, `waitsfx`, **`setflag ENGINE_HIVEBADGE`**,
|
||||
`readvar VAR_BADGES`, `scall AzaleaGymActivateRockets`.
|
||||
`.FightDone`: `checkevent EVENT_GOT_TM49_FURY_CUTTER` / `iftrue .GotFuryCutter`,
|
||||
then - and this is the bot-relevant part -
|
||||
```
|
||||
setevent EVENT_BEAT_TWINS_AMY_AND_MAY
|
||||
setevent EVENT_BEAT_BUG_CATCHER_BENNY
|
||||
setevent EVENT_BEAT_BUG_CATCHER_AL
|
||||
setevent EVENT_BEAT_BUG_CATCHER_JOSH
|
||||
```
|
||||
so beating Bugsy retroactively marks the five gym trainers beaten. Then
|
||||
`BugsyText_HiveBadgeSpeech`, `promptbutton`,
|
||||
`verbosegiveitem TM_FURY_CUTTER`, `iffalse .NoRoomForFuryCutter`,
|
||||
`setevent EVENT_GOT_TM49_FURY_CUTTER`, `BugsyText_FuryCutterSpeech`.
|
||||
- `AzaleaGymActivateRockets` - `ifequal 7, .RadioTowerRockets`,
|
||||
`ifequal 6, .GoldenrodRockets`, `end`. `VAR_BADGES`
|
||||
(`constants/script_constants.asm:55`, id 7) is the badge count, which is 2
|
||||
right after HIVEBADGE, so **this scall is a no-op at Bugsy**; it is the shared
|
||||
gym-clear hook that only fires at the 6th and 7th badge.
|
||||
- `TrainerTwinsAmyandmay1` / `TrainerTwinsAmyandmay2` - both use
|
||||
`EVENT_BEAT_TWINS_AMY_AND_MAY`, so you fight **one** twin, whichever spots you
|
||||
first, and the other drops to her after-battle line.
|
||||
- `TrainerBugCatcherBenny` / `...Al` / `...Josh` - plain
|
||||
`trainer BUG_CATCHER, <id>, EVENT_BEAT_BUG_CATCHER_<id>, ...` with
|
||||
`endifjustbattled` after-scripts.
|
||||
- `AzaleaGymGuideScript` - `checkevent EVENT_BEAT_BUGSY`; the pre-battle text is
|
||||
the "bug Pokemon don't like fire, flying-type moves are super-effective" hint.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BEAT_BUGSY` | `constants/event_flags.asm:707` | `AzaleaGymBugsyScript` | leader beaten |
|
||||
| `ENGINE_HIVEBADGE` | `constants/engine_flags.asm:39` (`wJohtoBadges`, `data/events/engine_flags.asm:47`) | `setflag` here; read by `CutFunction.CheckAble`, `TryCutOW`, `AzaleaGymStatue`, `engine/battle/core.asm:6566` (obedience) | **the badge that enables field Cut and L30 obedience** |
|
||||
| `EVENT_GOT_TM49_FURY_CUTTER` | `constants/event_flags.asm:15` | `AzaleaGymBugsyScript` | TM49 already handed over |
|
||||
| `EVENT_BEAT_TWINS_AMY_AND_MAY` | `constants/event_flags.asm:608` | both twin objects; force-set by `.FightDone` | one flag, two objects |
|
||||
| `EVENT_BEAT_BUG_CATCHER_BENNY` | `constants/event_flags.asm:843` | `TrainerBugCatcherBenny`; force-set by `.FightDone` | |
|
||||
| `EVENT_BEAT_BUG_CATCHER_AL` | `constants/event_flags.asm:844` | `TrainerBugCatcherAl`; force-set by `.FightDone` | |
|
||||
| `EVENT_BEAT_BUG_CATCHER_JOSH` | `constants/event_flags.asm:845` | `TrainerBugCatcherJosh`; force-set by `.FightDone` | |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `TM_FURY_CUTTER` (TM49) | Bugsy hands it over after the badge speech | `AzaleaGymBugsyScript.FightDone` -> `verbosegiveitem TM_FURY_CUTTER` | `EVENT_GOT_TM49_FURY_CUTTER` |
|
||||
|
||||
**Trainers**
|
||||
|
||||
Base rewards: `BUG_CATCHER` 4 (`data/trainers/attributes.asm:215`),
|
||||
`TWINS` 5 (`:365`), `BUGSY` 25 (`:17`). Money = base x last-mon level x 4.
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `AMYANDMAY1` | `TWINS` | `constants/trainer_constants.asm:621` | `TwinsGroup` TWINS(1) "AMY & MAY@", `TRAINERTYPE_NORMAL`: L10 `SPINARAK`, L10 `LEDYBA` | `TrainerTwinsAmyandmay1` | no (200 = 5x10x4) |
|
||||
| `AMYANDMAY2` | `TWINS` | `constants/trainer_constants.asm:624` | `TwinsGroup` TWINS(4) "AMY & MAY@", `TRAINERTYPE_NORMAL`: L10 `LEDYBA`, L10 `SPINARAK` | `TrainerTwinsAmyandmay2` | no (200) |
|
||||
| `JOSH` | `BUG_CATCHER` | `constants/trainer_constants.asm:319` | `BugCatcherGroup` BUG_CATCHER(7) "JOSH@", `TRAINERTYPE_NORMAL`: L13 `PARAS` | `TrainerBugCatcherJosh` | no (208 = 4x13x4) |
|
||||
| `BENNY` | `BUG_CATCHER` | `constants/trainer_constants.asm:317` | `BugCatcherGroup` BUG_CATCHER(5) "BENNY@", `TRAINERTYPE_NORMAL`: L7 `WEEDLE`, L9 `KAKUNA`, L12 `BEEDRILL` | `TrainerBugCatcherBenny` | no (192 = 4x12x4) |
|
||||
| `AL` | `BUG_CATCHER` | `constants/trainer_constants.asm:318` | `BugCatcherGroup` BUG_CATCHER(6) "AL@", `TRAINERTYPE_NORMAL`: L12 `CATERPIE`, L12 `WEEDLE` | `TrainerBugCatcherAl` | no (192 = 4x12x4) |
|
||||
| `BUGSY1` | `BUGSY` (class 3, `constants/trainer_constants.asm:33`) | `:34` | `BugsyGroup` "BUGSY@", **`TRAINERTYPE_MOVES`**: L14 `METAPOD` (`TACKLE`, `STRING_SHOT`, `HARDEN`), L14 `KAKUNA` (`POISON_STING`, `STRING_SHOT`, `HARDEN`), L16 `SCYTHER` (`QUICK_ATTACK`, `LEER`, `FURY_CUTTER`) | `AzaleaGymBugsyScript` | no (1600 = 25x16x4) |
|
||||
|
||||
Bugsy's AI/items row (`data/trainers/attributes.asm:17`): `NO_ITEM, NO_ITEM`,
|
||||
`AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY`,
|
||||
`CONTEXT_USE | SWITCH_SOMETIMES`. Bug Catchers are `AI_BASIC | AI_SETUP | AI_STATUS`;
|
||||
Twins are `NO_AI` with `SWITCH_OFTEN`.
|
||||
|
||||
**Wild encounters** - none (indoor).
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Rocket standing on the Slowpoke Well approach | `maps/AzaleaTown.asm` object const 2 at `31, 9`, flag `EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET`. `31, 9` is the only walkable neighbour of `31, 8`, which is the only walkable neighbour of the ladder at `31, 7` (derived from `maps/AzaleaTown.blk` + `data/tilesets/johto_modern_collision.asm`) | talk to Kurt once | `Kurt1` first-visit arm runs `setevent EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET`, which hides him (`Script_disappear` semantics, `engine/overworld/scripting.asm:887`) |
|
||||
| Rocket standing on the Azalea Gym door | `maps/AzaleaTown.asm` object const 12 at `10, 16`, flag `EVENT_SLOWPOKE_WELL_ROCKETS`. `10, 16` is the only walkable neighbour of the gym warp at `10, 15` | clear Slowpoke Well | the four `disappear SLOWPOKEWELLB1F_ROCKET*` calls in `maps/SlowpokeWellB1F.asm` `TrainerGruntM1.Script` set that shared flag |
|
||||
| Slowpoke Well B2F (King's Rock, TM Rain Dance) | `maps/SlowpokeWellB1F.asm` warp 2 at `7, 11`; the chamber's only outside neighbour is water at `7, 14` (derived from `maps/SlowpokeWellB1F.blk` + `data/tilesets/cave_collision.asm` + `data/collision/collision_permissions.asm`) | Surf (`ENGINE_FOGBADGE` + HM03) | out of section - Fog Badge is Morty |
|
||||
| Boulder at Slowpoke Well `3, 2` | `SlowpokeWellB1FBoulder` -> `jumpstd StrengthBoulderScript` | Strength | out of section; nothing behind it is required |
|
||||
| Field Cut (leaving Azalea west for Ilex Forest) | `engine/events/overworld.asm` `CutFunction.CheckAble` (`ld de, ENGINE_HIVEBADGE` / `call CheckBadge` / `jr c, .nohivebadge`, sym `03:47e1`) and `TryCutOW` (`CheckPartyMove` with `CUT`, then `ld de, ENGINE_HIVEBADGE` / `CheckEngineFlag`, sym `03:5193`) | HIVEBADGE **and** a party member that knows CUT | HIVEBADGE from `AzaleaGymBugsyScript`; HM01 itself comes from the Charcoal Kiln / Ilex Forest chain in section 05 |
|
||||
| Pokemon obedience above L30 | `engine/battle/core.asm:6566` masks `(1 << ZEPHYRBADGE) | (1 << HIVEBADGE) | ...` | HIVEBADGE | same |
|
||||
| Bugsy himself | `AzaleaGymBugsyScript` has no `checkevent` guard before the battle | nothing - the five gym trainers are optional | `.FightDone` force-sets all four of their beat events afterwards |
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
Coordinates are `x, y` as written in the asm. "warp N" means the Nth
|
||||
`warp_event` row of the source map.
|
||||
|
||||
1. `MAP_AZALEA_TOWN`, walk in from the Route 33 east connection. Post: engine
|
||||
flag `ENGINE_FLYPOINT_AZALEA` set by `AzaleaTownFlypointCallback`
|
||||
(`MAPCALLBACK_NEWMAP`). No input needed.
|
||||
2. `MAP_AZALEA_TOWN`, walk to `9, 5` and step on it -> warp 4 ->
|
||||
`MAP_KURTS_HOUSE` `3, 7`. Pre: none.
|
||||
3. `MAP_KURTS_HOUSE`, walk to `3, 3`, face UP, press A on object const 2
|
||||
(`KURTSHOUSE_KURT1`, `3, 2`). Pre: `EVENT_CLEARED_SLOWPOKE_WELL` clear and
|
||||
`EVENT_KURT_GAVE_YOU_LURE_BALL` clear. Post:
|
||||
`EVENT_AZALEA_TOWN_SLOWPOKETAIL_ROCKET` set, `EVENT_KURTS_HOUSE_KURT_1` set
|
||||
(Kurt walks out). If the player is facing UP when the script starts, Kurt
|
||||
takes the `.RunAround` movement instead - same result, one extra step.
|
||||
4. `MAP_KURTS_HOUSE`, step on `3, 7` or `4, 7` -> `MAP_AZALEA_TOWN` warp 4 (`9, 5`).
|
||||
5. Optional heal: `MAP_AZALEA_TOWN` `15, 9` -> `MAP_AZALEA_POKECENTER_1F`;
|
||||
talk to object const 2 at `3, 1` (`jumpstd PokecenterNurseScript`); leave via
|
||||
`3, 7`.
|
||||
6. `MAP_AZALEA_TOWN`, walk east along row 10 to `31, 10`, north to `31, 9`
|
||||
(now empty), `31, 8`, then step on `31, 7` -> warp 6 -> `MAP_SLOWPOKE_WELL_B1F`
|
||||
`17, 15`. Optional: from `31, 7` face UP and press A for the hidden
|
||||
`FULL_HEAL` at `31, 6` (post: `EVENT_AZALEA_TOWN_HIDDEN_FULL_HEAL`).
|
||||
7. `MAP_SLOWPOKE_WELL_B1F`, optional A on const 8 (`16, 14`, Kurt).
|
||||
8. Walk north along column 15 to `15, 8`. Trigger: const 2 `TrainerGruntM29`
|
||||
(sight 1, faces right). Battle `GRUNTM` / `GRUNTM_29` (L9 Rattata x2).
|
||||
Post: `EVENT_BEAT_ROCKET_GRUNTM_29`. Optional A again for the after-battle text.
|
||||
9. Continue north column 14 to `14, 4`, then west into the top room. Trigger:
|
||||
const 5 `TrainerGruntF1` at `13, 2` (sight 2, faces down) from `13, 3` or
|
||||
`13, 4`. Battle `GRUNTF` / `GRUNTF_1` (L9 Zubat, L11 Ekans). Post:
|
||||
`EVENT_BEAT_ROCKET_GRUNTF_1`.
|
||||
10. Walk to `11, 3` -> `10, 3`, press A on const 10 -> `SUPER_POTION`. Post:
|
||||
`EVENT_SLOWPOKE_WELL_B1F_SUPER_POTION`.
|
||||
11. Go to `11, 4` -> `11, 5` -> `11, 6`, then west along row 6. Trigger: const 4
|
||||
`TrainerGruntM2` at `5, 6` (sight 2, faces right) from `7, 6` or `6, 6`.
|
||||
Battle `GRUNTM` / `GRUNTM_2` (L7 Rattata, L9 Zubat, L9 Zubat). Post:
|
||||
`EVENT_BEAT_ROCKET_GRUNTM_2`.
|
||||
12. Optional: `7, 5` face UP, A on const 6 at `7, 4` (Slowpoke with mail,
|
||||
`yesorno` -> read). No flag.
|
||||
13. Walk to `5, 3`. Trigger: const 3 `TrainerGruntM1` at `5, 2` (sight 1, faces
|
||||
down). Battle `GRUNTM` / `GRUNTM_1` (L14 Koffing). Post:
|
||||
`EVENT_BEAT_ROCKET_GRUNTM_1`, then `.Script` runs with no `endifjustbattled`,
|
||||
so the whole cutscene plays automatically and ends with
|
||||
`special HealParty` + `warp KURTS_HOUSE, 3, 3`. Post-flags to assert:
|
||||
`EVENT_CLEARED_SLOWPOKE_WELL`, `EVENT_SLOWPOKE_WELL_ROCKETS`,
|
||||
`EVENT_SLOWPOKE_WELL_SLOWPOKES`, `EVENT_SLOWPOKE_WELL_KURT`,
|
||||
`EVENT_CHARCOAL_KILN_FARFETCH_D`, `EVENT_CHARCOAL_KILN_APPRENTICE`;
|
||||
cleared: `EVENT_AZALEA_TOWN_SLOWPOKES`, `EVENT_KURTS_HOUSE_SLOWPOKE`,
|
||||
`EVENT_KURTS_HOUSE_KURT_1`, `EVENT_ILEX_FOREST_APPRENTICE`,
|
||||
`EVENT_ILEX_FOREST_FARFETCHD_1`; scene: `AZALEA_TOWN` -> `SCENE_AZALEATOWN_RIVAL_BATTLE`.
|
||||
14. `MAP_KURTS_HOUSE` at `3, 3`, face UP, A on const 2. Pre:
|
||||
`EVENT_CLEARED_SLOWPOKE_WELL` set, `EVENT_KURT_GAVE_YOU_LURE_BALL` clear.
|
||||
Post: `LURE_BALL` in bag, `EVENT_KURT_GAVE_YOU_LURE_BALL` set. The script
|
||||
then falls straight into the apricorn prompt - answer no unless you want to
|
||||
hand over the White Apricorn you have not picked yet.
|
||||
15. Leave via `3, 7`; in `MAP_AZALEA_TOWN` walk to `8, 3`, face UP, A on const 10
|
||||
at `8, 2` -> `WHT_APRICORN` (daily). Optional: return to Kurt and give it
|
||||
(`special SelectApricornForKurt`, post: `EVENT_GAVE_KURT_WHT_APRICORN` +
|
||||
`ENGINE_KURT_MAKING_BALLS`), then come back a real in-game day later for
|
||||
`FAST_BALL`.
|
||||
16. Optional heal at the Pokecenter (`15, 9`).
|
||||
17. `MAP_AZALEA_TOWN`, walk to `10, 17` -> `10, 16` (now empty) -> step on
|
||||
`10, 15` -> warp 5 -> `MAP_AZALEA_GYM` `4, 15`.
|
||||
18. `MAP_AZALEA_GYM`: optional A on const 8 at `7, 13` for the type hint.
|
||||
Walk `4, 14` -> `4, 13` -> `4, 12` -> `4, 11`. Trigger: const 6
|
||||
`TrainerTwinsAmyandmay1` at `4, 10` (sight 1). Battle `TWINS` / `AMYANDMAY1`
|
||||
(L10 Spinarak, L10 Ledyba). Post: `EVENT_BEAT_TWINS_AMY_AND_MAY`, which also
|
||||
retires const 7.
|
||||
19. West along row 11 to `0, 11`, north up column 0 to `0, 5`. Trigger: const 5
|
||||
`TrainerBugCatcherJosh` at `0, 2` (sight 3, faces down). Battle
|
||||
`BUG_CATCHER` / `JOSH` (L13 Paras). Post: `EVENT_BEAT_BUG_CATCHER_JOSH`.
|
||||
20. North to `0, 3`, east along row 3 toward `4, 3`. Trigger: const 3
|
||||
`TrainerBugCatcherBenny` at `5, 3` (sight 2, spins). Battle
|
||||
`BUG_CATCHER` / `BENNY` (L7 Weedle, L9 Kakuna, L12 Beedrill). Post:
|
||||
`EVENT_BEAT_BUG_CATCHER_BENNY`.
|
||||
21. East along row 2/3 to column 8-9, south to `8, 11`. Trigger: const 4
|
||||
`TrainerBugCatcherAl` at `8, 8` (sight 3, faces down). Battle
|
||||
`BUG_CATCHER` / `AL` (L12 Caterpie, L12 Weedle). Post:
|
||||
`EVENT_BEAT_BUG_CATCHER_AL`.
|
||||
22. Route into the middle: `4, 3` or `5, 3` row -> `4, 4` -> `4, 5` -> `3, 5` ->
|
||||
`3, 6` -> `3, 7` -> `4, 7`, face RIGHT, A on const 2 at `5, 7`. Pre:
|
||||
`EVENT_BEAT_BUGSY` clear. Battle `BUGSY` / `BUGSY1` (L14 Metapod, L14 Kakuna,
|
||||
L16 Scyther). Post: `EVENT_BEAT_BUGSY`, `ENGINE_HIVEBADGE`, then the same
|
||||
conversation continues and yields `TM_FURY_CUTTER` +
|
||||
`EVENT_GOT_TM49_FURY_CUTTER` and force-sets the four gym-trainer events.
|
||||
A bot that wants the badge fast can skip steps 19-21 entirely.
|
||||
23. Leave via `4, 15` / `5, 15`. Next section starts when you walk west and hit
|
||||
`coord_event 5, 10` / `5, 11`.
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Map data for all six maps (blocks, connections, warps, coord/bg/object events, `scriptKey`) | `src/import/RomExtractorGen2.lua` (`self:write("maps", out)`, line 987), consumed by `src/world/gen2/Map.lua` | implemented - `docs/gold-phase1.md` states all 368 maps extract |
|
||||
| Collision / walkability (`COLL_*` quads, block -> cell) | `src/world/gen2/Map.lua` `Map:cellCollision`, `src/world/gen2/Permissions.lua` | implemented (quad order `ly*2 + lx` matches `gfx/tilesets.asm` `tilecoll`) |
|
||||
| Warps between these maps | `src/world/gen2/World.lua` + `Map:warpAt`; driver `tests/drivers/gold_warp_scene.lua` | implemented |
|
||||
| Map callbacks (`MAPCALLBACK_NEWMAP` flypoint, `MAPCALLBACK_OBJECTS` Kurt swap) | `src/world/gen2/World.lua`, driver `tests/drivers/gold_map_callbacks.lua` | implemented |
|
||||
| Script VM opcodes used here (`checkevent`/`setevent`/`clearevent`, `checkflag`/`setflag`, `readvar`, `scall`, `jumpstd`, `special`, `warp`, `setscene`/`setmapscene`, `loadtrainer`/`startbattle`/`winlosstext`/`endifjustbattled`, `applymovement`, `moveobject`, `showemote`, `variablesprite`, `verbosegiveitem`, `yesorno`, `checkitem`, `promptbutton`, `fruittree`) | `src/script/gen2/Opcodes.lua`, `src/script/gen2/Vm.lua` | implemented - every opcode in this section's scripts has a row and a VM arm |
|
||||
| Specials used here (`FadeOutToBlack`, `FadeInFromBlack`, `FadeOutToWhite`, `ReloadSpritesNoPalettes`, `HealParty`, `SelectApricornForKurt`, `FadeOutMusic`/`RestartMapMusic`) | `src/script/gen2/Specials.lua` | implemented (spot-checked `FadeOutToBlack`, `ReloadSpritesNoPalettes`, `HealParty`, `SelectApricornForKurt`) |
|
||||
| Trainer sight lines / approach walk (the four grunts, the five gym trainers) | `src/world/gen2/Trainers.lua` (`Trainers.sees`, `Trainers.approach`), wired at `src/world/gen2/World.lua:5212-5264`; driver `tests/drivers/gold_trainer_smoke.lua` | implemented |
|
||||
| Trainer parties + class attributes | `src/import/RomExtractorGen2.lua` (trainers), `src/world/gen2/Trainers.lua`, `src/battle/gen2/Battle.lua` (`BUGSY` in the gym-leader set, line 85) | implemented |
|
||||
| Prize money (base x level x 4, Mom's split) | `src/battle/gen2/Prize.lua` | implemented, and it ports the `ld c, 4` loop and `.DoubleReward` faithfully |
|
||||
| Badge storage + `VAR_BADGES` count | `src/core/gen2/Save.lua` (`player.badges`, `player.kantoBadges`), `src/world/gen2/World.lua:1241-1244`, `World:engineFlags` (`:1304`) | implemented |
|
||||
| Cut gate on `ENGINE_HIVEBADGE` | `src/world/gen2/FieldMoves.lua` (`FieldMoves.BADGE.CUT = "HIVE"`, `cutFromMenu`, the overworld ask path) | implemented |
|
||||
| Fly point `ENGINE_FLYPOINT_AZALEA` | `src/world/gen2/FieldMoves.lua:347` (`LANDMARK_AZALEA_TOWN` / `SPAWN_AZALEA` / flag 67) | implemented |
|
||||
| Hidden item at `31, 6` (`BGEVENT_ITEM`) | `src/world/gen2/HiddenItems.lua` | implemented (module exists and the extractor carries `hiddenItem = { item, event }`); not exercised by any Azalea driver |
|
||||
| Kurt / apricorn conversation, `ENGINE_KURT_MAKING_BALLS` day wait, Lure Ball, Fast Ball | `src/core/gen2/Apricorns.lua` | implemented at the model level; the file is written against `maps/KurtsHouse.asm` directly and lists `FRUITTREE_AZALEA_TOWN` -> `WHT_APRICORN` at index `0x14` |
|
||||
| Item balls (`OBJECTTYPE_ITEMBALL`: Super Potion at B1F `10, 3`, TM Rain Dance at B2F `15, 5`) | extracted by `src/import/RomExtractorGen2.lua:2968`; **no runtime handler found in `src/world/gen2/World.lua`** | **missing** - the object data is there, nothing picks it up |
|
||||
| `fruittree` at runtime (White Apricorn tree object) | opcode row exists (`src/script/gen2/Opcodes.lua`, `[0x9a]`) and `Vm.lua` references it; the daily-tree store is described in `src/core/gen2/Apricorns.lua` (`save.fruitTrees`) | partial - modelled, not verified end to end in a driver |
|
||||
| Wild encounters for Slowpoke Well (grass 3-slot time split, water, fishing) | `src/import/RomExtractorGen2.lua` (Encounters), `src/battle/gen2/Encounter.lua` | implemented per `docs/gold-phase1.md`; not verified for this map specifically |
|
||||
| A driver that walks any part of this section | none - `tests/drivers/gold_*.lua` has nothing matching azalea / slowpoke / kurt / bugsy | **missing** |
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
- **Bug Catcher Al's prize.** The walkthrough says 200G. The asm gives
|
||||
4 (`data/trainers/attributes.asm:215`) x 12 (last mon, L12 Weedle) x 4 = **192G**.
|
||||
Every other figure in the section matches the formula exactly
|
||||
(360 / 440 / 360 / 560 / 200 / 208 / 192 / 1600), so this looks like a
|
||||
walkthrough typo, not a special case.
|
||||
- **Bugsy's gender.** The walkthrough says "Bugsy and her apprentices" and
|
||||
"she may have the Bug Pokemon". `AzaleaGymGuideText` in `maps/AzaleaGym.asm`
|
||||
says "BUGSY's young, but **his** knowledge of bug #MON is for real". The asm
|
||||
wins.
|
||||
- **"Liz the Picnicker calls about Moo Moo Milk."** `LizPhoneCallerScript`
|
||||
(`engine/phone/scripts/trainers.asm:234-254`) only does
|
||||
`PhoneScript_GreetPhone_Female`, a `checkflag ENGINE_FLYPOINT_GOLDENROD`
|
||||
rematch gate, `PhoneScript_Random2` and `Phone_GenericCall_Female` /
|
||||
`Phone_CheckIfUnseenRare_Female`. There is no Moo Moo Milk text in her script,
|
||||
and no `MOOMOO` string turned up anywhere under `engine/phone/`. The generic
|
||||
call pool may contain such a line, but it is not attributable to Liz from the
|
||||
map/phone scripts - unresolved.
|
||||
- **"TM49" listed under *Items found in Azalea Town*.** TM49 is
|
||||
`verbosegiveitem TM_FURY_CUTTER` inside `AzaleaGymBugsyScript`, i.e. found in
|
||||
`MAP_AZALEA_GYM`, not on the town map. Cosmetic, but a bot indexing by map
|
||||
should not look for it outdoors.
|
||||
- **Battle order inside Slowpoke Well.** The walkthrough's "move on up the
|
||||
stairs", "head down the stairs" prose does not correspond to any `LADDER` or
|
||||
`STAIRCASE` collision inside B1F - the only two ladder cells are the two warps
|
||||
(`17, 15` and `7, 11`). The trainer order it describes (M29 -> F1 -> M2 -> M1)
|
||||
does match the geometry, so the "stairs" are just prose for the corridors.
|
||||
- **`SLOWPOKE_WELL_B2F` reachability.** I derived "Surf only" from
|
||||
`maps/SlowpokeWellB1F.blk` decoded against `data/tilesets/cave_collision.asm`
|
||||
and `data/collision/collision_permissions.asm`; no script or engine check
|
||||
states it. If a future reader wants a stronger citation, it would have to come
|
||||
from the block data, not from an `EVENT_*`.
|
||||
- **Gym-trainer skippability.** The claim that Josh, Al and (via the column-7
|
||||
stalk) the twins can be walked past is derived from `maps/AzaleaGym.blk` plus
|
||||
each object's sight field, not asserted anywhere in the asm. The retroactive
|
||||
`setevent` block in `AzaleaGymBugsyScript.FightDone` is direct asm evidence
|
||||
that skipping them is an anticipated state, but the exact dodge path should be
|
||||
confirmed in-game before a driver depends on it.
|
||||
- **`variablesprite SPRITE_AZALEA_ROCKET, SPRITE_RIVAL`.** This is why
|
||||
`AZALEATOWN_RIVAL` is declared with `SPRITE_AZALEA_ROCKET` in
|
||||
`maps/AzaleaTown.asm`. It also means consts 2 and 12 on that map would draw as
|
||||
the rival if they were still visible after the well is cleared - they are not,
|
||||
because both are hidden by then. Worth a look if a port ever renders them.
|
||||
@@ -0,0 +1,987 @@
|
||||
# Section 06 - Bug Catching Contest and Sudowoodo
|
||||
|
||||
Source: `../section-06-bug-catching-contest-and-sudowoodo.txt`
|
||||
Maps covered: `MAP_GOLDENROD_FLOWER_SHOP`, `MAP_ROUTE_35_GOLDENROD_GATE`, `MAP_ROUTE_35`,
|
||||
`MAP_ROUTE_35_NATIONAL_PARK_GATE`, `MAP_NATIONAL_PARK`, `MAP_NATIONAL_PARK_BUG_CONTEST`,
|
||||
`MAP_ROUTE_36_NATIONAL_PARK_GATE`, `MAP_ROUTE_36`, `MAP_ROUTE_37` (listed by the
|
||||
walkthrough header, never entered by its route)
|
||||
|
||||
Badges / key milestones in this section:
|
||||
|
||||
- SQUIRTBOTTLE (key item) - gated behind `ENGINE_PLAINBADGE`, i.e. Whitney must already
|
||||
be beaten before this section can be started at all.
|
||||
- Bug-Catching Contest run (Tue/Thu/Sat only) - SUN STONE / EVERSTONE / GOLD BERRY / BERRY.
|
||||
- `EVENT_FOUGHT_SUDOWOODO` - the single event that unblocks Route 36 west<->east, which
|
||||
is the only land route between Goldenrod and Violet through Route 36.
|
||||
- TM08 ROCK SMASH (`EVENT_GOT_TM08_ROCK_SMASH`), TM04 ROLLOUT, TM28 DIG, QUICK CLAW.
|
||||
- No badge is earned in this section.
|
||||
|
||||
Everything in this file was transcribed from files that were opened; nothing is
|
||||
reconstructed from memory. Symbol addresses are `bank:addr` from
|
||||
`pokegold-symbols/pokegold.sym`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Route order
|
||||
|
||||
The walkthrough's own prose is out of order in one place: it opens by collecting the
|
||||
SQUIRTBOTTLE in Goldenrod, then much later describes Floria running off to fetch it.
|
||||
In pokegold the SQUIRTBOTTLE has no Route 36 precondition at all (see
|
||||
"Unresolved / verify by hand"), so the opening order is the one a bot should follow.
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `MAP_GOLDENROD_FLOWER_SHOP` | `maps/GoldenrodFlowerShop.asm` | `GOLDENROD_CITY` warp 6 @ (33, 5) | warp 1/2 @ (2,7)/(3,7) -> `GOLDENROD_CITY` 6 | Talk to the teacher for the SQUIRTBOTTLE |
|
||||
| 2 | `MAP_ROUTE_35_GOLDENROD_GATE` | `maps/Route35GoldenrodGate.asm` | `GOLDENROD_CITY` warp 13 @ (19, 1) -> gate warp 3 | warp 1/2 @ (4,0)/(5,0) -> `ROUTE_35` 1/2 | Randy hands over Kenya the SPEAROW with FLOWER MAIL |
|
||||
| 3 | `MAP_ROUTE_35` | `maps/Route35.asm` | gate warp @ (9,33)/(10,33) | warp 3 @ (3, 5) -> `ROUTE_35_NATIONAL_PARK_GATE` 3 | Six trainers + Officer Dirk, walk north |
|
||||
| 4 | `MAP_ROUTE_35_NATIONAL_PARK_GATE` | `maps/Route35NationalParkGate.asm` | warp 3/4 @ (3,7)/(4,7) | warp 1/2 @ (3,0)/(4,0) -> `NATIONAL_PARK` 3/4 | Contest officer; optional entry to the Contest |
|
||||
| 5 | `MAP_NATIONAL_PARK_BUG_CONTEST` | `maps/NationalParkBugContest.asm` | script `warp NATIONAL_PARK_BUG_CONTEST, 10, 47` from the south gate (or `33, 18` from the north gate) | contest timer / balls exhausted, or either gate | Optional Tue/Thu/Sat Contest |
|
||||
| 6 | `MAP_NATIONAL_PARK` | `maps/NationalPark.asm` | warp 3/4 @ (10,47)/(11,47) | warp 1/2 @ (33,18)/(33,19) -> `ROUTE_36_NATIONAL_PARK_GATE` 1/2 | QUICK CLAW, Beverly, Jack, Krise, William, TM28 DIG |
|
||||
| 7 | `MAP_ROUTE_36_NATIONAL_PARK_GATE` | `maps/Route36NationalParkGate.asm` | warp 1/2 @ (0,4)/(0,5) | warp 3/4 @ (9,4)/(9,5) -> `ROUTE_36` 1/2 | Contest results / prize holding |
|
||||
| 8 | `MAP_ROUTE_36` | `maps/Route36.asm` | warp 1/2 @ (18,8)/(18,9) | south connection @ y=17 -> `ROUTE_35`; later east connection -> `VIOLET_CITY` | Ice Berry, Psychic Mark, Schoolboy Alan, Sudowoodo, TM08 |
|
||||
| 9 | `MAP_ROUTE_35` (again) | `maps/Route35.asm` | north connection from `ROUTE_36` (offset 0) | back north to `ROUTE_36` | Cut the tree, Bug Catcher Arnie, Bird Keeper Bryan, TM04 ROLLOUT |
|
||||
| 10 | `MAP_ROUTE_36` (again) | `maps/Route36.asm` | north connection from `ROUTE_35` | **east connection -> `VIOLET_CITY`** | Sudowoodo cleared, then east into Violet City |
|
||||
|
||||
Spills into the next section: step 10's east connection
|
||||
(`connection east, VioletCity, VIOLET_CITY, 0` in `data/maps/attributes.asm`) drops the
|
||||
player into `MAP_VIOLET_CITY`, which belongs to a neighbouring section. Route 37
|
||||
(`MAP_ROUTE_37`, north connection off Route 36 with offset 10) is named in this section's
|
||||
header but the walkthrough never routes into it; its data is recorded below for
|
||||
completeness because the item list ("Blk/Blu/Red Apricorn", "Ice Berry") mixes 36 and 37.
|
||||
|
||||
---
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_GOLDENROD_FLOWER_SHOP
|
||||
|
||||
- Script: `maps/GoldenrodFlowerShop.asm`
|
||||
- Blocks: `maps/GoldenrodFlowerShop.blk`
|
||||
- Header (`data/maps/maps.asm:275`): `TILESET_HOUSE, INDOOR, LANDMARK_GOLDENROD_CITY, MUSIC_GOLDENROD_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:253` -> `map_const GOLDENROD_FLOWER_SHOP, 4, 4`
|
||||
- Connections: none (indoor)
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 2 | 7 | `GOLDENROD_CITY` | 6 |
|
||||
| 2 | 3 | 7 | `GOLDENROD_CITY` | 6 |
|
||||
|
||||
**Coord events** (`def_coord_events`) - none.
|
||||
|
||||
**BG events** (`def_bg_events`) - none. (`FlowerShopShelf1`, `FlowerShopShelf2`,
|
||||
`FlowerShopRadio` exist in the file but are marked `; unreferenced`.)
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `GOLDENRODFLOWERSHOP_TEACHER` | `SPRITE_TEACHER` | 2 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `FlowerShopTeacherScript` | -1 |
|
||||
| `GOLDENRODFLOWERSHOP_FLORIA` | `SPRITE_LASS` | 5 | 6 | `SPRITEMOVEDATA_WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `FlowerShopFloriaScript` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `FlowerShopTeacherScript` (`57:52d1`) - `checkevent EVENT_GOT_SQUIRTBOTTLE` /
|
||||
`iftrue .Lalala`; then `checkflag ENGINE_PLAINBADGE` / `iffalse .Lalala`. Only with the
|
||||
badge and without the event does it `faceplayer` / `opentext` /
|
||||
`writetext GoldenrodFlowerShopTeacherBetterThanWhitneyText` / `promptbutton` /
|
||||
`verbosegiveitem SQUIRTBOTTLE` / `setevent EVENT_GOT_SQUIRTBOTTLE`. **Note the bug in
|
||||
the cart**: there is no `iffalse` after `verbosegiveitem`, so a full PACK still sets
|
||||
`EVENT_GOT_SQUIRTBOTTLE` and the item is lost. A bot must have a free KEY ITEM slot.
|
||||
- `FlowerShopFloriaScript` (`57:52f4`) - pure flavour, branches on `ENGINE_PLAINBADGE`.
|
||||
Gives nothing.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_GOT_SQUIRTBOTTLE` | `constants/event_flags.asm:101` | read+written by `FlowerShopTeacherScript` | one-shot; set even on a full bag |
|
||||
| `ENGINE_PLAINBADGE` | `constants/engine_flags.asm:40` | read by `FlowerShopTeacherScript`, `FlowerShopFloriaScript` | Whitney's badge is the hard precondition for the whole section |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `SQUIRTBOTTLE` | talk to teacher at (2,4) | `FlowerShopTeacherScript` `verbosegiveitem` | `EVENT_GOT_SQUIRTBOTTLE` |
|
||||
|
||||
**Trainers** - none.
|
||||
|
||||
**Wild encounters** - none (indoor).
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_35_GOLDENROD_GATE
|
||||
|
||||
- Script: `maps/Route35GoldenrodGate.asm`
|
||||
- Blocks: no `.blk` in `maps/` for this map (gate maps of this group share generated
|
||||
attributes; `data/maps/attributes.asm:542` carries `map_attributes Route35GoldenrodGate, ROUTE_35_GOLDENROD_GATE, $00`)
|
||||
- Header (`data/maps/maps.asm:260`): `TILESET_GATE, GATE, LANDMARK_ROUTE_35, MUSIC_ROUTE_36, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:239` -> `map_const ROUTE_35_GOLDENROD_GATE, 5, 4`
|
||||
- Connections: none. `GATE` environment is why the walkthrough's "ride your bike through"
|
||||
works.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 0 | `ROUTE_35` | 1 |
|
||||
| 2 | 5 | 0 | `ROUTE_35` | 2 |
|
||||
| 3 | 4 | 7 | `GOLDENROD_CITY` | 13 |
|
||||
| 4 | 5 | 7 | `GOLDENROD_CITY` | 13 |
|
||||
|
||||
**Coord events** - none. **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE35GOLDENRODGATE_RANDY` | `SPRITE_OFFICER` | 0 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `RandyScript` | -1 |
|
||||
| `ROUTE35GOLDENRODGATE_POKEFAN_F` | `SPRITE_POKEFAN_F` | 6 | 4 | `SPRITEMOVEDATA_WALK_UP_DOWN` (0,1) | `OBJECTTYPE_SCRIPT` | `Route35GoldenrodGatePokefanFScript` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `RandyScript` (`56:59ce`) - four-way branch, checked in this order:
|
||||
`EVENT_GOT_HP_UP_FROM_RANDY` -> `.gothpup`; `EVENT_GAVE_KENYA` -> `.questcomplete`
|
||||
(`verbosegiveitem HP_UP`, `setevent EVENT_GOT_HP_UP_FROM_RANDY`);
|
||||
`EVENT_GOT_KENYA` -> `.alreadyhavekenya`; otherwise the offer. On YES it prints thanks,
|
||||
then `readvar VAR_PARTYCOUNT` / `ifequal PARTY_LENGTH, .partyfull`. Only with a free
|
||||
slot does it run `givepoke SPEAROW, 10, NO_ITEM, GiftSpearowName, GiftSpearowOTName`,
|
||||
`givepokemail GiftSpearowMail`, `setevent EVENT_GOT_KENYA`.
|
||||
- `GiftSpearowMail` = `FLOWER_MAIL`, text `"DARK CAVE leads / to another road"`.
|
||||
- `GiftSpearowName` = `KENYA`, `GiftSpearowOTName` = `RANDY`.
|
||||
- The delivery target (`EVENT_GAVE_KENYA`) is on Route 31, i.e. a later section.
|
||||
- `Route35GoldenrodGatePokefanFScript` - flavour, branches on `EVENT_FOUGHT_SUDOWOODO`;
|
||||
its pre-Sudowoodo text is the in-game hint that the SQUIRTBOTTLE is what wakes the tree.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_GOT_KENYA` | `constants/event_flags.asm:89` | `RandyScript` | Spearow accepted |
|
||||
| `EVENT_GAVE_KENYA` | `constants/event_flags.asm:90` | read here, set on Route 31 | enables the HP UP reward |
|
||||
| `EVENT_GOT_HP_UP_FROM_RANDY` | `constants/event_flags.asm:91` | `RandyScript` | reward taken |
|
||||
| `EVENT_FOUGHT_SUDOWOODO` | `constants/event_flags.asm:51` | read here | flavour only on this map |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| SPEAROW "KENYA" (lv 10) + `FLOWER_MAIL` | say YES to Randy with a party slot free | `RandyScript` `givepoke` / `givepokemail` | `EVENT_GOT_KENYA` |
|
||||
| `HP_UP` | return after delivering to Route 31 | `RandyScript` `.questcomplete` | `EVENT_GOT_HP_UP_FROM_RANDY` |
|
||||
|
||||
**Trainers** - none. **Wild encounters** - none.
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_35
|
||||
|
||||
- Script: `maps/Route35.asm`
|
||||
- Blocks: `maps/Route35.blk` (180 bytes = 10 x 18 blocks)
|
||||
- Header (`data/maps/maps.asm:248`): `TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_35, MUSIC_ROUTE_36, FALSE, PALETTE_AUTO, FISHGROUP_POND`
|
||||
- Dimensions: `constants/map_constants.asm:227` -> `map_const ROUTE_35, 10, 18` (so cell
|
||||
coordinates run 0..19 in x and 0..35 in y)
|
||||
- Connections (`data/maps/attributes.asm:202`): north `ROUTE_36` offset 0, south
|
||||
`GOLDENROD_CITY` offset -5
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 9 | 33 | `ROUTE_35_GOLDENROD_GATE` | 1 |
|
||||
| 2 | 10 | 33 | `ROUTE_35_GOLDENROD_GATE` | 2 |
|
||||
| 3 | 3 | 5 | `ROUTE_35_NATIONAL_PARK_GATE` | 3 |
|
||||
|
||||
**Coord events** - none.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 1 | 7 | `BGEVENT_READ` | `Route35Sign` |
|
||||
| 11 | 31 | `BGEVENT_READ` | `Route35Sign` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE35_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 3 | 19 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` sight 4 | `TrainerCamperIvan` | -1 |
|
||||
| `ROUTE35_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 8 | 20 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` sight 3 | `TrainerCamperElliot` | -1 |
|
||||
| `ROUTE35_LASS1` | `SPRITE_LASS` | 7 | 20 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` sight 3 | `TrainerPicnickerBrooke` | -1 |
|
||||
| `ROUTE35_LASS2` | `SPRITE_LASS` | 11 | 24 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` sight 3 | `TrainerPicnickerKim` | -1 |
|
||||
| `ROUTE35_YOUNGSTER3` | `SPRITE_YOUNGSTER` | 14 | 28 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER` sight 0 | `TrainerBirdKeeperBryan` | -1 |
|
||||
| `ROUTE35_FISHER` | `SPRITE_FISHER` | 2 | 10 | `SPINCOUNTERCLOCKWISE` | `OBJECTTYPE_TRAINER` sight 2 | `TrainerFirebreatherWalt` | -1 |
|
||||
| `ROUTE35_BUG_CATCHER` | `SPRITE_BUG_CATCHER` | 16 | 7 | `STANDING_DOWN` (radius 2,0) | `OBJECTTYPE_TRAINER` sight 3 | `TrainerBugCatcherArnie` | -1 |
|
||||
| `ROUTE35_SUPER_NERD` | `SPRITE_SUPER_NERD` | 5 | 10 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` sight 2 | `TrainerJugglerIrwin` | -1 |
|
||||
| `ROUTE35_OFFICER` | `SPRITE_OFFICER` | 5 | 6 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `TrainerOfficerDirk` | -1 |
|
||||
| `ROUTE35_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 2 | 25 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route35FruitTree` (`4b:5c07`) | -1 |
|
||||
| `ROUTE35_POKE_BALL` | `SPRITE_POKE_BALL` | 13 | 16 | `STILL` | `OBJECTTYPE_ITEMBALL` | `Route35TMRollout` (`4b:5c05`) | `EVENT_ROUTE_35_TM_ROLLOUT` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `TrainerOfficerDirk` - **not** an `OBJECTTYPE_TRAINER` object. It is a plain script that
|
||||
runs `checktime NITE` / `iffalse .NotNight` first, so Dirk only battles at night. Then
|
||||
`checkevent EVENT_BEAT_OFFICER_DIRK`, `playmusic MUSIC_OFFICER_ENCOUNTER`,
|
||||
`winlosstext OfficerDirkBeatenText, 0`, `loadtrainer OFFICER, DIRK`, `startbattle`,
|
||||
`reloadmapafterbattle`, `setevent EVENT_BEAT_OFFICER_DIRK`. A daytime bot will get
|
||||
`OfficerDirkPrettyToughText` and no battle.
|
||||
- `TrainerJugglerIrwin` / `TrainerBugCatcherArnie` - standard phone trainers. After the
|
||||
win, `checkcellnum PHONE_JUGGLER_IRWIN` / `PHONE_BUG_CATCHER_ARNIE`, then
|
||||
`setevent EVENT_IRWIN_ASKED_FOR_PHONE_NUMBER` / `EVENT_ARNIE_ASKED_FOR_PHONE_NUMBER`
|
||||
and `askforphonenumber`. The walkthrough's "head back to Arnie and talk to her for her
|
||||
phone number" is this second talk.
|
||||
Rematch tiers: Irwin uses `EVENT_CLEARED_RADIO_TOWER` -> `IRWIN3`,
|
||||
`ENGINE_FLYPOINT_CIANWOOD` -> `IRWIN2`, else `IRWIN1`. Arnie uses
|
||||
`ENGINE_FLYPOINT_BLACKTHORN` -> `ARNIE3`, `ENGINE_FLYPOINT_LAKE_OF_RAGE` -> `ARNIE2`,
|
||||
else `ARNIE1`.
|
||||
- `Route35TMRollout` -> `itemball TM_ROLLOUT`. `Route35FruitTree` -> `fruittree FRUITTREE_ROUTE_35`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BEAT_PICNICKER_KIM` / `_CAMPER_ELLIOT` / `_PICNICKER_BROOKE` / `_CAMPER_IVAN` / `_JUGGLER_IRWIN` / `_FIREBREATHER_WALT` / `_BUG_CATCHER_ARNIE` / `_BIRD_KEEPER_BRYAN` | `constants/event_flags.asm` | `trainer` macro rows | per-trainer defeated bits |
|
||||
| `EVENT_BEAT_OFFICER_DIRK` | `constants/event_flags.asm` | `TrainerOfficerDirk` | set by hand, not by the `trainer` macro |
|
||||
| `EVENT_ROUTE_35_TM_ROLLOUT` | `constants/event_flags.asm:1107` | item ball object | TM04 taken |
|
||||
| `EVENT_IRWIN_ASKED_FOR_PHONE_NUMBER`, `EVENT_IRWIN_READY_FOR_REMATCH` | `constants/event_flags.asm` | Irwin script | phone state machine |
|
||||
| `EVENT_ARNIE_ASKED_FOR_PHONE_NUMBER`, `EVENT_ARNIE_READY_FOR_REMATCH` | `constants/event_flags.asm` | Arnie script | phone state machine |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `TM_ROLLOUT` (TM04, `constants/item_constants.asm:223`) | item ball at cell (13, 16) | `Route35TMRollout` | `EVENT_ROUTE_35_TM_ROLLOUT` |
|
||||
| `MYSTERYBERRY` | fruit tree object at (2, 25) | `FRUITTREE_ROUTE_35` -> `data/items/fruit_trees.asm:14` | daily reset (`Apricorns`/`TryResetFruitTrees` equivalent) |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `KIM` | `PICNICKER` (0x35) | 4 | `parties.asm:2601` `KIM` - lv15 VULPIX | `TrainerPicnickerKim` | no |
|
||||
| `ELLIOT` | `CAMPER` (0x36) | 4 | `parties.asm:2722` `ELLIOT` - lv13 SANDSHREW, lv15 MARILL | `TrainerCamperElliot` | no |
|
||||
| `BROOKE` | `PICNICKER` (0x35) | 3 | `parties.asm:2596` `BROOKE` `TRAINERTYPE_MOVES` - lv16 PIKACHU (THUNDERSHOCK, GROWL, QUICK_ATTACK, DOUBLE_TEAM) | `TrainerPicnickerBrooke` | no |
|
||||
| `IVAN` | `CAMPER` (0x36) | 3 | `parties.asm:2715` `IVAN` - lv10 DIGLETT, lv10 ZUBAT, lv14 DIGLETT | `TrainerCamperIvan` | no |
|
||||
| `IRWIN1` | `JUGGLER` (0x31) | 1 | `parties.asm:2384` `IRWIN` - lv2/6/10/14 VOLTORB | `TrainerJugglerIrwin` | yes (`PHONE_JUGGLER_IRWIN`); IRWIN2 `parties.asm:2407`, IRWIN3 `parties.asm:2415` |
|
||||
| `WALT` | `FIREBREATHER` (0x30) | 6 | `parties.asm:2365` `WALT` - lv11 MAGMAR, lv13 MAGMAR | `TrainerFirebreatherWalt` | no |
|
||||
| `DIRK` | `OFFICER` (0x41) | 2 | `parties.asm:3172` `DIRK` - lv14 GROWLITHE, lv14 GROWLITHE | `TrainerOfficerDirk` | night only |
|
||||
| `ARNIE1` | `BUG_CATCHER` (0x24) | 8 | `parties.asm:1455` `ARNIE` - lv15 VENONAT | `TrainerBugCatcherArnie` | yes (`PHONE_BUG_CATCHER_ARNIE`); ARNIE2 `parties.asm:1487`, ARNIE3 `parties.asm:1492` (lv28 VENOMOTH w/ moves) |
|
||||
| `BRYAN` | `BIRD_KEEPER` (0x18) | 3 | `parties.asm:534` `BRYAN` - lv12 PIDGEY, lv14 PIDGEOTTO | `TrainerBirdKeeperBryan` | no |
|
||||
|
||||
**Wild encounters** - `data/wild/johto_grass.asm`, `def_grass_wildmons ROUTE_35`,
|
||||
rates 10/10/10 percent (morn/day/nite). Gold table:
|
||||
|
||||
| slot | morn | day | nite |
|
||||
|---|---|---|---|
|
||||
| 1 | 12 NIDORAN_M | 12 NIDORAN_M | 12 NIDORAN_M |
|
||||
| 2 | 12 NIDORAN_F | 12 NIDORAN_F | 12 NIDORAN_F |
|
||||
| 3 | 14 DROWZEE | 14 DROWZEE | 14 DROWZEE |
|
||||
| 4 | 10 ABRA | 10 ABRA | 10 ABRA |
|
||||
| 5 | 14 PIDGEY | 14 PIDGEY | 14 HOOTHOOT |
|
||||
| 6 | 10 DITTO | 10 DITTO | 10 DITTO |
|
||||
| 7 | 12 YANMA | 12 YANMA | 12 YANMA |
|
||||
|
||||
(Silver swaps slots 1/2 to NIDORAN_F/NIDORAN_M.)
|
||||
|
||||
Water (`data/wild/johto_water.asm:156` `def_water_wildmons ROUTE_35`): 4 percent -
|
||||
20 PSYDUCK, 15 PSYDUCK, 20 GOLDUCK. Fishing group is `FISHGROUP_POND`
|
||||
(`data/wild/fish.asm:15`). Headbutt: `data/wild/treemon_maps.asm:16` ->
|
||||
`TREEMON_SET_FOREST` (`data/wild/treemons.asm:34`; Gold common CATERPIE/METAPOD/
|
||||
EXEGGCUTE/BUTTERFREE, rare adds PINECO). Swarm: `data/wild/swarm_grass.asm:6` is the
|
||||
YANMA swarm table for `ROUTE_35`.
|
||||
|
||||
**Cut tree** - `maps/Route35.blk` has exactly one cuttable block: block id `$5b` at block
|
||||
(8, 3), i.e. walk cells x 16-17, y 6-7. `data/collision/field_move_blocks.asm:14` gives
|
||||
`db $5b, $3c, 0` for `TILESET_JOHTO`. That is the tree the walkthrough tells the player to
|
||||
cut, and Bug Catcher Arnie at (16, 7) sits immediately behind it. Route 36, Route 37 and
|
||||
National Park have **no** cuttable blocks (National Park's `.park` entry in
|
||||
`field_move_blocks.asm` is only the two grass ids `$13` and `$03`).
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_35_NATIONAL_PARK_GATE
|
||||
|
||||
- Script: `maps/Route35NationalParkGate.asm`
|
||||
- Blocks: `maps/Route35NationalParkGate.blk`
|
||||
- Header (`data/maps/maps.asm:261`): `TILESET_GATE, INDOOR, LANDMARK_ROUTE_35, MUSIC_GOLDENROD_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:240` -> `map_const ROUTE_35_NATIONAL_PARK_GATE, 4, 4`
|
||||
- Connections: none
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 0 | `NATIONAL_PARK` | 3 |
|
||||
| 2 | 4 | 0 | `NATIONAL_PARK` | 4 |
|
||||
| 3 | 3 | 7 | `ROUTE_35` | 3 |
|
||||
| 4 | 4 | 7 | `ROUTE_35` | 3 |
|
||||
|
||||
**Coord events** - none. The map instead uses scene scripts:
|
||||
|
||||
| scene id | value | script | effect |
|
||||
|---|---|---|---|
|
||||
| `SCENE_ROUTE35NATIONALPARKGATE_NOOP` | 0 | `Route35NationalParkGateNoop1Scene` | `end` |
|
||||
| `SCENE_ROUTE35NATIONALPARKGATE_UNUSED` | 1 | `Route35NationalParkGateNoop2Scene` | `end` |
|
||||
| `SCENE_ROUTE35NATIONALPARKGATE_LEAVE_CONTEST_EARLY` | 2 | `Route35NationalParkGateLeaveContestEarlyScene` | `sdefer Route35NationalParkGateLeavingContestEarlyScript` |
|
||||
|
||||
(The `SCENE_*` names are generated by the `scene_script` macro,
|
||||
`macros/scripts/maps.asm:25`, in the order they appear in this file - there is no
|
||||
separate constants file for them.)
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 5 | 0 | `BGEVENT_READ` | `BugCatchingContestExplanationSign` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE35NATIONALPARKGATE_OFFICER1` | `SPRITE_OFFICER` | 2 | 1 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route35OfficerScriptContest` (`56:5e0c`) | `EVENT_ROUTE_35_NATIONAL_PARK_GATE_OFFICER_CONTEST_DAY` |
|
||||
| `ROUTE35NATIONALPARKGATE_YOUNGSTER` | `SPRITE_YOUNGSTER` | 6 | 5 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `Route35NationalParkGateYoungsterScript` | `EVENT_ROUTE_35_NATIONAL_PARK_GATE_YOUNGSTER` |
|
||||
| `ROUTE35NATIONALPARKGATE_OFFICER2` | `SPRITE_OFFICER` | 0 | 3 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route35NationalParkGateOfficerScript` | `EVENT_ROUTE_35_NATIONAL_PARK_GATE_OFFICER_NOT_CONTEST_DAY` |
|
||||
|
||||
**Callbacks**
|
||||
|
||||
- `MAPCALLBACK_NEWMAP` -> `Route35NationalParkGateCheckIfContestRunningCallback`:
|
||||
`checkflag ENGINE_BUG_CONTEST_TIMER` -> `setscene SCENE_..._LEAVE_CONTEST_EARLY`,
|
||||
else `setscene SCENE_..._NOOP`.
|
||||
- `MAPCALLBACK_OBJECTS` -> `Route35NationalParkGateCheckIfContestAvailableCallback`
|
||||
(`56:5db8`): `readvar VAR_WEEKDAY`; `TUESDAY`/`THURSDAY`/`SATURDAY` ->
|
||||
`appear OFFICER1`, `disappear YOUNGSTER`, `disappear OFFICER2`. Otherwise
|
||||
`checkflag ENGINE_BUG_CONTEST_TIMER` (mid-contest keeps the contest officer), else the
|
||||
non-contest pair appears.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route35OfficerScriptContest` (`56:5e0c`) - the entry point. Order of checks:
|
||||
1. `readvar VAR_WEEKDAY`; SUNDAY/MONDAY/WEDNESDAY/FRIDAY -> `Route35NationalParkGate_NoContestToday`.
|
||||
2. `checkflag ENGINE_DAILY_BUG_CONTEST` -> `Route35NationalParkGate_ContestIsOver`.
|
||||
3. `yesorno`. NO -> `Route35NationalParkGate_DeclinedToParticipate`.
|
||||
4. `readvar VAR_PARTYCOUNT` / `ifgreater 1, Route35NationalParkGate_LeaveTheRestBehind`.
|
||||
With more than one mon it needs box space (`readvar VAR_BOXSPACE`, `ifequal 0` ->
|
||||
`_NoRoomInBox`), rejects an EGG lead (`special CheckFirstMonIsEgg`) and rejects a
|
||||
fainted lead (`special ContestDropOffMons` -> `iftrue _FirstMonIsFainted`), then
|
||||
`setevent EVENT_LEFT_MONS_WITH_CONTEST_OFFICER`.
|
||||
5. `Route35NationalParkGate_OkayToProceed`: `setflag ENGINE_BUG_CONTEST_TIMER`,
|
||||
`special PlayMapMusic`, park ball text, `special GiveParkBalls`,
|
||||
`special FadeOutToWhite`, `special SelectRandomBugContestContestants`,
|
||||
`warp NATIONAL_PARK_BUG_CONTEST, 10, 47`.
|
||||
- `Route35NationalParkGateLeavingContestEarlyScript` - runs when re-entering this gate
|
||||
while `ENGINE_BUG_CONTEST_TIMER` is set. `readvar VAR_CONTESTMINUTES` /
|
||||
`addval 1` / `getnum STRING_BUFFER_3`, `yesorno`. YES ->
|
||||
`jumpstd BugContestResultsWarpScript`. NO -> `warp NATIONAL_PARK_BUG_CONTEST, 10, 47`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_BUG_CONTEST_TIMER` | `constants/engine_flags.asm:26` | set by the officer, cleared by `BugContestResultsScript` | "contest in progress" |
|
||||
| `ENGINE_DAILY_BUG_CONTEST` | `constants/engine_flags.asm:98` | set by `BugContestResultsScript` | "already ran today" |
|
||||
| `EVENT_LEFT_MONS_WITH_CONTEST_OFFICER` | `constants/event_flags.asm:439` | officer / results script | party is being held |
|
||||
| `EVENT_ROUTE_35_NATIONAL_PARK_GATE_OFFICER_CONTEST_DAY` / `_NOT_CONTEST_DAY` / `_YOUNGSTER` | `constants/event_flags.asm:1255-1256` (+ youngster) | `MAPCALLBACK_OBJECTS` | which NPC is visible |
|
||||
| `VAR_WEEKDAY` | `constants/script_constants.asm` (`; 0b`) | callback + officer | 0=Sunday .. 6=Saturday |
|
||||
| `VAR_CONTESTMINUTES` | `constants/script_constants.asm:65` (`; 11`) | early-exit script | minutes left |
|
||||
|
||||
**Items** - park balls only (see the Contest map). **Trainers** - none.
|
||||
|
||||
---
|
||||
|
||||
### MAP_NATIONAL_PARK
|
||||
|
||||
- Script: `maps/NationalPark.asm`
|
||||
- Blocks: `maps/NationalPark.blk` (540 bytes = 20 x 27 blocks)
|
||||
- Header (`data/maps/maps.asm:93`): `TILESET_PARK, ROUTE, LANDMARK_NATIONAL_PARK, MUSIC_NATIONAL_PARK, FALSE, PALETTE_AUTO, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:80` -> `map_const NATIONAL_PARK, 20, 27` (cells
|
||||
0..39 x, 0..53 y)
|
||||
- Connections: none (`data/maps/attributes.asm:412` has no `connection` rows). Both exits
|
||||
are warps.
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 33 | 18 | `ROUTE_36_NATIONAL_PARK_GATE` | 1 |
|
||||
| 2 | 33 | 19 | `ROUTE_36_NATIONAL_PARK_GATE` | 2 |
|
||||
| 3 | 10 | 47 | `ROUTE_35_NATIONAL_PARK_GATE` | 1 |
|
||||
| 4 | 11 | 47 | `ROUTE_35_NATIONAL_PARK_GATE` | 2 |
|
||||
|
||||
**Coord events** - none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 14 | 44 | `BGEVENT_READ` | `NationalParkRelaxationSquareSign` |
|
||||
| 27 | 31 | `BGEVENT_READ` | `NationalParkBattleNoticeSign` |
|
||||
| 6 | 47 | `BGEVENT_ITEM` | `NationalParkHiddenFullHeal` -> `hiddenitem FULL_HEAL, EVENT_NATIONAL_PARK_HIDDEN_FULL_HEAL` |
|
||||
| 12 | 4 | `BGEVENT_READ` | `NationalParkTrainerTipsSign` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `NATIONALPARK_LASS1` | `SPRITE_LASS` | 15 | 24 | `WALK_LEFT_RIGHT` (1,0) | `OBJECTTYPE_SCRIPT` | `NationalParkLassScript` | -1 |
|
||||
| `NATIONALPARK_POKEFAN_F1` | `SPRITE_POKEFAN_F` | 14 | 4 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `NationalParkPokefanFScript` | -1 |
|
||||
| `NATIONALPARK_TEACHER1` | `SPRITE_TEACHER` | 27 | 40 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `NationalParkTeacher1Script` (`43:4008`) | -1 |
|
||||
| `NATIONALPARK_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 11 | 41 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `NationalParkYoungster1Script` | -1 |
|
||||
| `NATIONALPARK_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 10 | 41 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `NationalParkYoungster2Script` | -1 |
|
||||
| `NATIONALPARK_TEACHER2` | `SPRITE_TEACHER` | 17 | 41 | `WANDER` (1,2) | `OBJECTTYPE_SCRIPT` | `NationalParkTeacher2Script` | -1 |
|
||||
| `NATIONALPARK_PERSIAN` | `SPRITE_GROWLITHE` | 26 | 40 | `POKEMON` | `OBJECTTYPE_SCRIPT` | `NationalParkPersian` | -1 |
|
||||
| `NATIONALPARK_YOUNGSTER3` | `SPRITE_YOUNGSTER` | 27 | 23 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` sight 3 | `TrainerSchoolboyJack1` | -1 |
|
||||
| `NATIONALPARK_POKEFAN_F2` | `SPRITE_POKEFAN_F` | 18 | 29 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` sight 2 | `TrainerPokefanfBeverly1` | -1 |
|
||||
| `NATIONALPARK_POKEFAN_M` | `SPRITE_POKEFAN_M` | 16 | 9 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` sight 2 | `TrainerPokefanmWilliam` | -1 |
|
||||
| `NATIONALPARK_LASS2` | `SPRITE_LASS` | 8 | 14 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` sight 3 | `TrainerLassKrise` | -1 |
|
||||
| `NATIONALPARK_POKE_BALL1` | `SPRITE_POKE_BALL` | 35 | 12 | `STILL` | `OBJECTTYPE_ITEMBALL` | `NationalParkParlyzHeal` (`43:418f`) | `EVENT_NATIONAL_PARK_PARLYZ_HEAL` |
|
||||
| `NATIONALPARK_GAMEBOY_KID` | `SPRITE_GAMEBOY_KID` | 26 | 6 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `NationalParkGameboyKidScript` | -1 |
|
||||
| `NATIONALPARK_POKE_BALL2` | `SPRITE_POKE_BALL` | 1 | 43 | `STILL` | `OBJECTTYPE_ITEMBALL` | `NationalParkTMDig` (`43:4191`) | `EVENT_NATIONAL_PARK_TM_DIG` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `NationalParkTeacher1Script` (`43:4008`) - the walkthrough's "lady on the bench".
|
||||
`checkevent EVENT_GOT_QUICK_CLAW` / `iftrue .GotQuickClaw`; else text,
|
||||
`verbosegiveitem QUICK_CLAW`, `iffalse .NoRoom`, `setevent EVENT_GOT_QUICK_CLAW`.
|
||||
Unlike the flower shop teacher this one **does** guard the `setevent` with `iffalse`.
|
||||
- `TrainerSchoolboyJack1` - phone trainer (`PHONE_SCHOOLBOY_JACK`). Rematch tiers:
|
||||
`EVENT_CLEARED_RADIO_TOWER` -> `JACK3`, `ENGINE_FLYPOINT_OLIVINE` -> `JACK2`, else `JACK1`.
|
||||
- `TrainerPokefanfBeverly1` - phone trainer (`PHONE_POKEFAN_BEVERLY`), female std scripts
|
||||
(`AskNumber1FScript` etc). Rematch tiers: `EVENT_CLEARED_RADIO_TOWER` -> `BEVERLY3`,
|
||||
`ENGINE_FLYPOINT_MAHOGANY` -> `BEVERLY2`, else `BEVERLY1`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_GOT_QUICK_CLAW` | `constants/event_flags.asm:96` | `NationalParkTeacher1Script` | one-shot gift |
|
||||
| `EVENT_NATIONAL_PARK_PARLYZ_HEAL` | `constants/event_flags.asm:1017` | item ball | ball taken |
|
||||
| `EVENT_NATIONAL_PARK_TM_DIG` | `constants/event_flags.asm:1018` | item ball | ball taken |
|
||||
| `EVENT_NATIONAL_PARK_HIDDEN_FULL_HEAL` | `constants/event_flags.asm:142` | `hiddenitem` | hidden item taken |
|
||||
| `EVENT_BEAT_SCHOOLBOY_JACK`, `EVENT_BEAT_POKEFANF_BEVERLY`, `EVENT_BEAT_POKEFANM_WILLIAM`, `EVENT_BEAT_LASS_KRISE` | `constants/event_flags.asm` | `trainer` rows | defeated bits |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `QUICK_CLAW` | talk to teacher at (27, 40) | `NationalParkTeacher1Script` | `EVENT_GOT_QUICK_CLAW` |
|
||||
| `PARLYZ_HEAL` | item ball at (35, 12) | `NationalParkParlyzHeal` | `EVENT_NATIONAL_PARK_PARLYZ_HEAL` |
|
||||
| `TM_DIG` (TM28) | item ball at (1, 43) | `NationalParkTMDig` | `EVENT_NATIONAL_PARK_TM_DIG` |
|
||||
| `FULL_HEAL` | hidden, bg_event at (6, 47) | `NationalParkHiddenFullHeal` | `EVENT_NATIONAL_PARK_HIDDEN_FULL_HEAL` |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `BEVERLY1` | `POKEFANF` (0x3e) | 1 | `parties.asm:3115` `BEVERLY` `TRAINERTYPE_ITEM` - lv14 SNUBBULL @ BERRY | `TrainerPokefanfBeverly1` | yes (`PHONE_POKEFAN_BEVERLY`); BEVERLY2 `parties.asm:3125`, BEVERLY3 `parties.asm:3130` |
|
||||
| `JACK1` | `SCHOOLBOY` (0x17) | 1 | `parties.asm:409` `JACK` - lv12 ODDISH, lv15 VOLTORB | `TrainerSchoolboyJack1` | yes (`PHONE_SCHOOLBOY_JACK`); JACK2 `parties.asm:484`, JACK3 `parties.asm:490` |
|
||||
| `KRISE` | `LASS` (0x19) | 4 | `parties.asm:658` `KRISE` - lv12 ODDISH, lv15 CUBONE | `TrainerLassKrise` | no |
|
||||
| `WILLIAM` | `POKEFANM` (0x3b) | 1 | `parties.asm:2961` `WILLIAM` `TRAINERTYPE_ITEM` - lv14 RAICHU @ BERRY | `TrainerPokefanmWilliam` | no |
|
||||
|
||||
**Wild encounters** - `data/wild/johto_grass.asm`, `def_grass_wildmons NATIONAL_PARK`,
|
||||
rates 10/10/10 percent. Gold table:
|
||||
|
||||
| slot | morn | day | nite |
|
||||
|---|---|---|---|
|
||||
| 1 | 10 CATERPIE | 10 CATERPIE | 10 HOOTHOOT |
|
||||
| 2 | 10 METAPOD | 10 METAPOD | 10 HOOTHOOT |
|
||||
| 3 | 12 CATERPIE | 11 SUNKERN | 12 HOOTHOOT |
|
||||
| 4 | 12 PIDGEY | 12 PIDGEY | 12 HOOTHOOT |
|
||||
| 5 | 10 PIDGEY | 13 SUNKERN | 10 HOOTHOOT |
|
||||
| 6 | 14 PIDGEY | 14 PIDGEY | 14 HOOTHOOT |
|
||||
| 7 | 14 PIDGEY | 14 PIDGEY | 14 HOOTHOOT |
|
||||
|
||||
(Silver uses WEEDLE/KAKUNA in slots 1-3 morn. The nite block is shared by both versions.)
|
||||
No `NATIONAL_PARK` entry exists in `data/wild/johto_water.asm`, `data/wild/treemon_maps.asm`
|
||||
or `data/wild/swarm_grass.asm`. The rare bugs the walkthrough lists (Scyther, Pinsir,
|
||||
Paras, Venonat, Butterfree, Beedrill) are **Contest-only**; see below.
|
||||
|
||||
---
|
||||
|
||||
### MAP_NATIONAL_PARK_BUG_CONTEST
|
||||
|
||||
- Script: `maps/NationalParkBugContest.asm`
|
||||
- Blocks: none of its own; `data/maps/attributes.asm:413` gives
|
||||
`map_attributes NationalParkBugContest, NATIONAL_PARK_BUG_CONTEST, $00` and the map is a
|
||||
same-geometry twin of National Park.
|
||||
- Header (`data/maps/maps.asm:94`): `TILESET_PARK, ROUTE, LANDMARK_NATIONAL_PARK, MUSIC_BUG_CATCHING_CONTEST, FALSE, PALETTE_AUTO, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:81` -> `map_const NATIONAL_PARK_BUG_CONTEST, 20, 27`
|
||||
- Connections: none
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 33 | 18 | `ROUTE_36_NATIONAL_PARK_GATE` | 1 |
|
||||
| 2 | 33 | 19 | `ROUTE_36_NATIONAL_PARK_GATE` | 1 |
|
||||
| 3 | 10 | 47 | `ROUTE_35_NATIONAL_PARK_GATE` | 1 |
|
||||
| 4 | 11 | 47 | `ROUTE_35_NATIONAL_PARK_GATE` | 1 |
|
||||
|
||||
(Warps 2 and 4 point at dest warp 1, not 2 - that is what the file says.)
|
||||
|
||||
**Coord events** - none.
|
||||
|
||||
**BG events** - same four rows as National Park, aimed at the `...BugContest...` copies:
|
||||
(14,44) Relaxation Square sign, (27,31) Battle Notice sign, (6,47) `BGEVENT_ITEM`
|
||||
`hiddenitem FULL_HEAL, EVENT_NATIONAL_PARK_HIDDEN_FULL_HEAL` (the same flag as the normal
|
||||
map), (12,4) Trainer Tips sign.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `..._YOUNGSTER1` | `SPRITE_YOUNGSTER` | 19 | 29 | `WANDER` (2,2) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant1AScript` | `EVENT_BUG_CATCHING_CONTESTANT_1A` |
|
||||
| `..._YOUNGSTER2` | `SPRITE_YOUNGSTER` | 28 | 22 | `WANDER` (2,2) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant2AScript` | `..._2A` |
|
||||
| `..._ROCKER` | `SPRITE_ROCKER` | 9 | 18 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant3AScript` | `..._3A` |
|
||||
| `..._POKEFAN_M` | `SPRITE_POKEFAN_M` | 7 | 13 | `WALK_UP_DOWN` (1,0) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant4AScript` | `..._4A` |
|
||||
| `..._YOUNGSTER3` | `SPRITE_YOUNGSTER` | 23 | 9 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant5AScript` | `..._5A` |
|
||||
| `..._YOUNGSTER4` | `SPRITE_YOUNGSTER` | 27 | 13 | `WANDER` (3,3) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant6AScript` | `..._6A` |
|
||||
| `..._LASS` | `SPRITE_LASS` | 7 | 23 | `WALK_LEFT_RIGHT` (2,0) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant7AScript` | `..._7A` |
|
||||
| `..._YOUNGSTER5` | `SPRITE_YOUNGSTER` | 11 | 27 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant8AScript` | `..._8A` |
|
||||
| `..._YOUNGSTER6` | `SPRITE_YOUNGSTER` | 16 | 8 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant9AScript` | `..._9A` |
|
||||
| `..._YOUNGSTER7` | `SPRITE_YOUNGSTER` | 17 | 34 | `WANDER` (3,3) | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant10AScript` | `..._10A` |
|
||||
| `..._POKE_BALL1` | `SPRITE_POKE_BALL` | 35 | 12 | `STILL` | `OBJECTTYPE_ITEMBALL` | `NationalParkBugContestParlyzHeal` | `EVENT_NATIONAL_PARK_PARLYZ_HEAL` |
|
||||
| `..._POKE_BALL2` | `SPRITE_POKE_BALL` | 1 | 43 | `STILL` | `OBJECTTYPE_ITEMBALL` | `NationalParkBugContestTMDig` | `EVENT_NATIONAL_PARK_TM_DIG` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- Every `BugCatchingContestantNAScript` is `faceplayer / opentext / writetext / waitbutton
|
||||
/ closetext / end` - flavour only, no branching. The `B` variants in the north gate are
|
||||
the ones that branch on `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1` (set by
|
||||
`BugContestResults_FirstPlace`) to say "you won".
|
||||
- The battle path is `BugCatchingContestBattleScript::`
|
||||
(`engine/events/bug_contest/contest.asm:9`): `loadvar VAR_BATTLETYPE, BATTLETYPE_CONTEST`,
|
||||
`randomwildmon`, `startbattle`, `reloadmapafterbattle`, `readmem wParkBallsRemaining`,
|
||||
`iffalse BugCatchingContestOutOfBallsScript`.
|
||||
- `GiveParkBalls` (`engine/events/bug_contest/contest.asm:1`) clears `wContestMon`, writes
|
||||
`BUG_CONTEST_BALLS` into `wParkBallsRemaining` and `farcall StartBugContestTimer`.
|
||||
|
||||
**Contest constants** (`constants/script_constants.asm`)
|
||||
|
||||
| constant | line | value |
|
||||
|---|---|---|
|
||||
| `BUG_CONTEST_BALLS` | 283 | 20 |
|
||||
| `BUG_CONTEST_MINUTES` | 284 | 20 |
|
||||
| `BUG_CONTEST_PLAYER` | 286 | 1 |
|
||||
| `NUM_BUG_CONTESTANTS` | 287 | 10 (not counting the player) |
|
||||
| `VAR_CONTESTMINUTES` | 65 | var id 0x11 |
|
||||
|
||||
**Contest wild table** - `data/wild/bug_contest_mons.asm`, label `ContestMons`
|
||||
(`25:7bb8`). This table replaces National Park's grass entirely for the duration:
|
||||
|
||||
| % | species | min lv | max lv |
|
||||
|---|---|---|---|
|
||||
| 20 | CATERPIE | 7 | 18 |
|
||||
| 20 | WEEDLE | 7 | 18 |
|
||||
| 10 | METAPOD | 9 | 18 |
|
||||
| 10 | KAKUNA | 9 | 18 |
|
||||
| 5 | BUTTERFREE | 12 | 15 |
|
||||
| 5 | BEEDRILL | 12 | 15 |
|
||||
| 10 | VENONAT | 10 | 16 |
|
||||
| 10 | PARAS | 10 | 17 |
|
||||
| 5 | SCYTHER | 13 | 14 |
|
||||
| 5 | PINSIR | 13 | 14 |
|
||||
| -1 (terminator row) | VENOMOTH | 30 | 40 |
|
||||
|
||||
**Scoring** - `ContestScore` (`04:7cbc`, `engine/events/bug_contest/judging.asm`) sums:
|
||||
`MaxHP * 4` (high byte only), then Attack, Defense, Speed, SpclAtk, SpclDef, then a DV
|
||||
bonus assembled from bit 1 of each DV nibble, then `remaining HP / 8`, then `+1` if the
|
||||
mon is holding an item. Five of the ten AI contestants are rolled by
|
||||
`ComputeAIContestantScores`, each picking one of three canned mon/score pairs from
|
||||
`BugContestantPointers` (`data/events/bug_contest_winners.asm`) with a 0..7 random
|
||||
perturbation. `DetermineContestWinners` keeps a 3-deep podium.
|
||||
|
||||
**Prizes** - `BugContestResultsScript` (`40:420e`, `engine/events/std_scripts.asm:275`):
|
||||
|
||||
| place | item | no-room fallback flag |
|
||||
|---|---|---|
|
||||
| 1st | `SUN_STONE` | `EVENT_CONTEST_OFFICER_HAS_SUN_STONE` |
|
||||
| 2nd | `EVERSTONE` | `EVENT_CONTEST_OFFICER_HAS_EVERSTONE` |
|
||||
| 3rd | `GOLD_BERRY` | `EVENT_CONTEST_OFFICER_HAS_GOLD_BERRY` |
|
||||
| consolation | `BERRY` | `EVENT_CONTEST_OFFICER_HAS_BERRY` |
|
||||
|
||||
First place also sets `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1`. The tail of the script clears
|
||||
`ENGINE_BUG_CONTEST_TIMER`, sets all twenty `EVENT_BUG_CATCHING_CONTESTANT_*` flags, sets
|
||||
`ENGINE_DAILY_BUG_CONTEST`, and resets both gate scenes to their NOOP.
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_36_NATIONAL_PARK_GATE
|
||||
|
||||
- Script: `maps/Route36NationalParkGate.asm`
|
||||
- Blocks: `maps/Route36NationalParkGate.blk`
|
||||
- Header (`data/maps/maps.asm:263`): `TILESET_GATE, INDOOR, LANDMARK_ROUTE_36, MUSIC_GOLDENROD_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:242` -> `map_const ROUTE_36_NATIONAL_PARK_GATE, 5, 4`
|
||||
- Connections: none
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 0 | 4 | `NATIONAL_PARK` | 1 |
|
||||
| 2 | 0 | 5 | `NATIONAL_PARK` | 2 |
|
||||
| 3 | 9 | 4 | `ROUTE_36` | 1 |
|
||||
| 4 | 9 | 5 | `ROUTE_36` | 2 |
|
||||
|
||||
**Coord events** - none. Scene scripts (same three-slot shape as the south gate):
|
||||
`SCENE_ROUTE36NATIONALPARKGATE_NOOP` (0), `..._UNUSED` (1),
|
||||
`..._LEAVE_CONTEST_EARLY` (2) -> `sdefer Route36NationalParkGateLeavingContestEarlyScript`.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 6 | 0 | `BGEVENT_READ` | `BugCatchingContestExplanationSign` (the label defined in `maps/Route35NationalParkGate.asm`) |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE36NATIONALPARKGATE_OFFICER1` | `SPRITE_OFFICER` | 0 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route36OfficerScriptContest` (`56:67cc`) | `EVENT_ROUTE_36_NATIONAL_PARK_GATE_OFFICER_CONTEST_DAY` |
|
||||
| `..._YOUNGSTER1` | `SPRITE_YOUNGSTER` | 2 | 5 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant1BScript` | `EVENT_BUG_CATCHING_CONTESTANT_1B` |
|
||||
| `..._YOUNGSTER2` | `SPRITE_YOUNGSTER` | 4 | 5 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant2BScript` | `..._2B` |
|
||||
| `..._ROCKER` | `SPRITE_ROCKER` | 2 | 6 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant3BScript` | `..._3B` |
|
||||
| `..._POKEFAN_M` | `SPRITE_POKEFAN_M` | 6 | 5 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant4BScript` | `..._4B` |
|
||||
| `..._YOUNGSTER3` | `SPRITE_YOUNGSTER` | 2 | 7 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant5BScript` | `..._5B` |
|
||||
| `..._YOUNGSTER4` | `SPRITE_YOUNGSTER` | 5 | 6 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant6BScript` | `..._6B` |
|
||||
| `..._LASS` | `SPRITE_LASS` | 3 | 6 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant7BScript` | `..._7B` |
|
||||
| `..._YOUNGSTER5` | `SPRITE_YOUNGSTER` | 4 | 7 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant8BScript` | `..._8B` |
|
||||
| `..._YOUNGSTER6` | `SPRITE_YOUNGSTER` | 6 | 7 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant9BScript` | `..._9B` |
|
||||
| `..._YOUNGSTER7` | `SPRITE_YOUNGSTER` | 6 | 6 | `STANDING_UP` | `OBJECTTYPE_SCRIPT` | `BugCatchingContestant10BScript` | `..._10B` |
|
||||
| `ROUTE36NATIONALPARKGATE_OFFICER2` | `SPRITE_OFFICER` | 3 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route36NationalParkGateOfficerScript` | `EVENT_ROUTE_36_NATIONAL_PARK_GATE_OFFICER_NOT_CONTEST_DAY` |
|
||||
|
||||
**Callbacks**
|
||||
|
||||
- `MAPCALLBACK_NEWMAP` -> `Route36NationalParkGateCheckIfContestRunningCallback`
|
||||
(same shape as the south gate).
|
||||
- `MAPCALLBACK_OBJECTS` -> `Route36NationalParkGateCheckIfContestAvailableCallback`
|
||||
(`56:6710`): first `checkevent EVENT_WARPED_FROM_ROUTE_35_NATIONAL_PARK_GATE` -> return
|
||||
unchanged (this is how the awards ceremony keeps the contest officer standing there when
|
||||
you finished from the *south* gate). Then weekday / `ENGINE_BUG_CONTEST_TIMER`, same as
|
||||
the south gate.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route36OfficerScriptContest` (`56:67cc`) - same structure as the south gate's officer
|
||||
but warps to `NATIONAL_PARK_BUG_CONTEST, 33, 18` and, if `ENGINE_DAILY_BUG_CONTEST` is
|
||||
already set, falls into `Route36Officer_ContestHasConcluded`, which is the **prize
|
||||
holding desk**: it checks `EVENT_CONTEST_OFFICER_HAS_SUN_STONE`, then `_EVERSTONE`, then
|
||||
`_GOLD_BERRY`, then `_BERRY`, `verbosegiveitem`s the first one it finds and clears that
|
||||
event. A bot that won with a full PACK collects here.
|
||||
- `Route36NationalParkGateLeavingContestEarlyScript` - the finish-now flow.
|
||||
`special FadeOutToBlack`, `.CopyContestants` (turns each unset `..._NA` flag into an
|
||||
`appear` of the matching `B` object), `disappear OFFICER1` / `appear OFFICER2`,
|
||||
`applymovement PLAYER, Route36NationalParkGatePlayerWaitWithContestantsMovement`
|
||||
(`big_step DOWN`, `big_step RIGHT`, `turn_head UP`), then
|
||||
`jumpstd BugContestResultsScript`.
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_36
|
||||
|
||||
- Script: `maps/Route36.asm`
|
||||
- Blocks: `maps/Route36.blk` (270 bytes = 30 x 9 blocks)
|
||||
- Header (`data/maps/maps.asm:249`): `TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_36, MUSIC_ROUTE_36, FALSE, PALETTE_AUTO, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:228` -> `map_const ROUTE_36, 30, 9` (cells
|
||||
0..59 x, 0..17 y)
|
||||
- Connections (`data/maps/attributes.asm:206`): north `ROUTE_37` offset 10, south
|
||||
`ROUTE_35` offset 0, east `VIOLET_CITY` offset 0
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 18 | 8 | `ROUTE_36_NATIONAL_PARK_GATE` | 3 |
|
||||
| 2 | 18 | 9 | `ROUTE_36_NATIONAL_PARK_GATE` | 4 |
|
||||
| 3 | 47 | 13 | `ROUTE_36_RUINS_OF_ALPH_GATE` | 1 |
|
||||
| 4 | 48 | 13 | `ROUTE_36_RUINS_OF_ALPH_GATE` | 2 |
|
||||
|
||||
**Coord events** - none. (Sudowoodo is an object, not a trip-wire.)
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 29 | 1 | `BGEVENT_READ` | `Route36TrainerTips2` |
|
||||
| 45 | 11 | `BGEVENT_READ` | `RuinsOfAlphNorthSign` |
|
||||
| 55 | 7 | `BGEVENT_READ` | `Route36Sign` |
|
||||
| 21 | 7 | `BGEVENT_READ` | `Route36TrainerTips1` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE36_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 20 | 12 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` sight 2 | `TrainerPsychicMark` | -1 |
|
||||
| `ROUTE36_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 31 | 14 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` sight 5 | `TrainerSchoolboyAlan1` | -1 |
|
||||
| `ROUTE36_WEIRD_TREE` | `SPRITE_WEIRD_TREE` | 35 | 9 | `SPRITEMOVEDATA_SUDOWOODO` | `OBJECTTYPE_SCRIPT` | `SudowoodoScript` (`4b:61aa`) | `EVENT_ROUTE_36_SUDOWOODO` |
|
||||
| `ROUTE36_LASS1` | `SPRITE_LASS` | 51 | 8 | `WALK_LEFT_RIGHT` (2,0) | `OBJECTTYPE_SCRIPT` | `Route36LassScript` (`4b:621e`) | -1 |
|
||||
| `ROUTE36_FISHER` | `SPRITE_FISHER` | 44 | 9 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `Route36RockSmashGuyScript` (`4b:61f7`) | -1 |
|
||||
| `ROUTE36_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 21 | 4 | `STILL` | `OBJECTTYPE_SCRIPT` | `Route36FruitTree` (`4b:631a`) | -1 |
|
||||
| `ROUTE36_ARTHUR` | `SPRITE_YOUNGSTER` | 46 | 6 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `ArthurScript` (`4b:62d4`) | `EVENT_ROUTE_36_ARTHUR_OF_THURSDAY` |
|
||||
|
||||
**Callbacks**
|
||||
|
||||
- `MAPCALLBACK_OBJECTS` -> `Route36ArthurCallback` (`4b:619e`): `readvar VAR_WEEKDAY`,
|
||||
`ifequal THURSDAY, .ArthurAppears` (-> `appear ROUTE36_ARTHUR`), otherwise
|
||||
`disappear ROUTE36_ARTHUR`.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `SudowoodoScript` (`4b:61aa`) - the gate of this section.
|
||||
`checkitem SQUIRTBOTTLE` / `iftrue .Fight`. Without it: `waitsfx`,
|
||||
`playsound SFX_SANDSTORM`, `applymovement ROUTE36_WEIRD_TREE, SudowoodoShakeMovement`
|
||||
(`tree_shake`), `end` - the tree just wobbles and stays solid.
|
||||
With it: `writetext UseSquirtbottleText`, `yesorno`, `iffalse DidntUseSquirtbottleScript`.
|
||||
- `WateredWeirdTreeScript::` (`4b:61c1`) - exported so that using the SQUIRTBOTTLE from the
|
||||
PACK reaches the same body (`engine/events/squirtbottle.asm` does
|
||||
`farsjump WateredWeirdTreeScript`). Body: text, shake, `SudowoodoAttackedText`,
|
||||
**`loadwildmon SUDOWOODO, 20`**, `startbattle`, `setevent EVENT_FOUGHT_SUDOWOODO`,
|
||||
`ifequal DRAW, DidntCatchSudowoodo`, `disappear ROUTE36_WEIRD_TREE`,
|
||||
`variablesprite SPRITE_WEIRD_TREE, SPRITE_TWIN`, `reloadmapafterbattle`.
|
||||
Note the ordering: `EVENT_FOUGHT_SUDOWOODO` is set **before** the DRAW check, so a run
|
||||
or a KO still clears the road and still unlocks TM08.
|
||||
- `DidntCatchSudowoodo` (`4b:61e9`) - `reloadmapafterbattle`,
|
||||
`applymovement ROUTE36_WEIRD_TREE, WeirdTreeMovement_Flee` (two `fast_jump_step UP`),
|
||||
`disappear`, `variablesprite`, `special LoadUsedSpritesGFX`.
|
||||
- `_Squirtbottle` (`14:4763`, `engine/events/squirtbottle.asm`) - the PACK path.
|
||||
`.CheckCanUseSquirtbottle` requires `wMapGroup == GROUP_ROUTE_36`,
|
||||
`wMapNumber == MAP_ROUTE_36`, `GetFacingObject` to succeed, and that object's movement
|
||||
data to be `SPRITEMOVEDATA_SUDOWOODO`. Anything else prints
|
||||
`_SquirtbottleNothingText`.
|
||||
- `Route36RockSmashGuyScript` (`4b:61f7`) - `checkevent EVENT_GOT_TM08_ROCK_SMASH` ->
|
||||
`.AlreadyGotRockSmash`; `checkevent EVENT_FOUGHT_SUDOWOODO` -> `.ClearedSudowoodo`
|
||||
(`verbosegiveitem TM_ROCK_SMASH`, `iffalse .NoRoomForTM`,
|
||||
`setevent EVENT_GOT_TM08_ROCK_SMASH`). Before Sudowoodo it just prints
|
||||
`RockSmashGuyText1`.
|
||||
- `ArthurScript` (`4b:62d4`) - `checkevent EVENT_GOT_HARD_STONE_FROM_ARTHUR` ->
|
||||
`.AlreadyGotStone`; `readvar VAR_WEEKDAY` / `ifnotequal THURSDAY, ArthurNotThursdayScript`;
|
||||
else `setevent EVENT_MET_ARTHUR_OF_THURSDAY`, `verbosegiveitem HARD_STONE`,
|
||||
`iffalse .BagFull`, `setevent EVENT_GOT_HARD_STONE_FROM_ARTHUR`.
|
||||
- `Route36LassScript` (`4b:621e`) - flavour, branches on `EVENT_FOUGHT_SUDOWOODO`. This is
|
||||
the girl at (51, 8). She is **not** Floria and gives nothing.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_ROUTE_36_SUDOWOODO` | `constants/event_flags.asm:1178` | object visibility flag; cleared by `disappear` in `WateredWeirdTreeScript` | tree present on the map |
|
||||
| `EVENT_FOUGHT_SUDOWOODO` | `constants/event_flags.asm:51` | set by `WateredWeirdTreeScript`; read by the Rock Smash guy, the Route 36 lass, the Route 35 gate pokefan | **the** progress flag of this section |
|
||||
| `EVENT_GOT_TM08_ROCK_SMASH` | `constants/event_flags.asm:84` | `Route36RockSmashGuyScript` | TM08 taken |
|
||||
| `EVENT_ROUTE_36_ARTHUR_OF_THURSDAY` | `constants/event_flags.asm:1276` | `Route36ArthurCallback` | Arthur visible |
|
||||
| `EVENT_MET_ARTHUR_OF_THURSDAY` | `constants/event_flags.asm:111` | `ArthurScript` | first-meeting text |
|
||||
| `EVENT_GOT_HARD_STONE_FROM_ARTHUR` | `constants/event_flags.asm:112` | `ArthurScript` | HARD STONE taken |
|
||||
| `EVENT_BEAT_PSYCHIC_MARK`, `EVENT_BEAT_SCHOOLBOY_ALAN` | `constants/event_flags.asm` | `trainer` rows | defeated bits |
|
||||
| `EVENT_ALAN_ASKED_FOR_PHONE_NUMBER`, `EVENT_ALAN_READY_FOR_REMATCH` | `constants/event_flags.asm` | Alan script | phone state machine |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `ICE_BERRY` | fruit tree object at (21, 4) | `FRUITTREE_ROUTE_36` -> `data/items/fruit_trees.asm:16` | daily |
|
||||
| `TM_ROCK_SMASH` (TM08) | talk to the fisher at (44, 9) after Sudowoodo | `Route36RockSmashGuyScript` | `EVENT_GOT_TM08_ROCK_SMASH` |
|
||||
| `HARD_STONE` | talk to Arthur at (46, 6) on a Thursday | `ArthurScript` | `EVENT_GOT_HARD_STONE_FROM_ARTHUR` |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `MARK` | `PSYCHIC_T` (0x34) | 7 | `parties.asm:2545` `MARK` `TRAINERTYPE_MOVES` - lv13 ABRA (TELEPORT, FLASH), lv13 ABRA (TELEPORT, FLASH), lv15 KADABRA (TELEPORT, KINESIS, CONFUSION) | `TrainerPsychicMark` | no |
|
||||
| `ALAN1` | `SCHOOLBOY` (0x17) | 3 | `parties.asm:423` `ALAN` - lv16 TANGELA | `TrainerSchoolboyAlan1` | yes (`PHONE_SCHOOLBOY_ALAN`); ALAN2 `parties.asm:496`, ALAN3 `parties.asm:502` |
|
||||
|
||||
**Wild** - `data/wild/johto_grass.asm`, `def_grass_wildmons ROUTE_36`, 10/10/10 percent.
|
||||
Gold:
|
||||
|
||||
| slot | morn | day | nite |
|
||||
|---|---|---|---|
|
||||
| 1 | 12 NIDORAN_M | 12 NIDORAN_M | 12 NIDORAN_M |
|
||||
| 2 | 12 NIDORAN_F | 12 NIDORAN_F | 12 NIDORAN_F |
|
||||
| 3 | 13 PIDGEY | 14 PIDGEY | 13 HOOTHOOT |
|
||||
| 4 | 13 GROWLITHE | 13 GROWLITHE | 13 GROWLITHE |
|
||||
| 5 | 13 STANTLER | 13 STANTLER | 13 STANTLER |
|
||||
| 6 | 15 PIDGEY | 15 GROWLITHE | 15 HOOTHOOT |
|
||||
| 7 | 15 PIDGEY | 15 GROWLITHE | 15 HOOTHOOT |
|
||||
|
||||
(Silver substitutes VULPIX for GROWLITHE and swaps the NIDORAN order.) No `ROUTE_36`
|
||||
entry in `johto_water.asm`. Headbutt: `data/wild/treemon_maps.asm:17` -> `TREEMON_SET_FOREST`.
|
||||
`ROUTE_36` is a roam node in `data/wild/roammon_maps.asm:26` with four exits
|
||||
(`ROUTE_35, ROUTE_31, ROUTE_32, ROUTE_37`).
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_37 (header-listed only; not entered by this walkthrough section)
|
||||
|
||||
- Script: `maps/Route37.asm`, Blocks: `maps/Route37.blk`
|
||||
- Header (`data/maps/maps.asm:250`): `TILESET_JOHTO, ROUTE, LANDMARK_ROUTE_37, MUSIC_ROUTE_36, FALSE, PALETTE_AUTO, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:229` -> `map_const ROUTE_37, 10, 9`
|
||||
- Connections (`data/maps/attributes.asm:211`): north `ECRUTEAK_CITY` offset -5,
|
||||
south `ROUTE_36` offset -10
|
||||
- **Warps: `def_warp_events` is empty.** Route 37 is reached only by connection.
|
||||
- BG events: (5,3) `BGEVENT_READ` `Route37Sign`; (4,2) `BGEVENT_ITEM`
|
||||
`Route37HiddenEther` -> `hiddenitem ETHER, EVENT_ROUTE_37_HIDDEN_ETHER`.
|
||||
- Objects: Twins Ann & Anne at (6,12) and (7,12) (`ANNANDANNE1` / `ANNANDANNE2`, both
|
||||
sharing `EVENT_BEAT_TWINS_ANN_AND_ANNE`), Psychic Greg at (9,6), three fruit trees at
|
||||
(13,5) `FRUITTREE_ROUTE_37_1` = `RED_APRICORN`, (16,5) `_2` = `BLU_APRICORN`,
|
||||
(15,7) `_3` = `BLK_APRICORN` (`data/items/fruit_trees.asm:20-22`), and Sunny of Sunday
|
||||
at (16,8) behind `EVENT_ROUTE_37_SUNNY_OF_SUNDAY` (gift `MAGNET`,
|
||||
`EVENT_GOT_MAGNET_FROM_SUNNY`).
|
||||
- Callback: `MAPCALLBACK_OBJECTS` -> `Route37SunnyCallback` (weekday == SUNDAY).
|
||||
- Wild: `data/wild/johto_grass.asm` `def_grass_wildmons ROUTE_37`, 10/10/10; Gold nite
|
||||
slot 1/5/6/7 are SPINARAK 13/15/15/15, Silver morn is LEDYBA - this is where the
|
||||
walkthrough's "Ledyba (Silver only, morning only) / Spinarak (Gold only, night only)"
|
||||
actually lives.
|
||||
|
||||
---
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| SQUIRTBOTTLE not obtainable | `maps/GoldenrodFlowerShop.asm:FlowerShopTeacherScript` (`57:52d1`) - `checkflag ENGINE_PLAINBADGE` / `iffalse .Lalala` | Plain Badge (Whitney) | beat Whitney; talk to the teacher at (2,4) |
|
||||
| Sudowoodo blocks Route 36 | `maps/Route36.asm:SudowoodoScript` (`4b:61aa`) - the object at cell (35,9) with `SPRITEMOVEDATA_SUDOWOODO` is a solid NPC; the script's `checkitem SQUIRTBOTTLE` / `iftrue .Fight` is the only way past | SQUIRTBOTTLE in the PACK | `EVENT_FOUGHT_SUDOWOODO` set by `WateredWeirdTreeScript` (`4b:61c1`); the `disappear` runs on any outcome except a draw-flee, and the flee path disappears it too |
|
||||
| SQUIRTBOTTLE from the PACK does nothing elsewhere | `engine/events/squirtbottle.asm:_Squirtbottle` (`14:4763`) `.CheckCanUseSquirtbottle` | map must be `ROUTE_36` and the faced object must carry `SPRITEMOVEDATA_SUDOWOODO` | face the tree first |
|
||||
| TM08 ROCK SMASH withheld | `maps/Route36.asm:Route36RockSmashGuyScript` (`4b:61f7`) - `checkevent EVENT_FOUGHT_SUDOWOODO` | Sudowoodo dealt with | talk to the fisher at (44, 9) |
|
||||
| East side of Route 35 (Arnie, Bryan, TM04) | `maps/Route35.blk` block `$5b` at block (8,3) = cells x16-17 y6-7; `data/collision/field_move_blocks.asm:14` | CUT, which needs `ENGINE_HIVEBADGE` (`engine/events/overworld.asm:133` `CutFunction.CheckAble`) and a party mon that knows CUT | Azalea's badge + HM01; or enter from Route 36 via the south connection, which is what the walkthrough does |
|
||||
| Bug Contest closed | `maps/Route35NationalParkGate.asm:Route35OfficerScriptContest` (`56:5e0c`) and `maps/Route36NationalParkGate.asm:Route36OfficerScriptContest` (`56:67cc`) - `readvar VAR_WEEKDAY`, four `ifequal` bail-outs, plus `checkflag ENGINE_DAILY_BUG_CONTEST` | Tuesday, Thursday or Saturday, and not already run today | set the RTC weekday; `ENGINE_DAILY_BUG_CONTEST` clears on the daily reset |
|
||||
| Contest entry refused | same scripts - `readvar VAR_PARTYCOUNT` / `VAR_BOXSPACE` / `special CheckFirstMonIsEgg` / `special ContestDropOffMons` | lead mon is not an EGG, is not fainted, and there is party-or-box room for a caught bug | reorder the party, heal, free a box slot |
|
||||
| Kenya not given | `maps/Route35GoldenrodGate.asm:RandyScript` (`56:59ce`) - `readvar VAR_PARTYCOUNT` / `ifequal PARTY_LENGTH, .partyfull` | fewer than 6 party mons | deposit one |
|
||||
| Arthur / Sunny absent | `Route36ArthurCallback` (`4b:619e`) / `Route37SunnyCallback` - `MAPCALLBACK_OBJECTS` + `VAR_WEEKDAY` | Thursday / Sunday | RTC weekday |
|
||||
| Officer Dirk will not battle | `maps/Route35.asm:TrainerOfficerDirk` - `checktime NITE` / `iffalse .NotNight` | night time of day | wait, or set the RTC |
|
||||
|
||||
---
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
Coordinates are `(x, y)` walk cells as written in the asm. "talk" means stand adjacent
|
||||
facing the target and press A.
|
||||
|
||||
1. `MAP_GOLDENROD_CITY` -> warp 6 at (33, 5). Pre: `ENGINE_PLAINBADGE` set. Post: on
|
||||
`MAP_GOLDENROD_FLOWER_SHOP`.
|
||||
2. `MAP_GOLDENROD_FLOWER_SHOP`: talk to `GOLDENRODFLOWERSHOP_TEACHER` at (2, 4).
|
||||
Pre: `!EVENT_GOT_SQUIRTBOTTLE` and a free KEY ITEM slot. Post:
|
||||
`EVENT_GOT_SQUIRTBOTTLE`, SQUIRTBOTTLE in the PACK.
|
||||
3. Exit via warp 1 (2, 7) -> `MAP_GOLDENROD_CITY` warp 6. Walk to (19, 1), warp 13 ->
|
||||
`MAP_ROUTE_35_GOLDENROD_GATE` warp 3.
|
||||
4. Optional: talk to `ROUTE35GOLDENRODGATE_RANDY` at (0, 4), answer YES.
|
||||
Pre: `VAR_PARTYCOUNT < 6`. Post: `EVENT_GOT_KENYA`, lv10 SPEAROW "KENYA" with
|
||||
`FLOWER_MAIL` in the last party slot.
|
||||
5. Warp 1 at (4, 0) -> `MAP_ROUTE_35` warp 1 (lands at (9, 33)).
|
||||
6. `MAP_ROUTE_35`, walk north. Battle, in the walkthrough's order:
|
||||
Kim (11, 24), Elliot (8, 20), Brooke (7, 20), Ivan (3, 19), Irwin (5, 10),
|
||||
Walt (2, 10). Post: `EVENT_BEAT_*` for each. Sight ranges are 3/3/3/4/2/2.
|
||||
7. Optional, night only: talk to `ROUTE35_OFFICER` at (5, 6). Pre: `checktime NITE` true
|
||||
and `!EVENT_BEAT_OFFICER_DIRK`. Post: `EVENT_BEAT_OFFICER_DIRK`.
|
||||
8. Walk to (3, 5), warp 3 -> `MAP_ROUTE_35_NATIONAL_PARK_GATE` warp 3.
|
||||
9. Optional Contest: on a Tue/Thu/Sat with `!ENGINE_DAILY_BUG_CONTEST`, talk to
|
||||
`ROUTE35NATIONALPARKGATE_OFFICER1` at (2, 1) and answer YES. Post:
|
||||
`ENGINE_BUG_CONTEST_TIMER` set, 20 PARK BALLs, warp to
|
||||
`MAP_NATIONAL_PARK_BUG_CONTEST` (10, 47). Finish by re-entering either gate and
|
||||
answering YES, or by running the timer/balls out. Post: `ENGINE_DAILY_BUG_CONTEST`,
|
||||
prize per placing, `ENGINE_BUG_CONTEST_TIMER` cleared.
|
||||
10. Warp 1 at (3, 0) -> `MAP_NATIONAL_PARK` warp 3 (lands at (10, 47)).
|
||||
11. `MAP_NATIONAL_PARK`: talk to `NATIONALPARK_TEACHER1` at (27, 40).
|
||||
Pre: `!EVENT_GOT_QUICK_CLAW`, PACK room. Post: `EVENT_GOT_QUICK_CLAW`.
|
||||
12. Optional: item ball at (1, 43) -> `TM_DIG`, flag `EVENT_NATIONAL_PARK_TM_DIG`.
|
||||
Hidden `FULL_HEAL` at bg (6, 47), flag `EVENT_NATIONAL_PARK_HIDDEN_FULL_HEAL`.
|
||||
Item ball at (35, 12) -> `PARLYZ_HEAL`, flag `EVENT_NATIONAL_PARK_PARLYZ_HEAL`.
|
||||
13. Battle Beverly (18, 29), Jack (27, 23), Krise (8, 14), William (16, 9).
|
||||
14. Walk to (33, 18), warp 1 -> `MAP_ROUTE_36_NATIONAL_PARK_GATE` warp 1.
|
||||
If a prize was held: talk to `ROUTE36NATIONALPARKGATE_OFFICER1` at (0, 3) with
|
||||
`ENGINE_DAILY_BUG_CONTEST` set to collect it.
|
||||
15. Warp 3 at (9, 4) -> `MAP_ROUTE_36` warp 1 (lands at (18, 8)).
|
||||
16. `MAP_ROUTE_36`: fruit tree at (21, 4) -> `ICE_BERRY`. Battle Psychic Mark at (20, 12).
|
||||
17. Leave south (`ROUTE_36` south connection, offset 0) into `MAP_ROUTE_35`'s north edge.
|
||||
18. `MAP_ROUTE_35` east side: CUT the tree at block (8, 3) (cells x16-17, y6-7).
|
||||
Pre: `ENGINE_HIVEBADGE` + a mon knowing CUT. Then battle Arnie at (16, 7) and
|
||||
Bryan at (14, 28), and take the item ball at (13, 16) -> `TM_ROLLOUT`
|
||||
(`EVENT_ROUTE_35_TM_ROLLOUT`). Talk to Arnie a second time for
|
||||
`PHONE_BUG_CATCHER_ARNIE`.
|
||||
19. Return north to `MAP_ROUTE_36`. Battle Schoolboy Alan at (31, 14); talk again for
|
||||
`PHONE_SCHOOLBOY_ALAN`.
|
||||
20. Walk to face `ROUTE36_WEIRD_TREE` at (35, 9) and press A. Pre: SQUIRTBOTTLE in the
|
||||
PACK (`checkitem`). Answer YES. A lv20 SUDOWOODO wild battle starts
|
||||
(`loadwildmon SUDOWOODO, 20`). Post: `EVENT_FOUGHT_SUDOWOODO` set regardless of
|
||||
outcome; `EVENT_ROUTE_36_SUDOWOODO` cleared, sprite swapped to `SPRITE_TWIN`.
|
||||
21. Talk to `ROUTE36_FISHER` at (44, 9). Pre: `EVENT_FOUGHT_SUDOWOODO`, PACK room.
|
||||
Post: `EVENT_GOT_TM08_ROCK_SMASH`, TM08 ROCK SMASH.
|
||||
22. Optional, Thursday only: talk to `ROUTE36_ARTHUR` at (46, 6). Post:
|
||||
`EVENT_MET_ARTHUR_OF_THURSDAY`, `EVENT_GOT_HARD_STONE_FROM_ARTHUR`, HARD STONE.
|
||||
23. Continue east past x=59 on the `ROUTE_36` east connection into `MAP_VIOLET_CITY`
|
||||
(next section).
|
||||
|
||||
---
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Map geometry, warps, bg/object events for all nine maps | `src/import/RomExtractorGen2.lua` (extracted into the cache), consumed by `src/world/gen2/World.lua` / `Map.lua` | implemented (data-driven; nothing map-specific is hand written) |
|
||||
| Script bytecode for every label above (`FlowerShopTeacherScript`, `RandyScript`, `SudowoodoScript`, `Route36RockSmashGuyScript`, `ArthurScript`, both contest officers, `BugContestResultsScript`) | `src/script/gen2/Vm.lua` + extracted bytecode | implemented - the opcodes these scripts use (`checkevent`, `checkflag`, `checkitem`, `verbosegiveitem`, `givepoke`, `givepokemail`, `loadwildmon`, `variablesprite`, `appear`/`disappear`, `setscene`, `sdefer`, `askforphonenumber`, `checkcellnum`, `checktime`, `fruittree`) all have branches in `Vm.lua` / `Opcodes.lua` |
|
||||
| `VAR_WEEKDAY` (Arthur, Sunny, contest days) | `src/world/gen2/World.lua:1201` `World:weekday`, `World.lua:101` `VAR_WEEKDAY = 0x0b` | implemented |
|
||||
| `MAPCALLBACK_OBJECTS` day-of-week NPC swaps | `src/world/gen2/World.lua:5991` `World:runMapCallback`, called from `World.lua:5700` | implemented; assertion driver `tests/drivers/gold_map_callbacks.lua` exists but does **not** cover Route 36 / Route 37 / either park gate |
|
||||
| Bug Catching Contest rules, timer, park balls, scoring, AI contestants, prizes | `src/core/gen2/BugContest.lua` (815 lines; transcribed from `bug_contest/judging.asm`, `contest.asm`, `contest_2.asm`, `caught_mon.asm`, `bug_contest_mons.asm`) | implemented - `BugContest.score`, `rollContestant`, `judge`, `prizeFor`, `pickContestants`, `dropOffMons`, `returnMons`, `start/stop/tickTimer`, `chooseWild` |
|
||||
| Contest comparison / debug UI | `src/ui/gen2/ContestMenu.lua` | implemented (a comparison screen, not the cart's HUD) |
|
||||
| Contest super-tall-grass doubled encounter rate | `src/world/gen2/Permissions.lua:85` `SUPER_TALL_GRASS`, `BugContest.encounterRate` | implemented |
|
||||
| Squirtbottle field check | `src/script/gen2/CallAsm.lua:411` `H.CheckCanUseSquirtbottle` (registered as `"14:4786"`), tested in `tests/gen2_callasm_test.lua:224-244` | implemented |
|
||||
| **Using the SQUIRTBOTTLE from the PACK** | `src/world/gen2/World.lua:3294` `World:useFieldItem` | **missing** - the dispatch has arms only for `ITEMFINDER`, `BICYCLE`, `SACRED_ASH`, repels, trophy boxes and rods. There is no `SQUIRTBOTTLE` arm, so `_Squirtbottle` is unreachable from the PACK. The talk-to-the-tree path (`SudowoodoScript`'s `checkitem`) still works, which is the route the walkthrough takes |
|
||||
| **Item ball pickup** (TM04 on Route 35, TM28 DIG and PARLYZ HEAL in National Park) | extractor reads them (`src/import/RomExtractorGen2.lua:2969` `obj.itemball = readItemBall(...)`) but `src/world/gen2/World.lua:5257` `World:interact` only dispatches on `npc.def.trainer`, strength boulders, `npc.def.scriptKey`, bg events and hidden items | **missing** - `OBJECTTYPE_ITEMBALL` objects have no `scriptKey` by design, and nothing consumes `obj.itemball`. A bot cannot pick up any of this section's three item balls |
|
||||
| Hidden item (National Park FULL HEAL, Route 37 ETHER) | `src/world/gen2/HiddenItems.lua`, wired into `World:interact` | implemented |
|
||||
| Fruit trees (MYSTERYBERRY, ICE BERRY, the Route 37 apricorns) | `src/core/gen2/Apricorns.lua` + the VM's `fruittree` branch | implemented |
|
||||
| Overworld trainers, sight ranges, `loadtrainer` / `startbattle` | `src/world/gen2/Trainers.lua`, `src/battle/gen2/Battle.lua` | implemented |
|
||||
| Phone numbers and rematch tiers (Irwin, Arnie, Jack, Beverly, Alan) | `src/core/gen2/Phone.lua` (Jack, Beverly, Irwin, Arnie, Alan all have callee/caller script keys and `..._READY_FOR_REMATCH` event ids) | implemented |
|
||||
| Kenya the SPEAROW + FLOWER MAIL | `src/core/gen2/Mail.lua` + `Vm.lua:439` `givepoke` / `Vm.lua:462` `givepokemail` | implemented (extractor resolves `cmd.mail`) |
|
||||
| CUT field move + `ENGINE_HIVEBADGE` gate + the Route 35 tree block | `src/world/gen2/FieldMoves.lua:104` (`CUT = "HIVE"`), `FieldMoves.somethingToCut`, `World:tryCutOW` | implemented |
|
||||
| Sudowoodo wild battle + DRAW-flee branch | `src/script/gen2/Vm.lua:44` (DRAW), `Vm.lua:836` `loadwildmon`, `World.lua:5067` (`variablesprite` with a raw sprite id) | implemented |
|
||||
| Driver / regression coverage for this specific stretch | `tests/drivers/gold_*.lua` | **missing** - there is no driver that walks Route 35 -> National Park -> Route 36, runs a contest, or fights Sudowoodo. `tests/gen2_contest_test.lua` covers the contest rules headlessly; `tests/gen2_callasm_test.lua` covers only the Squirtbottle facing check |
|
||||
|
||||
---
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
1. **"Talk to Floria... while she goes away to get the Squirtbottle."** No such script
|
||||
exists in pokegold. `maps/GoldenrodFlowerShop.asm` has a `GOLDENRODFLOWERSHOP_FLORIA`
|
||||
object at (5, 6) whose `FlowerShopFloriaScript` only prints text, and the girl on
|
||||
Route 36 at (51, 8) is an anonymous `ROUTE36_LASS1` running `Route36LassScript`, which
|
||||
also only prints text. The SQUIRTBOTTLE comes from `FlowerShopTeacherScript` gated on
|
||||
`ENGINE_PLAINBADGE` alone, with no Route 36 precondition. The described sequence looks
|
||||
like Crystal behaviour that leaked into a Gold/Silver walkthrough.
|
||||
2. **Contest Pokemon list.** The walkthrough lists "Caterpie/Weedle, Nincada, Scyther,
|
||||
Pinsir, Volbeat, Wurmple" at "Levels 28-31". `data/wild/bug_contest_mons.asm` has
|
||||
CATERPIE, WEEDLE, METAPOD, KAKUNA, BUTTERFREE, BEEDRILL, VENONAT, PARAS, SCYTHER,
|
||||
PINSIR at levels 7-18 (plus VENOMOTH 30-40 on the `-1` terminator row, which is never
|
||||
selected). NINCADA, VOLBEAT and WURMPLE do not exist in Gen 2 at all. Treat the
|
||||
walkthrough's list and levels as wrong for this game.
|
||||
3. **"20 Sport Balls."** The item is `PARK_BALL` (`constants/item_constants.asm:185`),
|
||||
given by `special GiveParkBalls`, count `BUG_CONTEST_BALLS = 20`. "Sport Ball" is the
|
||||
Gen 3+ name.
|
||||
4. **"If you win the contest, you can win a Shiny Stone!"** First place gives `SUN_STONE`
|
||||
(`BugContestResults_FirstPlace`, `engine/events/std_scripts.asm:346`). The
|
||||
walkthrough's own item list ("Sun Stone (1st Prize)") agrees; the prose line does not.
|
||||
There is no Shiny Stone in Gen 2.
|
||||
5. **"If you catch a Scyther at full HP, it may be worth around 342 points."** Not
|
||||
verifiable from the asm without a concrete mon: `ContestScore` (`04:7cbc`) is
|
||||
`MaxHP*4 + Atk + Def + Spe + SpA + SpD + DVbonus + HP/8 + heldItem`, all taken from the
|
||||
high byte of each stat word. The walkthrough's qualitative claim ("score takes HP into
|
||||
consideration") is confirmed; the specific number is not checked here.
|
||||
6. **National Park species list.** The walkthrough lists Butterfree, Weedle, Kakuna,
|
||||
Beedrill, Paras, Venonat, Scyther and Pinsir as "found in National Park". None of them
|
||||
appear in `def_grass_wildmons NATIONAL_PARK`; they are Contest-only
|
||||
(`data/wild/bug_contest_mons.asm`). Sunkern, Caterpie, Metapod, Pidgey and Hoothoot are
|
||||
the only real National Park grass encounters in Gold.
|
||||
7. **"Routes 36 & 37" item and species lists are merged.** Everything the walkthrough
|
||||
attributes to "Routes 36 & 37" except the ICE BERRY, HARD STONE and TM08 actually lives
|
||||
on Route 37 (`RED_APRICORN`, `BLU_APRICORN`, `BLK_APRICORN`; Ledyba/Spinarak/Pidgeotto),
|
||||
which this section never enters. Vulpix/Growlithe and Stantler appear on both routes.
|
||||
8. **"Route 35 & National Park" item list omits the hidden FULL HEAL** at National Park
|
||||
bg (6, 47) and lists no hidden items at all. Not a contradiction, just incomplete.
|
||||
9. **"Head back to Arnie and talk to *her*."** `BUG_CATCHER, ARNIE1` uses the male phone
|
||||
std scripts (`AskNumber1MScript`, `RegisteredNumberMScript`). Cosmetic only.
|
||||
10. **"Then, we're ready to head into Route 35 after Liz's offer for a rematch on Route
|
||||
32."** Liz's rematch is a phone event outside this section's maps; no code on any map
|
||||
in this section references it, so it was not resolved here.
|
||||
11. **`EVENT_FOUGHT_SUDOWOODO` is set before the DRAW check** in
|
||||
`WateredWeirdTreeScript`, and both the win branch and `DidntCatchSudowoodo` run
|
||||
`disappear ROUTE36_WEIRD_TREE`. So the walkthrough's "catch it or else" is a
|
||||
preference, not a gate - fainting or fleeing still opens the road and still unlocks
|
||||
TM08. Worth confirming in-game that a *black-out* mid-battle does not leave a
|
||||
half-applied state, which the asm alone cannot answer.
|
||||
12. **`MAP_NATIONAL_PARK_BUG_CONTEST` warps 2 and 4 point at destination warp 1**, not 2,
|
||||
in `maps/NationalParkBugContest.asm:228,230`. Transcribed verbatim; a bot pathing off
|
||||
those rows should expect the asymmetry rather than assume a typo.
|
||||
@@ -0,0 +1,681 @@
|
||||
# Section 09 - Olivine City Gym
|
||||
|
||||
Source: `../section-09-olivine-city-gym.txt`
|
||||
Maps covered: `MAP_GOLDENROD_CITY` (fly stop, optional), `MAP_GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` (transit), `MAP_GOLDENROD_UNDERGROUND`, `MAP_OLIVINE_CITY`, `MAP_OLIVINE_LIGHTHOUSE_1F` .. `MAP_OLIVINE_LIGHTHOUSE_6F`, `MAP_OLIVINE_GYM`
|
||||
Badges / key milestones in this section: **MINERALBADGE** (`ENGINE_MINERALBADGE`), `EVENT_BEAT_JASMINE`, `TM_IRON_TAIL` (TM23), `EVENT_JASMINE_RETURNED_TO_GYM` (set on Lighthouse 6F, the gate for the gym even existing), the 6th-badge Rocket trigger (`GoldenrodRocketsScript`).
|
||||
|
||||
This section assumes the player already has the SECRETPOTION from Cianwood (section 8) and
|
||||
`ENGINE_STORMBADGE` (Chuck), which is what makes `FLY` usable at all.
|
||||
|
||||
---
|
||||
|
||||
## 1. Route order
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `GOLDENROD_CITY` (optional) | `maps/GoldenrodCity.asm` | Fly (`SPAWN_GOLDENROD`, `ENGINE_FLYPOINT_GOLDENROD`) | warp 15 at (11, 29) -> `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` warp 5 | Only if raising an Eevee: buy the 500 haircut for happiness |
|
||||
| 2 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | `maps/GoldenrodUndergroundSwitchRoomEntrances.asm` | warp 5 at (4, 29) / warp 6 at (5, 29) | warp 4 at (5, 25) -> `GOLDENROD_UNDERGROUND` warp 2 | Pure transit corridor to the salon |
|
||||
| 3 | `GOLDENROD_UNDERGROUND` | `maps/GoldenrodUnderground.asm` | warp 2 at (3, 34) | back out the way in, then Fly | Talk to the *older* Haircut Brother at (7, 14), pay 500, happiness bump |
|
||||
| 4 | `OLIVINE_CITY` | `maps/OlivineCity.asm` | Fly (`SPAWN_OLIVINE`, `ENGINE_FLYPOINT_OLIVINE`) | warp 9 at (29, 27) -> `OLIVINE_LIGHTHOUSE_1F` warp 1 | Head for the lighthouse |
|
||||
| 5 | `OLIVINE_LIGHTHOUSE_1F` -> `2F` -> `3F` -> `4F` -> `3F` -> `4F` -> `5F` -> `6F` | `maps/OlivineLighthouse[1-6]F.asm` | ladders + one floor pit ("fall to the left of that one lass") | same ladders/pits back down | Hand Jasmine the SECRETPOTION |
|
||||
| 6 | `OLIVINE_LIGHTHOUSE_6F` | `maps/OlivineLighthouse6F.asm` | warp 1 at (9, 15) from 5F warp 1 | warp 1 at (9, 15) back to 5F | `OlivineLighthouseJasmine` -> `EVENT_JASMINE_RETURNED_TO_GYM`, `clearevent EVENT_OLIVINE_GYM_JASMINE` |
|
||||
| 7 | `OLIVINE_CITY` | `maps/OlivineCity.asm` | `OLIVINE_LIGHTHOUSE_1F` warps 1/2 at (10, 17)/(11, 17) | warp 2 at (10, 11) -> `OLIVINE_GYM` warp 1 | Walk to the gym door |
|
||||
| 8 | `OLIVINE_GYM` | `maps/OlivineGym.asm` | warps 1/2 at (4, 15)/(5, 15) | same warps back to `OLIVINE_CITY` warp 2 | Beat Jasmine, take MINERALBADGE + TM23 |
|
||||
|
||||
Spill into the next section: the walkthrough offers "fly to Ecruteak and go through the Lake of
|
||||
Rage" as an alternative to fighting Jasmine now. `MAP_LAKE_OF_RAGE` and the Route 42/43 chain
|
||||
belong to the next section; nothing in this section's asm depends on them.
|
||||
|
||||
---
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_GOLDENROD_UNDERGROUND
|
||||
|
||||
- Script: `maps/GoldenrodUnderground.asm` (`46:4900 GoldenrodUnderground_MapEvents`, `46:4129 OlderHaircutBrotherScript`)
|
||||
- Blocks: `maps/GoldenrodUnderground.blk`
|
||||
- Header: `data/maps/maps.asm:123` -> `map GoldenrodUnderground, TILESET_GATE, DUNGEON, LANDMARK_GOLDENROD_CITY, MUSIC_UNION_CAVE, TRUE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:110` -> `map_const GOLDENROD_UNDERGROUND, 15, 18` (map group 1, the `newgroup OLIVINE` group, map id 45 - it is *not* in the Goldenrod group)
|
||||
- Attributes: `data/maps/attributes.asm:442` -> `map_attributes GoldenrodUnderground, GOLDENROD_UNDERGROUND, $00` (no connections)
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 2 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 7 |
|
||||
| 2 | 3 | 34 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 4 |
|
||||
| 3 | 18 | 6 | `GOLDENROD_UNDERGROUND` | 4 |
|
||||
| 4 | 21 | 31 | `GOLDENROD_UNDERGROUND` | 3 |
|
||||
| 5 | 22 | 31 | `GOLDENROD_UNDERGROUND` | 3 |
|
||||
| 6 | 22 | 27 | `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` | 1 |
|
||||
|
||||
**Coord events** (`def_coord_events`) - none.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 18 | 6 | `BGEVENT_READ` | `BasementDoorScript` |
|
||||
| 19 | 6 | `BGEVENT_READ` | `GoldenrodUndergroundNoEntrySign` |
|
||||
| 6 | 13 | `BGEVENT_ITEM` | `GoldenrodUndergroundHiddenParlyzHeal` (`hiddenitem PARLYZ_HEAL, EVENT_GOLDENROD_UNDERGROUND_HIDDEN_PARLYZ_HEAL`) |
|
||||
| 4 | 18 | `BGEVENT_ITEM` | `GoldenrodUndergroundHiddenSuperPotion` (`hiddenitem SUPER_POTION, EVENT_GOLDENROD_UNDERGROUND_HIDDEN_SUPER_POTION`) |
|
||||
| 17 | 8 | `BGEVENT_ITEM` | `GoldenrodUndergroundHiddenAntidote` (`hiddenitem ANTIDOTE, EVENT_GOLDENROD_UNDERGROUND_HIDDEN_ANTIDOTE`) |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `GOLDENRODUNDERGROUND_SUPER_NERD1` | `SPRITE_SUPER_NERD` | 5 | 31 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSupernerdEric` | -1 |
|
||||
| `GOLDENRODUNDERGROUND_SUPER_NERD2` | `SPRITE_SUPER_NERD` | 6 | 9 | `STANDING_UP` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerSupernerdTeru` | -1 |
|
||||
| `GOLDENRODUNDERGROUND_SUPER_NERD3` | `SPRITE_SUPER_NERD` | 3 | 27 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerPokemaniacIssac` | -1 |
|
||||
| `GOLDENRODUNDERGROUND_SUPER_NERD4` | `SPRITE_SUPER_NERD` | 2 | 6 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerPokemaniacDonald` | -1 |
|
||||
| `GOLDENRODUNDERGROUND_POKE_BALL` | `SPRITE_POKE_BALL` | 7 | 25 | `STILL` | `OBJECTTYPE_ITEMBALL` | `GoldenrodUndergroundCoinCase` (`itemball COIN_CASE`) | `EVENT_GOLDENROD_UNDERGROUND_COIN_CASE` |
|
||||
| `GOLDENRODUNDERGROUND_GRAMPS` | `SPRITE_GRAMPS` | 7 | 11 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `BargainMerchantScript` | `EVENT_GOLDENROD_UNDERGROUND_GRAMPS` |
|
||||
| `GOLDENRODUNDERGROUND_OLDER_HAIRCUT_BROTHER` | `SPRITE_SUPER_NERD` | 7 | 14 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `OlderHaircutBrotherScript` | `EVENT_GOLDENROD_UNDERGROUND_OLDER_HAIRCUT_BROTHER` |
|
||||
| `GOLDENRODUNDERGROUND_YOUNGER_HAIRCUT_BROTHER` | `SPRITE_SUPER_NERD` | 7 | 15 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `YoungerHaircutBrotherScript` | `EVENT_GOLDENROD_UNDERGROUND_YOUNGER_HAIRCUT_BROTHER` |
|
||||
| `GOLDENRODUNDERGROUND_GRANNY` | `SPRITE_GRANNY` | 7 | 21 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `BitterMerchantScript` | `EVENT_GOLDENROD_UNDERGROUND_GRANNY` |
|
||||
|
||||
Object-event visibility convention (verified in `engine/overworld/scripting.asm:879-898`): `appear`
|
||||
*clears* the flag and `disappear` *sets* it, so **an object is on the map only while its event flag
|
||||
is clear.**
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `GoldenrodUndergroundCheckDayOfWeekCallback` (`callback MAPCALLBACK_OBJECTS`): `readvar VAR_WEEKDAY`
|
||||
and appear/disappear the four merchants. Who is standing there:
|
||||
- Sunday: younger brother + granny
|
||||
- Monday: gramps only, and only during `MORN` (`checktime MORN`)
|
||||
- Tuesday / Thursday: **older brother**
|
||||
- Wednesday / Friday: younger brother
|
||||
- Saturday: older brother + granny
|
||||
|
||||
The walkthrough's "500 haircut" therefore only exists on **Tuesday, Thursday or Saturday**.
|
||||
- `OlderHaircutBrotherScript` (`46:4129`): `readvar VAR_WEEKDAY`, bails to
|
||||
`GoldenrodUndergroundScript_ShopClosed` unless TUE/THU/SAT. Then
|
||||
`checkflag ENGINE_GOLDENROD_UNDERGROUND_GOT_HAIRCUT` (one cut per day, cleared by the daily
|
||||
reset), `yesorno`, `checkmoney YOUR_MONEY, 500`, `special OlderHaircutBrother`, then
|
||||
`takemoney YOUR_MONEY, 500`. `DEF GOLDENRODUNDERGROUND_OLDER_HAIRCUT_PRICE EQU 500` is at the top
|
||||
of the file. The special returns 0/1 (cancel or Egg) or 2/3/4, which the script mirrors into
|
||||
`EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1/2/3` just to pick which of three "looks happier" lines to
|
||||
print.
|
||||
- `special OlderHaircutBrother` -> `engine/events/haircut.asm:15` -> `HaircutOrGrooming`, weighted
|
||||
roll over `HappinessData_OlderHaircutBrother` (`data/events/happiness_probabilities.asm:1`):
|
||||
30% -> `HAPPINESS_OLDERCUT1`, 50%+1 -> `HAPPINESS_OLDERCUT2`, remainder -> `HAPPINESS_OLDERCUT3`.
|
||||
The actual deltas are `data/events/happiness_changes.asm:13-15`:
|
||||
`+1/+1/+1`, `+3/+3/+1`, `+5/+5/+2` (columns are happiness <100 / <200 / otherwise).
|
||||
The younger brother (300, SUN/WED/FRI) rolls 60%+1 / 30% / rest for `+1/+1/+1`, `+3/+3/+1`,
|
||||
`+10/+10/+4` - **its jackpot row is strictly better than the older brother's**, which the
|
||||
walkthrough does not mention.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_GOLDENROD_UNDERGROUND_GOT_HAIRCUT` (id 89) | `constants/engine_flags.asm` | checked+set by both `*HaircutBrotherScript` | one haircut per day, either brother |
|
||||
| `EVENT_GOLDENROD_UNDERGROUND_OLDER_HAIRCUT_BROTHER` (0x754) | `constants/event_flags.asm:1270` | day-of-week callback | clear = older brother is standing at (7, 14) |
|
||||
| `EVENT_GOLDENROD_UNDERGROUND_YOUNGER_HAIRCUT_BROTHER` (0x755) | `constants/event_flags.asm` | day-of-week callback | clear = younger brother at (7, 15) |
|
||||
| `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1/2/3` (0x000-0x002) | `constants/event_flags.asm:5-7` | haircut scripts | scratch, do not persist |
|
||||
|
||||
**Items** - nothing the walkthrough takes here; the Coin Case itemball and the three hidden items
|
||||
belong to earlier sections.
|
||||
|
||||
**Trainers** - the four in the table above are optional and already beaten by this point in a
|
||||
linear run.
|
||||
|
||||
**Wild encounters** - none (indoor `DUNGEON`, no entry in `data/wild/johto_grass.asm`).
|
||||
|
||||
---
|
||||
|
||||
### MAP_OLIVINE_CITY
|
||||
|
||||
- Script: `maps/OlivineCity.asm` (`49:4463 OlivineCity_MapEvents`)
|
||||
- Blocks: `maps/OlivineCity.blk`
|
||||
- Header: `data/maps/maps.asm:63` -> `map OlivineCity, TILESET_JOHTO, TOWN, LANDMARK_OLIVINE_CITY, MUSIC_VIOLET_CITY, FALSE, PALETTE_AUTO, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:52` -> `map_const OLIVINE_CITY, 20, 18` (group 1, id 14)
|
||||
- Connections: `data/maps/attributes.asm:143-145` -> north `Route39` (`ROUTE_39`, offset 5), west `Route40` (`ROUTE_40`, offset 9). No south/east.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 13 | 21 | `OLIVINE_POKECENTER_1F` | 1 |
|
||||
| 2 | 10 | 11 | `OLIVINE_GYM` | 1 |
|
||||
| 3 | 25 | 11 | `OLIVINE_TIMS_HOUSE` | 1 |
|
||||
| 4 | 0 | 0 | `OLIVINE_HOUSE_BETA` | 1 (marked `; inaccessible` in the asm) |
|
||||
| 5 | 29 | 11 | `OLIVINE_PUNISHMENT_SPEECH_HOUSE` | 1 |
|
||||
| 6 | 13 | 15 | `OLIVINE_GOOD_ROD_HOUSE` | 1 |
|
||||
| 7 | 7 | 21 | `OLIVINE_CAFE` | 1 |
|
||||
| 8 | 19 | 17 | `OLIVINE_MART` | 2 |
|
||||
| 9 | 29 | 27 | `OLIVINE_LIGHTHOUSE_1F` | 1 |
|
||||
| 10 | 19 | 27 | `OLIVINE_PORT_PASSAGE` | 1 |
|
||||
| 11 | 20 | 27 | `OLIVINE_PORT_PASSAGE` | 2 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_OLIVINECITY_RIVAL_ENCOUNTER` (= 0) | 13 | 12 | `OlivineCityRivalSceneTop` | rival cutscene, ends with `setscene SCENE_OLIVINECITY_NOOP` |
|
||||
| `SCENE_OLIVINECITY_RIVAL_ENCOUNTER` (= 0) | 13 | 13 | `OlivineCityRivalSceneBottom` | same, mirrored movement |
|
||||
|
||||
Scene ids come from the `scene_script` macro's own `const_def` (`macros/scripts/maps.asm:12-36`):
|
||||
`SCENE_OLIVINECITY_RIVAL_ENCOUNTER` = 0, `SCENE_OLIVINECITY_NOOP` = 1. By this section the scene is
|
||||
already 1 (the rival scene fires on the *first* Olivine visit, section 8), so both trip-wires are
|
||||
inert and a bot may walk x=13 freely.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 17 | 11 | `BGEVENT_READ` | `OlivineCitySign` |
|
||||
| 20 | 24 | `BGEVENT_READ` | `OlivineCityPortSign` |
|
||||
| 7 | 11 | `BGEVENT_READ` | `OlivineGymSign` |
|
||||
| 30 | 28 | `BGEVENT_READ` | `OlivineLighthouseSign` |
|
||||
| 14 | 21 | `BGEVENT_READ` | `OlivineCityPokecenterSign` (`jumpstd PokecenterSignScript`) |
|
||||
| 20 | 17 | `BGEVENT_READ` | `OlivineCityMartSign` (`jumpstd MartSignScript`) |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `OLIVINECITY_SAILOR1` | `SPRITE_SAILOR` | 26 | 27 | `WALK_UP_DOWN` (0,1) | `OBJECTTYPE_SCRIPT` | `OlivineCitySailor1Script` | -1 |
|
||||
| `OLIVINECITY_STANDING_YOUNGSTER` | `SPRITE_YOUNGSTER` | 20 | 13 | `WALK_LEFT_RIGHT` (1,0) | `OBJECTTYPE_SCRIPT` | `OlivineCityStandingYoungsterScript` | -1 |
|
||||
| `OLIVINECITY_SAILOR2` | `SPRITE_SAILOR` | 17 | 21 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `OlivineCitySailor2Script` | -1 |
|
||||
| `OLIVINECITY_OLIVINE_RIVAL` | `SPRITE_OLIVINE_RIVAL` | 10 | 11 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_RIVAL_OLIVINE_CITY` |
|
||||
|
||||
Note the rival object sits **on the gym door tile** (10, 11). `InitializeEventsScript`
|
||||
(`engine/events/std_scripts.asm:520`) sets `EVENT_RIVAL_OLIVINE_CITY` at new game, so he is hidden
|
||||
except during the cutscene, and the door is walkable.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `OlivineCityFlypointCallback` (`callback MAPCALLBACK_NEWMAP`): `setflag ENGINE_FLYPOINT_OLIVINE`
|
||||
(engine flag id 70). This is what makes "fly straight back to Olivine City" legal; it was already
|
||||
set in section 8.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_FLYPOINT_OLIVINE` (id 70) | `constants/engine_flags.asm:85` | set by `OlivineCityFlypointCallback` | Olivine selectable on the Fly map |
|
||||
| `ENGINE_FLYPOINT_GOLDENROD` (id 69) | `constants/engine_flags.asm:84` | set by `maps/GoldenrodCity.asm:24` | Goldenrod selectable on the Fly map |
|
||||
| `EVENT_RIVAL_OLIVINE_CITY` | `constants/event_flags.asm` | set by `InitializeEventsScript`, `appear`/`disappear` in the scene scripts | clear = rival standing on the gym door |
|
||||
|
||||
**Items** - none taken in this section.
|
||||
|
||||
**Trainers** - none on the overworld map.
|
||||
|
||||
**Wild encounters** - `data/wild/johto_water.asm:239`:
|
||||
|
||||
```
|
||||
def_water_wildmons OLIVINE_CITY
|
||||
db 6 percent ; encounter rate
|
||||
db 20, TENTACOOL
|
||||
db 15, TENTACOOL
|
||||
db 20, TENTACRUEL
|
||||
```
|
||||
|
||||
No grass table (`data/wild/johto_grass.asm` has no `OLIVINE` entry). Fish group is
|
||||
`FISHGROUP_SHORE` per the header row.
|
||||
|
||||
---
|
||||
|
||||
### MAP_OLIVINE_LIGHTHOUSE_1F .. 6F
|
||||
|
||||
- Scripts: `maps/OlivineLighthouse1F.asm` ... `maps/OlivineLighthouse6F.asm`
|
||||
- Blocks: `maps/OlivineLighthouse[1-6]F.blk`
|
||||
- Header: `data/maps/maps.asm:112-117`. 1F-5F are
|
||||
`TILESET_LIGHTHOUSE, DUNGEON, LANDMARK_LIGHTHOUSE, MUSIC_LIGHTHOUSE, FALSE, PALETTE_DAY, FISHGROUP_SHORE`;
|
||||
**6F swaps the music to `MUSIC_VIOLET_CITY`** (line 117), which is a cheap way for a bot to
|
||||
confirm it reached the top.
|
||||
- Dimensions: `constants/map_constants.asm:99-104` -> every floor is `10, 9` blocks = 20 x 18 tiles
|
||||
(group 1, ids 34-39).
|
||||
- Attributes: `data/maps/attributes.asm:436` (6F) and neighbours - `$00`, no connections.
|
||||
|
||||
**Warps** - transcribed verbatim, all six floors.
|
||||
|
||||
`OLIVINE_LIGHTHOUSE_1F`:
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 10 | 17 | `OLIVINE_CITY` | 9 |
|
||||
| 2 | 11 | 17 | `OLIVINE_CITY` | 9 |
|
||||
| 3 | 3 | 11 | `OLIVINE_LIGHTHOUSE_2F` | 1 |
|
||||
| 4 | 16 | 13 | `OLIVINE_LIGHTHOUSE_2F` | 3 |
|
||||
| 5 | 17 | 13 | `OLIVINE_LIGHTHOUSE_2F` | 4 |
|
||||
|
||||
`OLIVINE_LIGHTHOUSE_2F`:
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 11 | `OLIVINE_LIGHTHOUSE_1F` | 3 |
|
||||
| 2 | 5 | 3 | `OLIVINE_LIGHTHOUSE_3F` | 2 |
|
||||
| 3 | 16 | 13 | `OLIVINE_LIGHTHOUSE_1F` | 4 |
|
||||
| 4 | 17 | 13 | `OLIVINE_LIGHTHOUSE_1F` | 5 |
|
||||
| 5 | 16 | 11 | `OLIVINE_LIGHTHOUSE_3F` | 4 |
|
||||
| 6 | 17 | 11 | `OLIVINE_LIGHTHOUSE_3F` | 5 |
|
||||
|
||||
`OLIVINE_LIGHTHOUSE_3F`:
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 13 | 3 | `OLIVINE_LIGHTHOUSE_4F` | 1 |
|
||||
| 2 | 5 | 3 | `OLIVINE_LIGHTHOUSE_2F` | 2 |
|
||||
| 3 | 9 | 5 | `OLIVINE_LIGHTHOUSE_4F` | 4 |
|
||||
| 4 | 16 | 11 | `OLIVINE_LIGHTHOUSE_2F` | 5 |
|
||||
| 5 | 17 | 11 | `OLIVINE_LIGHTHOUSE_2F` | 6 |
|
||||
| 6 | 16 | 9 | `OLIVINE_LIGHTHOUSE_4F` | 5 |
|
||||
| 7 | 17 | 9 | `OLIVINE_LIGHTHOUSE_4F` | 6 |
|
||||
| 8 | 8 | 3 | `OLIVINE_LIGHTHOUSE_4F` | 7 |
|
||||
| 9 | 9 | 3 | `OLIVINE_LIGHTHOUSE_4F` | 8 |
|
||||
|
||||
`OLIVINE_LIGHTHOUSE_4F`:
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 13 | 3 | `OLIVINE_LIGHTHOUSE_3F` | 1 |
|
||||
| 2 | 3 | 5 | `OLIVINE_LIGHTHOUSE_5F` | 2 |
|
||||
| 3 | 9 | 7 | `OLIVINE_LIGHTHOUSE_5F` | 3 |
|
||||
| 4 | 9 | 5 | `OLIVINE_LIGHTHOUSE_3F` | 3 |
|
||||
| 5 | 16 | 9 | `OLIVINE_LIGHTHOUSE_3F` | 6 |
|
||||
| 6 | 17 | 9 | `OLIVINE_LIGHTHOUSE_3F` | 7 |
|
||||
| 7 | 8 | 3 | `OLIVINE_LIGHTHOUSE_3F` | 8 |
|
||||
| 8 | 9 | 3 | `OLIVINE_LIGHTHOUSE_3F` | 9 |
|
||||
| 9 | 16 | 7 | `OLIVINE_LIGHTHOUSE_5F` | 4 |
|
||||
| 10 | 17 | 7 | `OLIVINE_LIGHTHOUSE_5F` | 5 |
|
||||
|
||||
`OLIVINE_LIGHTHOUSE_5F`:
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 9 | 15 | `OLIVINE_LIGHTHOUSE_6F` | 1 |
|
||||
| 2 | 3 | 5 | `OLIVINE_LIGHTHOUSE_4F` | 2 |
|
||||
| 3 | 9 | 7 | `OLIVINE_LIGHTHOUSE_4F` | 3 |
|
||||
| 4 | 16 | 7 | `OLIVINE_LIGHTHOUSE_4F` | 9 |
|
||||
| 5 | 17 | 7 | `OLIVINE_LIGHTHOUSE_4F` | 10 |
|
||||
| 6 | 16 | 5 | `OLIVINE_LIGHTHOUSE_6F` | 2 |
|
||||
| 7 | 17 | 5 | `OLIVINE_LIGHTHOUSE_6F` | 3 |
|
||||
|
||||
`OLIVINE_LIGHTHOUSE_6F`:
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 9 | 15 | `OLIVINE_LIGHTHOUSE_5F` | 1 |
|
||||
| 2 | 16 | 5 | `OLIVINE_LIGHTHOUSE_5F` | 6 |
|
||||
| 3 | 17 | 5 | `OLIVINE_LIGHTHOUSE_5F` | 7 |
|
||||
|
||||
**Which warps are ladders and which are pits.** Derived by decoding each floor's `.blk` against
|
||||
`data/tilesets/lighthouse_collision.asm`. Block `$3a` and `$31` carry `LADDER` in their bottom-right
|
||||
tile; block `$28` is `tilecoll FLOOR, FLOOR, PIT, PIT`, i.e. its two bottom tiles drop you a floor.
|
||||
|
||||
| floor | tile | block | kind | goes to |
|
||||
|---|---|---|---|---|
|
||||
| 1F | (10, 17), (11, 17) | `$2e` bottom tiles `WARP_CARPET_DOWN` | exit door | `OLIVINE_CITY` |
|
||||
| 1F | (3, 11) | `$3a` | ladder UP | 2F (3, 11) |
|
||||
| 2F | (3, 11) | `$31` | ladder DOWN | 1F (3, 11) |
|
||||
| 2F | (5, 3) | `$3a` | ladder UP | 3F (5, 3) |
|
||||
| 2F | (16, 13), (17, 13) | `$28` | **PIT** | 1F (16, 13) / (17, 13) |
|
||||
| 3F | (5, 3) | `$31` | ladder DOWN | 2F (5, 3) |
|
||||
| 3F | (13, 3) | `$3a` | ladder UP | 4F (13, 3) |
|
||||
| 3F | (9, 5) | `$3a` | ladder UP | 4F (9, 5) |
|
||||
| 3F | (16, 11), (17, 11) | `$28` | **PIT** | 2F (16, 11) / (17, 11) |
|
||||
| 4F | (13, 3) | `$31` | ladder DOWN | 3F (13, 3) |
|
||||
| 4F | (9, 5) | `$31` | ladder DOWN | 3F (9, 5) |
|
||||
| 4F | (3, 5) | `$3a` | ladder UP | 5F (3, 5) |
|
||||
| 4F | (9, 7) | `$3a` | ladder UP | 5F (9, 7) |
|
||||
| 4F | (8, 3), (9, 3) | `$28` | **PIT** | 3F (8, 3) / (9, 3) |
|
||||
| 4F | (16, 9), (17, 9) | `$28` | **PIT** | 3F (16, 9) / (17, 9) |
|
||||
| 5F | (3, 5) | `$31` | ladder DOWN | 4F (3, 5) |
|
||||
| 5F | (9, 7) | `$31` | ladder DOWN | 4F (9, 7) |
|
||||
| 5F | (9, 15) | `$3a` | ladder UP | 6F (9, 15) |
|
||||
| 5F | (16, 7), (17, 7) | `$28` | **PIT** | 4F (16, 7) / (17, 7) |
|
||||
| 6F | (9, 15) | `$31` | ladder DOWN | 5F (9, 15) |
|
||||
| 6F | (16, 5), (17, 5) | `$28` | **PIT** | 5F (16, 5) / (17, 5) |
|
||||
|
||||
The pit destination coordinates always match the pit's own coordinates on the floor below - the
|
||||
warp row on the lower floor exists purely as a landing slot and its own destination is never
|
||||
triggered (the landing tile is plain `FLOOR`).
|
||||
|
||||
**Coord events** - none on any floor.
|
||||
|
||||
**BG events** - only 5F: `bg_event 3, 13, BGEVENT_ITEM, OlivineLighthouse5FHiddenHyperPotion`.
|
||||
|
||||
**Object events** (trainers a bot will be forced into on the climb)
|
||||
|
||||
| floor | const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 1F | - | `SPRITE_SAILOR` | 8 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `OlivineLighthouse1FSailorScript` | -1 |
|
||||
| 1F | - | `SPRITE_POKEFAN_F` | 16 | 9 | `WALK_UP_DOWN` (0,2) | `OBJECTTYPE_SCRIPT` | `OlivineLighthouse1FPokefanFScript` | -1 |
|
||||
| 2F | - | `SPRITE_SAILOR` | 9 | 3 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSailorHuey` | -1 |
|
||||
| 2F | - | `SPRITE_GENTLEMAN` | 17 | 8 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerGentlemanAlfred` | -1 |
|
||||
| 3F | - | `SPRITE_SAILOR` | 9 | 2 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 1 | `TrainerSailorTerrell` | -1 |
|
||||
| 3F | - | `SPRITE_GENTLEMAN` | 13 | 5 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerGentlemanPreston` | -1 |
|
||||
| 3F | - | `SPRITE_YOUNGSTER` | 3 | 9 | `STANDING_UP` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerBirdKeeperTheo` | -1 |
|
||||
| 3F | - | `SPRITE_POKE_BALL` | 8 | 2 | `STILL` | `OBJECTTYPE_ITEMBALL` | `OlivineLighthouse3FEther` | `EVENT_OLIVINE_LIGHTHOUSE_3F_ETHER` |
|
||||
| 4F | - | `SPRITE_SAILOR` | 7 | 14 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSailorKent` | -1 |
|
||||
| 4F | - | `SPRITE_LASS` | 11 | 2 | `STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 1 | `TrainerLassConnie` | -1 |
|
||||
| 5F | - | `SPRITE_SAILOR` | 8 | 11 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSailorErnest` | -1 |
|
||||
| 5F | - | `SPRITE_YOUNGSTER` | 8 | 3 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerBirdKeeperDenis` | -1 |
|
||||
| 5F | - | `SPRITE_POKE_BALL` | 15 | 12 | `STILL` | `OBJECTTYPE_ITEMBALL` | `OlivineLighthouse5FRareCandy` | `EVENT_OLIVINE_LIGHTHOUSE_5F_RARE_CANDY` |
|
||||
| 5F | - | `SPRITE_POKE_BALL` | 6 | 15 | `STILL` | `OBJECTTYPE_ITEMBALL` | `OlivineLighthouse5FGreatBall` | `EVENT_OLIVINE_LIGHTHOUSE_5F_GREAT_BALL` |
|
||||
| 5F | - | `SPRITE_POKE_BALL` | 2 | 13 | `STILL` | `OBJECTTYPE_ITEMBALL` | `OlivineLighthouse5FTMSwagger` | `EVENT_OLIVINE_LIGHTHOUSE_5F_TM_SWAGGER` |
|
||||
|
||||
`TrainerLassConnie` at 4F (11, 2) is "that one lass". The pit at 4F (8, 3) / (9, 3) is the tile
|
||||
pair immediately left-and-below her, which matches the walkthrough's "falling to the left of that
|
||||
one lass".
|
||||
|
||||
**6F object events** (`def_object_events`, `maps/OlivineLighthouse6F.asm:270-273`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `OLIVINELIGHTHOUSE6F_JASMINE` | `SPRITE_JASMINE` | 8 | 8 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `OlivineLighthouseJasmine` | `EVENT_OLIVINE_LIGHTHOUSE_JASMINE` (0x6d2) |
|
||||
| `OLIVINELIGHTHOUSE6F_MONSTER` | `SPRITE_MONSTER` | 9 | 8 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `OlivineLighthouseAmphy` | -1 |
|
||||
| `OLIVINELIGHTHOUSE6F_POKE_BALL` | `SPRITE_POKE_BALL` | 3 | 4 | `STILL` | `OBJECTTYPE_ITEMBALL` | `OlivineLighthouse6FSuperPotion` (`itemball SUPER_POTION`) | `EVENT_OLIVINE_LIGHTHOUSE_6F_SUPER_POTION` (0x668) |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `OlivineLighthouseJasmine` (`44:6ccd`, `maps/OlivineLighthouse6F.asm:11`):
|
||||
1. `faceplayer` / `opentext`
|
||||
2. `checkitem SECRETPOTION` -> `.BroughtSecretpotion` (`44:6ce7`). Without the potion it falls
|
||||
into the `EVENT_JASMINE_EXPLAINED_AMPHYS_SICKNESS` (0x37) branch and just repeats the request.
|
||||
3. `.BroughtSecretpotion`: `writetext JasmineCureAmphyText`, `yesorno`. **`iffalse .Refused`** -
|
||||
answering No leaves everything unset and the gym stays empty, so a bot must answer Yes.
|
||||
4. `takeitem SECRETPOTION`, a long cutscene (`turnobject`, `playmusic MUSIC_HEAL`,
|
||||
`special RestartMapMusic`, `cry AMPHAROS`, `special FadeOutToWhite` / `FadeInFromWhite`).
|
||||
5. **`setevent EVENT_JASMINE_RETURNED_TO_GYM`** (0x20) and
|
||||
**`clearevent EVENT_OLIVINE_GYM_JASMINE`** (0x6d3) - this pair is the entire gate on the gym.
|
||||
6. `readvar VAR_FACING` picks one of three exit movements
|
||||
(`OlivineLighthouseJasmineLeaves{Up,Down,Right}Movement`) then `disappear OLIVINELIGHTHOUSE6F_JASMINE`.
|
||||
- `OlivineLighthouseAmphy`: cosmetic; branches on `EVENT_JASMINE_RETURNED_TO_GYM` for the healthy
|
||||
cry.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_JASMINE_RETURNED_TO_GYM` (0x20) | `constants/event_flags.asm:41` | set by `OlivineLighthouseJasmine`; read by `OlivineGymGuideScript` and `maps/EcruteakCity.asm:47` | the section's real progress bit |
|
||||
| `EVENT_OLIVINE_GYM_JASMINE` (0x6d3) | `constants/event_flags.asm:1141` | set at new game by `InitializeEventsScript` (`engine/events/std_scripts.asm:511`), cleared by `OlivineLighthouseJasmine` | clear = Jasmine is standing in her gym |
|
||||
| `EVENT_OLIVINE_LIGHTHOUSE_JASMINE` (0x6d2) | `constants/event_flags.asm:1140` | `disappear` at the end of the cure | set = she has left the lighthouse |
|
||||
| `EVENT_JASMINE_EXPLAINED_AMPHYS_SICKNESS` (0x37) | `constants/event_flags.asm:64` | `OlivineLighthouseJasmine` | first-visit text latch only |
|
||||
|
||||
**Items** - `SECRETPOTION` is consumed (`takeitem SECRETPOTION`). Nothing is given.
|
||||
|
||||
**Wild encounters** - none on any lighthouse floor (indoor `DUNGEON`, no table).
|
||||
|
||||
---
|
||||
|
||||
### MAP_OLIVINE_GYM
|
||||
|
||||
- Script: `maps/OlivineGym.asm` (`51:410e OlivineGym_MapScripts`, `51:4507 OlivineGym_MapEvents`)
|
||||
- Blocks: `maps/OlivineGym.blk` (`2b:7c7b OlivineGym_Blocks`)
|
||||
- Header: `data/maps/maps.asm:51` -> `map OlivineGym, TILESET_CHAMPIONS_ROOM, INDOOR, LANDMARK_OLIVINE_CITY, MUSIC_GYM, TRUE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:40` -> `map_const OLIVINE_GYM, 5, 8` = 10 x 16 tiles (group 1, id 2)
|
||||
- Attributes: `data/maps/attributes.asm:473` -> `map_attributes OlivineGym, OLIVINE_GYM, $00`, no connections
|
||||
- `def_scene_scripts` and `def_callbacks` are both **empty** - no scene variable, no map callbacks.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 15 | `OLIVINE_CITY` | 2 |
|
||||
| 2 | 5 | 15 | `OLIVINE_CITY` | 2 |
|
||||
|
||||
**Coord events** (`def_coord_events`) - **none**. There is no trip-wire in this gym; every
|
||||
interaction is a `faceplayer` talk.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 3 | 13 | `BGEVENT_READ` | `OlivineGymStatue` |
|
||||
| 6 | 13 | `BGEVENT_READ` | `OlivineGymStatue` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `OLIVINEGYM_JASMINE` | `SPRITE_JASMINE` | 5 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, `PAL_NPC_RED` | `OlivineGymJasmineScript` | `EVENT_OLIVINE_GYM_JASMINE` |
|
||||
| `OLIVINEGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 13 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, `PAL_NPC_RED` | `OlivineGymGuideScript` | -1 |
|
||||
|
||||
The walkthrough's "there are no apprentices of any kind" is literally true in the asm: the object
|
||||
table has exactly two rows and neither is `OBJECTTYPE_TRAINER`.
|
||||
|
||||
**Walkable shape** (decoded from `maps/OlivineGym.blk` against
|
||||
`data/tilesets/champions_room_collision.asm`, `.` = FLOOR, `#` = WALL, `W` = `WARP_CARPET_DOWN`,
|
||||
`U` = `UP_WALL`; x across 0..9, y down 0..15):
|
||||
|
||||
```
|
||||
y0 # # # # # # # # # #
|
||||
y1 # # # # # # # # # #
|
||||
y2 # # # U U U U # # #
|
||||
y3 # # # . . . . # # #
|
||||
y4 # # # . . . . # # #
|
||||
y5 # # # # . . # # # #
|
||||
y6 # # # # . . # # # #
|
||||
y7 # # # . . . . # # #
|
||||
y8 # # . . . . . . # #
|
||||
y9 # # # # . . # # # #
|
||||
y10 # # # . . . . # # #
|
||||
y11 # # # . . . . # # #
|
||||
y12 # # . # . . # . # #
|
||||
y13 # # . # . . # . # #
|
||||
y14 # . . . . . . . . #
|
||||
y15 # . . . W W . . . #
|
||||
```
|
||||
|
||||
Columns x=4 and x=5 are floor from y=14 straight up to y=3, so a bot can enter at (4, 15) or
|
||||
(5, 15) and hold UP to reach Jasmine at (5, 3) with no turns. The two statues are the wall tiles at
|
||||
(3, 13) and (6, 13); the gym guide stands at (7, 13), reachable from (7, 14).
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `OlivineGymJasmineScript` (`51:4110`, `maps/OlivineGym.asm:10`). Opcode-by-opcode:
|
||||
|
||||
```
|
||||
faceplayer / opentext
|
||||
checkevent EVENT_BEAT_JASMINE -> iftrue .FightDone
|
||||
writetext Jasmine_SteelTypeIntro / waitbutton / closetext
|
||||
winlosstext Jasmine_BetterTrainer, 0
|
||||
loadtrainer JASMINE, JASMINE1
|
||||
startbattle
|
||||
reloadmapafterbattle
|
||||
setevent EVENT_BEAT_JASMINE
|
||||
opentext / writetext Text_ReceivedMineralBadge
|
||||
playsound SFX_GET_BADGE / waitsfx
|
||||
setflag ENGINE_MINERALBADGE
|
||||
readvar VAR_BADGES
|
||||
scall OlivineGymActivateRockets
|
||||
.FightDone:
|
||||
checkevent EVENT_GOT_TM23_IRON_TAIL -> iftrue .GotIronTail
|
||||
writetext Jasmine_BadgeSpeech / promptbutton
|
||||
verbosegiveitem TM_IRON_TAIL
|
||||
iffalse .NoRoomForIronTail
|
||||
setevent EVENT_GOT_TM23_IRON_TAIL
|
||||
writetext Jasmine_IronTailSpeech / waitbutton / closetext / end
|
||||
```
|
||||
|
||||
Two bot-relevant consequences: (a) `winlosstext ..., 0` means there is **no loss-warp script**,
|
||||
losing just blacks you out normally; (b) if the bag has no room in the TM pocket,
|
||||
`verbosegiveitem` returns false and `EVENT_GOT_TM23_IRON_TAIL` is **not** set - re-talking to
|
||||
Jasmine retries the give from `.FightDone`. Keep TM room free.
|
||||
|
||||
- `OlivineGymActivateRockets` (`51:4159`): the `scall`ed tail, dispatching on the `readvar
|
||||
VAR_BADGES` value that was just pushed:
|
||||
|
||||
```
|
||||
ifequal 7, .RadioTowerRockets -> jumpstd RadioTowerRocketsScript
|
||||
ifequal 6, .GoldenrodRockets -> jumpstd GoldenrodRocketsScript
|
||||
end
|
||||
```
|
||||
|
||||
`VAR_BADGES` is `CountSetBits` over the two `wBadges` bytes
|
||||
(`engine/overworld/variables.asm:80-86`), so it counts Johto **and** Kanto badges. Since
|
||||
`setflag ENGINE_MINERALBADGE` runs first, MINERALBADGE is included in the count. On the
|
||||
walkthrough's route (Zephyr, Hive, Plain, Fog, Storm, Mineral) the count is **6** and
|
||||
`GoldenrodRocketsScript` fires: `clearevent EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER`
|
||||
(`engine/events/std_scripts.asm:251-253`), which makes the two Rocket grunts appear in Goldenrod
|
||||
City at (28, 20) and (8, 15) (`maps/GoldenrodCity.asm:376-377`). Every gym carries the same
|
||||
`scall` (`maps/VioletGym.asm:31`, `maps/MahoganyGym.asm:34`, ...), so whichever gym hands you
|
||||
badge 6 is the one that arms the Rockets - beating Jasmine *before* Chuck simply moves the
|
||||
trigger to Chuck.
|
||||
|
||||
- `OlivineGymGuideScript` (`51:4168`): three-way on `EVENT_BEAT_JASMINE`, then
|
||||
`EVENT_JASMINE_RETURNED_TO_GYM`. Text only, no flags written.
|
||||
- `OlivineGymStatue` (`51:418a`): `checkflag ENGINE_MINERALBADGE`, `jumpstd GymStatue1Script`
|
||||
before, `gettrainername STRING_BUFFER_4, JASMINE, JASMINE1` + `jumpstd GymStatue2Script` after.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_OLIVINE_GYM_JASMINE` (0x6d3) | `constants/event_flags.asm:1141` | set by `InitializeEventsScript`; cleared by `OlivineLighthouseJasmine` | **precondition**: while set, the gym is empty and unbeatable |
|
||||
| `EVENT_BEAT_JASMINE` (0x4c1) | `constants/event_flags.asm:710` | set by `OlivineGymJasmineScript` | postcondition of the battle |
|
||||
| `ENGINE_MINERALBADGE` (engine flag id 30) | `constants/engine_flags.asm:42`, `data/events/engine_flags.asm:50` (`engine_flag wJohtoBadges, MINERALBADGE`) | `setflag` here, `checkflag` in `OlivineGymStatue` | the badge bit; `MINERALBADGE` bit index in `wJohtoBadges` is `constants/ram_constants.asm:256` |
|
||||
| `EVENT_GOT_TM23_IRON_TAIL` (0x0d) | `constants/event_flags.asm:19` | `OlivineGymJasmineScript` | one-time TM guard |
|
||||
| `EVENT_JASMINE_RETURNED_TO_GYM` (0x20) | `constants/event_flags.asm:41` | read by `OlivineGymGuideScript` | flavour only inside the gym |
|
||||
| `EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER` (0x6cd) | `constants/event_flags.asm` | cleared by `GoldenrodRocketsScript` | side effect of reaching 6 badges |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `TM_IRON_TAIL` (TM23, item id `$d6`, `constants/item_constants.asm:243`) | `verbosegiveitem` after the battle | `OlivineGymJasmineScript` | `EVENT_GOT_TM23_IRON_TAIL` |
|
||||
| 3500 prize money | battle reward | see below | - |
|
||||
|
||||
Prize money check: `TrainerClassAttributes` for Jasmine (`data/trainers/attributes.asm`, the sixth
|
||||
gym-leader block) is `db HYPER_POTION, NO_ITEM ; items` and `db 25 ; base reward`.
|
||||
`ComputeTrainerReward` (`engine/battle/read_trainer_party.asm:300-317`) sets
|
||||
`wBattleReward = 25 * wCurPartyLevel` = `25 * 35` = **875**, and the payout loop in
|
||||
`engine/battle/core.asm:2340-2360` adds `wBattleReward` **four times** (`ld c, 4`). 875 x 4 =
|
||||
**3500**, which matches the walkthrough exactly. (Note Jasmine's AI holds a `HYPER_POTION`.)
|
||||
|
||||
Badge effect check: `BadgeStatBoosts` (`engine/battle/core.asm:6533-6588`) - the comment block
|
||||
states "MineralBadge: Defense", and the code deliberately swaps the PlainBadge and MineralBadge
|
||||
bits before the every-other-badge walk, boosting the stat at `wBattleMonDefense` by 1/8
|
||||
(`BoostStat`). Separately `data/types/badge_type_boosts.asm:7` maps MINERALBADGE to `STEEL` for the
|
||||
type-matching trainer-card/boost table. So the walkthrough's "raises your Pokemon's defense
|
||||
slightly" is correct.
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `JASMINE` / `JASMINE1` | `JASMINE` (trainer class 6, `constants/trainer_constants.asm:42-43`) | 1 | `JasmineGroup` (`0e:5a42`, `data/trainers/parties.asm:53`) | `OlivineGymJasmineScript` | none (gym leaders have no phone row) |
|
||||
|
||||
`JasmineGroup`, verbatim (`TRAINERTYPE_MOVES`):
|
||||
|
||||
```
|
||||
db "JASMINE@", TRAINERTYPE_MOVES
|
||||
db 30, MAGNEMITE, THUNDERBOLT, SUPERSONIC, SONICBOOM, THUNDER_WAVE
|
||||
db 30, MAGNEMITE, THUNDERBOLT, SUPERSONIC, SONICBOOM, THUNDER_WAVE
|
||||
db 35, STEELIX, SCREECH, SUNNY_DAY, ROCK_THROW, IRON_TAIL
|
||||
db -1 ; end
|
||||
```
|
||||
|
||||
EXP check against the walkthrough's numbers: trainer EXP is `base * level / 7 * 3 / 2`.
|
||||
Magnemite base exp 89 (`data/pokemon/base_stats/magnemite.asm:8`) -> `89*30/7 = 381`, `*3/2 = 571`.
|
||||
Steelix base exp 196 (`data/pokemon/base_stats/steelix.asm:8`) -> `196*35/7 = 980`, `*3/2 = 1470`.
|
||||
Both match.
|
||||
|
||||
**Wild encounters** - none (`INDOOR`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Jasmine is not in her gym | `maps/OlivineGym.asm:212` object row, event flag `EVENT_OLIVINE_GYM_JASMINE`; visibility semantics in `engine/overworld/scripting.asm:879-898` | flag must be **clear** | `clearevent EVENT_OLIVINE_GYM_JASMINE` at `maps/OlivineLighthouse6F.asm:69` |
|
||||
| Jasmine will not leave the lighthouse | `maps/OlivineLighthouse6F.asm:14` `checkitem SECRETPOTION` | `SECRETPOTION` in the bag (from the Cianwood Pharmacy, section 8) | hand it over and answer **Yes** to `JasmineCureAmphyText` (`yesorno` at line 29; `iffalse .Refused` aborts the whole scene) |
|
||||
| Cannot Fly to Olivine / Goldenrod | `engine/events/overworld.asm:544-547` (`FlyFunction.TryFly` -> `ld de, ENGINE_STORMBADGE` / `call CheckBadge`), mirrored in the port at `src/world/gen2/FieldMoves.lua:107` (`FLY = "STORM"`), plus `ENGINE_FLYPOINT_OLIVINE` (id 70) / `ENGINE_FLYPOINT_GOLDENROD` (id 69) set by each town's `MAPCALLBACK_NEWMAP` (`maps/OlivineCity.asm:22`, `maps/GoldenrodCity.asm:24`) | `ENGINE_STORMBADGE` + having visited the town on foot | both already true entering this section |
|
||||
| Haircut unavailable | `OlderHaircutBrotherScript` `readvar VAR_WEEKDAY` + `GoldenrodUndergroundCheckDayOfWeekCallback` | in-game weekday must be TUE/THU/SAT for the 500 brother | wait for the day, or use the younger brother SUN/WED/FRI for 300 |
|
||||
| Second haircut same day | `checkflag ENGINE_GOLDENROD_UNDERGROUND_GOT_HAIRCUT` (engine flag 89) | flag clear | daily reset |
|
||||
| 4F of the lighthouse is a dead end from the (13, 3) ladder | geometry: 4F pit at (8, 3)/(9, 3), block `$28` in `maps/OlivineLighthouse4F.blk`, warps 7/8 | must step into the pit and re-climb via 3F warp 3 at (9, 5) | no flag, purely navigational |
|
||||
| No TM pocket room | `verbosegiveitem TM_IRON_TAIL` -> `iffalse .NoRoomForIronTail` (`maps/OlivineGym.asm:35-36`) | free TM slot | drop a TM and re-talk to Jasmine |
|
||||
|
||||
Nothing in this section requires `CUT`, `SURF`, `STRENGTH`, `WHIRLPOOL`, `WATERFALL` or `FLASH`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
Optional Eevee-happiness detour (steps 1-5) can be skipped entirely.
|
||||
|
||||
1. **Fly** to `SPAWN_GOLDENROD`. Precondition: `ENGINE_STORMBADGE` set and `ENGINE_FLYPOINT_GOLDENROD` (id 69) set. Postcondition: on `GOLDENROD_CITY`.
|
||||
2. `GOLDENROD_CITY`: walk to (11, 29), step onto warp 15. Postcondition: on `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES` at warp 5 (4, 29).
|
||||
3. `GOLDENROD_UNDERGROUND_SWITCH_ROOM_ENTRANCES`: walk to (5, 25), step onto warp 4. Postcondition: on `GOLDENROD_UNDERGROUND` at warp 2 (3, 34).
|
||||
4. `GOLDENROD_UNDERGROUND`: precondition `VAR_WEEKDAY in {TUESDAY, THURSDAY, SATURDAY}` and `ENGINE_GOLDENROD_UNDERGROUND_GOT_HAIRCUT` clear and money >= 500. Walk to (6, 14) and face RIGHT (the brother stands at (7, 14) facing left); talk. Answer **Yes**, pick the Eevee. Postcondition: `ENGINE_GOLDENROD_UNDERGROUND_GOT_HAIRCUT` set, money -500, Eevee happiness +1/+3/+5.
|
||||
5. Retrace warps out (warp 2 at (3, 34) -> switch-room warp 5/6 at (4, 29)/(5, 29) -> `GOLDENROD_CITY`).
|
||||
6. **Fly** to `SPAWN_OLIVINE`. Precondition: `ENGINE_FLYPOINT_OLIVINE` (id 70). Postcondition: on `OLIVINE_CITY`.
|
||||
7. `OLIVINE_CITY`: walk to (29, 27), step onto warp 9. Postcondition: on `OLIVINE_LIGHTHOUSE_1F` at warp 1 (10, 17).
|
||||
8. `1F`: walk to (3, 11), step on ladder-up. -> `2F` (3, 11).
|
||||
9. `2F`: walk to (5, 3), ladder-up. -> `3F` (5, 3). (Sailor Huey (9, 3) and Gentleman Alfred (17, 8) are already beaten; their `EVENT_BEAT_*` flags guard re-battle.)
|
||||
10. `3F`: walk to (13, 3), ladder-up. -> `4F` (13, 3).
|
||||
11. `4F`: walk to the pit at (8, 3) or (9, 3) - "left of Lass Connie at (11, 2)". Step in. -> `3F` (8, 3)/(9, 3).
|
||||
12. `3F`: walk to (9, 5), ladder-up. -> `4F` (9, 5).
|
||||
13. `4F`: walk to (9, 7), ladder-up. -> `5F` (9, 7). (Alternative: 4F (3, 5) -> 5F (3, 5).)
|
||||
14. `5F`: walk to (9, 15), ladder-up. -> `6F` (9, 15). Confirm arrival by `MUSIC_VIOLET_CITY` playing (6F is the only lighthouse floor with it).
|
||||
15. `6F`: precondition `SECRETPOTION` in bag and `EVENT_OLIVINE_LIGHTHOUSE_JASMINE` clear. Walk to (8, 9) and face UP (Jasmine at (8, 8) faces down) and talk. Answer **Yes** to `JasmineCureAmphyText`. Sit through ~120 frames of cutscene (`pause 60`, `pause 15`, several `pause 10`). Postcondition: `SECRETPOTION` removed, `EVENT_JASMINE_RETURNED_TO_GYM` set, `EVENT_OLIVINE_GYM_JASMINE` **cleared**, `EVENT_OLIVINE_LIGHTHOUSE_JASMINE` set.
|
||||
16. Descend: `6F` pit at (16, 5)/(17, 5) -> `5F`; `5F` pit at (16, 7)/(17, 7) -> `4F`; `4F` pit at (16, 9)/(17, 9) -> `3F`; `3F` pit at (16, 11)/(17, 11) -> `2F`; `2F` pit at (16, 13)/(17, 13) -> `1F`. Then `1F` (10, 17)/(11, 17) facing DOWN -> `OLIVINE_CITY` warp 9 at (29, 27).
|
||||
17. `OLIVINE_CITY`: (optional) heal at the Pokecenter, warp 1 at (13, 21). Save before the gym.
|
||||
18. `OLIVINE_CITY`: walk to (10, 11), step onto warp 2. Postcondition: on `OLIVINE_GYM` at warp 1 (4, 15).
|
||||
19. `OLIVINE_GYM`: (optional) talk to the guide by walking to (7, 14) and facing UP.
|
||||
20. `OLIVINE_GYM`: hold UP along x=5 from (5, 14) to (5, 4), face UP, talk to Jasmine at (5, 3). Precondition: `EVENT_OLIVINE_GYM_JASMINE` clear, `EVENT_BEAT_JASMINE` clear, a free TM slot. Battle `loadtrainer JASMINE, JASMINE1`.
|
||||
21. Win. Postconditions in order: `EVENT_BEAT_JASMINE` set -> `ENGINE_MINERALBADGE` set -> money +3500 -> `VAR_BADGES` re-read -> if it equals 6, `EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER` cleared (Rockets appear in Goldenrod) -> `verbosegiveitem TM_IRON_TAIL` -> `EVENT_GOT_TM23_IRON_TAIL` set.
|
||||
22. Leave via (4, 15)/(5, 15) facing DOWN -> `OLIVINE_CITY` warp 2 (10, 11).
|
||||
|
||||
Battle notes for the driver: lead a Fire or Fighting attacker; both Magnemite carry
|
||||
`THUNDER_WAVE` (paralysis risk) and `SONICBOOM` (fixed 20 damage, ignores type - it will connect on
|
||||
a Ground type); Steelix has `IRON_TAIL` and `SUNNY_DAY` (which boosts your own Fire moves too).
|
||||
|
||||
---
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Map headers, blocks, warps, coord/bg/object events for all of these maps | `src/import/RomExtractorGen2.lua` (`readItemBall` ~line 2875, object/trainer struct decode ~line 2961) | implemented (data-driven from ROM, no hand-ported Olivine map) |
|
||||
| Script opcodes used by `OlivineGymJasmineScript` (`faceplayer`, `checkevent`, `winlosstext`, `loadtrainer`, `startbattle`, `reloadmapafterbattle`, `setflag`, `readvar`, `scall`, `verbosegiveitem`, `jumpstd`, `gettrainername`) | `src/script/gen2/Vm.lua` (`faceplayer` L164, `gettrainername` L407, `verbosegiveitem` L490, `jumpstd` L742, `loadtrainer` L806, `winlosstext` L918); opcode table `src/script/gen2/Opcodes.lua` | implemented |
|
||||
| `VAR_BADGES` (`CountSetBits` over Johto+Kanto) | `src/world/gen2/World.lua:1240-1245` | implemented |
|
||||
| Fly gate + flypoint engine flags | `src/world/gen2/FieldMoves.lua:103-130`, `FLYPOINTS` L340-368 (`OLIVINE` flag 70, `GOLDENROD` flag 69) | implemented |
|
||||
| Pit / warp-carpet collision (the lighthouse climb) | `src/world/gen2/Permissions.lua:162-180` (`COLL_PIT` 0x60 / 0x68 and the `$7x` warp nybble; carpet direction table) | implemented |
|
||||
| Haircut brothers (weighted roll + happiness deltas) | `src/script/gen2/Specials.lua:1577-1615` (`HAIRCUT_TABLES`), `src/core/gen2/Happiness.lua:79-84` | implemented |
|
||||
| Trainer prize money incl. the x4 payout loop | `src/battle/gen2/Prize.lua` (header comment cites `ld c, 4`) | implemented |
|
||||
| Gym-leader battle music for `JASMINE` | `src/battle/gen2/BattleMusic.lua:25`, `src/battle/gen2/Battle.lua:86` | implemented |
|
||||
| Eevee -> Espeon/Umbreon (`EVOLVE_HAPPINESS`, `HAPPINESS_TO_EVOLVE` 220, `TR_MORNDAY`/`TR_NITE`) | `src/core/gen2/Evolution.lua:40-52,143` | implemented |
|
||||
| Trainer card badge display | `src/ui/gen2/TrainerCard.lua`; leader palette list at `src/import/RomExtractorGen2.lua:4982` | implemented |
|
||||
| **`BadgeStatBoosts`** - the MINERALBADGE +1/8 Defense (and the PlainBadge/MineralBadge bit swap) | nothing found. `grep -i badge src/battle/gen2/` returns no hits; the only badge-boost code in the repo is Gen 1's (`src/battle/Damage.lua:24`, `src/battle/BattleState.lua:439`) | **missing** |
|
||||
| **`BadgeTypeBoosts`** (`data/types/badge_type_boosts.asm`, MINERALBADGE -> STEEL) | no hits for `BadgeTypeBoosts` anywhere in `src/` | **missing** |
|
||||
| A driver that walks the lighthouse or fights a gym leader | `tests/drivers/gold_*.lua` - `gold_trainer_smoke.lua` and `gold_battle_smoke.lua` exist but neither names Olivine or Jasmine | **missing** (no section-9 driver) |
|
||||
|
||||
---
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
- **"Climb up the Olivine Lighthouse in the EXACT same way you came up."** The ladder/pit graph in
|
||||
section 2 is fully decoded from the `.blk` files and `data/tilesets/lighthouse_collision.asm`, but
|
||||
I did **not** flood-fill each floor to prove the specific walking path between a ladder and the
|
||||
next pit. Steps 8-14 of the checklist are the geometrically obvious route (and the pit at 4F
|
||||
(8, 3)/(9, 3) really is left of Lass Connie at (11, 2)), but the intermediate tile-by-tile route
|
||||
on 3F, 4F and 5F should be confirmed in-game before a driver hard-codes it.
|
||||
- **"Talk to the man who is running the second/middle shop."** The asm has four merchant objects in
|
||||
one column at x=7: gramps y=11, older brother y=14, younger brother y=15, granny y=21, and
|
||||
`GoldenrodUndergroundCheckDayOfWeekCallback` hides most of them on any given day. "Middle" is
|
||||
therefore day-dependent; the unambiguous identifier is the **500 price**
|
||||
(`GOLDENRODUNDERGROUND_OLDER_HAIRCUT_PRICE`), i.e. the older brother at (7, 14), available
|
||||
TUE/THU/SAT only. The walkthrough does not mention the weekday restriction at all.
|
||||
- **"Eevee's happiness meter should get a nice boost."** The older brother's roll is +1 (30%),
|
||||
+3 (50%) or +5 (20%) at happiness < 200. The younger brother's top roll is +10. The walkthrough's
|
||||
implied "500 is the better option" is not supported by
|
||||
`data/events/happiness_probabilities.asm` / `data/events/happiness_changes.asm`.
|
||||
- **"Say Yes to the fact that Ampharos will be cured by the medicine."** Confirmed as a hard
|
||||
requirement (`yesorno` / `iffalse .Refused` at `maps/OlivineLighthouse6F.asm:29-30`), but note the
|
||||
refusal path is *recoverable*: it sets nothing, so the player can simply talk again.
|
||||
- **EXP values (571 / 1470)** are reproduced by the standard trainer formula from the base-exp bytes
|
||||
I read, but I did not open the EXP-award routine itself to confirm the `*3/2` trainer multiplier
|
||||
in this disassembly; treat those two numbers as arithmetic agreement rather than a code citation.
|
||||
- **`data/trainers/attributes.asm` Jasmine row**: the file has no per-class labels, only ordered
|
||||
comment headers. I identified Jasmine's row by the `; Jasmine` comment (sixth block, `db 25 ;
|
||||
base reward`, `db HYPER_POTION, NO_ITEM`). If that comment ever drifts from the class order in
|
||||
`constants/trainer_constants.asm`, the 3500 derivation drifts with it.
|
||||
- The walkthrough's "you can go to the Lake of Rage first" branch is out of scope here; nothing in
|
||||
the Olivine Gym asm checks for it.
|
||||
@@ -0,0 +1,851 @@
|
||||
# Section 11 - Team Rocket Hideout and Mahogany Town Gym
|
||||
|
||||
Source: `../section-11-team-rocket-hideout-and-mahogany-town-gym.txt`
|
||||
(the FAQ's own heading is "17 > Team Rocket Hideout and Mahogany Town Gym")
|
||||
|
||||
Maps covered: `MAP_MAHOGANY_TOWN`, `MAP_MAHOGANY_POKECENTER_1F`, `MAP_MAHOGANY_MART_1F`,
|
||||
`MAP_TEAM_ROCKET_BASE_B1F`, `MAP_TEAM_ROCKET_BASE_B2F`, `MAP_TEAM_ROCKET_BASE_B3F`,
|
||||
`MAP_MAHOGANY_GYM`
|
||||
|
||||
Badges / key milestones in this section:
|
||||
|
||||
- `EVENT_UNCOVERED_STAIRCASE_IN_MAHOGANY_MART` - the hideout entrance exists
|
||||
- `EVENT_LEARNED_SLOWPOKETAIL`, `EVENT_LEARNED_RATICATE_TAIL` - Giovanni's-office door
|
||||
- `EVENT_LEARNED_HAIL_GIOVANNI` - transmitter-room door
|
||||
- `EVENT_GOT_HM06_WHIRLPOOL` (HM06 = `HM_WHIRLPOOL`)
|
||||
- `EVENT_CLEARED_ROCKET_HIDEOUT` + `clearflag ENGINE_ROCKET_SIGNAL_ON_CH20`
|
||||
- `ENGINE_GLACIERBADGE` (GLACIERBADGE, badge 7) + `EVENT_GOT_TM16_ICY_WIND` (TM16 = `TM_ICY_WIND`)
|
||||
|
||||
Entry precondition, set in the previous section: `maps/LakeOfRage.asm`
|
||||
`LakeOfRageLanceScript.AgreedToHelp` runs
|
||||
`clearevent EVENT_MAHOGANY_MART_LANCE_AND_DRAGONITE`,
|
||||
`setevent EVENT_DECIDED_TO_HELP_LANCE`,
|
||||
`setmapscene MAHOGANY_MART_1F, SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS`.
|
||||
Without that scene set, the mart is an ordinary shop and there is no staircase.
|
||||
|
||||
---
|
||||
|
||||
## 1. Route order
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `MAP_MAHOGANY_TOWN` | `maps/MahoganyTown.asm` | from Route 43 gate (warp 5, `9,1`) or fly point `ENGINE_FLYPOINT_MAHOGANY` | warp 4 `15,13` -> Pokecenter, warp 1 `11,7` -> Mart | "Heal at the Pokemon Center and withdraw your Gyarados" |
|
||||
| 2 | `MAP_MAHOGANY_POKECENTER_1F` | `maps/MahoganyPokecenter1F.asm` | warp 1/2 `3,7` / `4,7` | same | heal / PC withdraw |
|
||||
| 3 | `MAP_MAHOGANY_MART_1F` | `maps/MahoganyMart1F.asm` | warp 1/2 `3,7` / `4,7` | warp 3 `7,3` | Lance's Dragonite Hyper Beams the shop open, uncovers the staircase |
|
||||
| 4 | `MAP_TEAM_ROCKET_BASE_B1F` | `maps/TeamRocketBaseB1F.asm` | warp 1 `27,2` | warp 2 `3,14` | security cameras, GruntM16, Scientist Jed, secret switch, 3 item balls |
|
||||
| 5 | `MAP_TEAM_ROCKET_BASE_B2F` (bottom region) | `maps/TeamRocketBaseB2F.asm` | warp 1 `3,14` | warp 5 `27,14` | Lance heals you; GruntM19 then GruntM17 |
|
||||
| 6 | `MAP_TEAM_ROCKET_BASE_B3F` (right region) | `maps/TeamRocketBaseB3F.asm` | warp 4 `27,14` | warp 2 `27,2` | Lance's two-password speech, 4 item balls, Ross / GruntF5 / Mitch / GruntM28, both passwords |
|
||||
| 7 | `MAP_TEAM_ROCKET_BASE_B2F` (top region) | `maps/TeamRocketBaseB2F.asm` | warp 3 `27,2` | warp 2 `3,2` | walk left past GruntM18 |
|
||||
| 8 | `MAP_TEAM_ROCKET_BASE_B3F` (left region) | `maps/TeamRocketBaseB3F.asm` | warp 1 `3,2` | warp 3 `3,6` | rival cutscene, password door, ExecutiveM 4, Murkrow gives HAIL GIOVANNI |
|
||||
| 9 | `MAP_TEAM_ROCKET_BASE_B2F` (TM room) | `maps/TeamRocketBaseB2F.asm` | warp 4 `3,6` | warp 4 `3,6` | "the second stairs that you did NOT come out of" - TM46 Thief at `3,10` |
|
||||
| 10 | back through 8 -> 7 -> 6 -> 5 | | | | "Head back the way you came, past the spinning Team Rocket member" |
|
||||
| 11 | `MAP_TEAM_ROCKET_BASE_B2F` (transmitter room) | `maps/TeamRocketBaseB2F.asm` | password door at `14,12` / `15,12` | warp 1 `3,14` (then B1F warp panel `5,15`) | ExecutiveF 2 ambush, 3 Electrode, HM06 Whirlpool from Lance |
|
||||
| 12 | `MAP_MAHOGANY_TOWN` | `maps/MahoganyTown.asm` | Mart warps 1/2 | warp 3 `6,13` | heal, then the gym |
|
||||
| 13 | `MAP_MAHOGANY_GYM` | `maps/MahoganyGym.asm` | warp 1/2 `4,17` / `5,17` | same | five ice-puzzle trainers, then Pryce -> GLACIERBADGE + TM16 |
|
||||
|
||||
Spill into the next section: the last paragraph ("You can now use Whirlpool, once you
|
||||
find that in a dark cavern") points at `MAP_ICE_PATH_1F` / `MAP_ICE_PATH_B2F_MAHOGANY_SIDE`.
|
||||
Not covered here.
|
||||
|
||||
---
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_MAHOGANY_TOWN
|
||||
|
||||
- Script: `maps/MahoganyTown.asm`
|
||||
- Blocks: `maps/MahoganyTown.blk`
|
||||
- Header: `data/maps/maps.asm:74` -> `map MahoganyTown, TILESET_JOHTO, TOWN, LANDMARK_MAHOGANY_TOWN, MUSIC_CHERRYGROVE_CITY, FALSE, PALETTE_AUTO, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm` -> `map_const MAHOGANY_TOWN, 10, 9` (group `MAHOGANY` = 2, map id 7); 10x9 blocks = 20x18 map cells
|
||||
- Connections: `data/maps/attributes.asm:152-155` -> north `Route43`, west `Route42`, east `Route44`
|
||||
- Scene ids (declared inline by `scene_script`): `SCENE_MAHOGANYTOWN_TRY_RAGECANDYBAR` = 0, `SCENE_MAHOGANYTOWN_NOOP` = 1
|
||||
- Callback: `MAPCALLBACK_NEWMAP, MahoganyTownFlypointCallback` -> `setflag ENGINE_FLYPOINT_MAHOGANY`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 11 | 7 | `MAHOGANY_MART_1F` | 1 |
|
||||
| 2 | 17 | 7 | `MAHOGANY_RED_GYARADOS_SPEECH_HOUSE` | 1 |
|
||||
| 3 | 6 | 13 | `MAHOGANY_GYM` | 1 |
|
||||
| 4 | 15 | 13 | `MAHOGANY_POKECENTER_1F` | 1 |
|
||||
| 5 | 9 | 1 | `ROUTE_43_MAHOGANY_GATE` | 3 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_MAHOGANYTOWN_TRY_RAGECANDYBAR` (0) | 19 | 8 | `MahoganyTownTryARageCandyBarScript` | merchant steps in front of you and pitches a RAGECANDYBAR |
|
||||
| `SCENE_MAHOGANYTOWN_TRY_RAGECANDYBAR` (0) | 19 | 9 | `MahoganyTownTryARageCandyBarScript` | same |
|
||||
|
||||
The script never calls `setscene`, so the pitch re-fires every time you cross x=19
|
||||
until `RadioTowerRocketsScript` (`engine/events/std_scripts.asm:263`) runs
|
||||
`setmapscene MAHOGANY_TOWN, SCENE_MAHOGANYTOWN_NOOP`.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 1 | 5 | `BGEVENT_READ` | `MahoganyTownSign` |
|
||||
| 9 | 7 | `BGEVENT_READ` | `MahoganyTownRagecandybarSign` |
|
||||
| 3 | 13 | `BGEVENT_READ` | `MahoganyGymSign` |
|
||||
| 16 | 13 | `BGEVENT_READ` | `MahoganyTownPokecenterSign` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `MAHOGANYTOWN_POKEFAN_M` | `SPRITE_POKEFAN_M` | 19 | 8 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `MahoganyTownPokefanMScript` | `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_EAST` |
|
||||
| `MAHOGANYTOWN_GRAMPS` | `SPRITE_GRAMPS` | 6 | 9 | `WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT` | `MahoganyTownGrampsScript` | -1 |
|
||||
| `MAHOGANYTOWN_FISHER` | `SPRITE_FISHER` | 6 | 14 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `MahoganyTownFisherScript` | `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM` |
|
||||
| `MAHOGANYTOWN_LASS` | `SPRITE_LASS` | 12 | 8 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `MahoganyTownLassScript` | `EVENT_MAHOGANY_MART_OWNERS` |
|
||||
|
||||
Object-event flag polarity (verified in `engine/overworld/map_objects_2.asm`
|
||||
`CheckObjectFlag`): the flag being **set** masks (hides) the object; `-1` means
|
||||
always visible. So `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM` clear = the fisher
|
||||
stands on `6,14`, one cell south of the gym warp at `6,13`, blocking it. That flag
|
||||
is set by `RocketBaseElectrodeScript` at the end of the hideout, which is the real
|
||||
gate on the Mahogany gym (see section 3).
|
||||
|
||||
### MAP_MAHOGANY_POKECENTER_1F
|
||||
|
||||
- Script: `maps/MahoganyPokecenter1F.asm`
|
||||
- Header: `data/maps/maps.asm:70` -> `TILESET_POKECENTER, INDOOR, LANDMARK_MAHOGANY_TOWN, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `map_const MAHOGANY_POKECENTER_1F, 5, 4` (group 2, map id 3)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 7 | `MAHOGANY_TOWN` | 4 |
|
||||
| 2 | 4 | 7 | `MAHOGANY_TOWN` | 4 |
|
||||
| 3 | 0 | 7 | `POKECENTER_2F` | 1 |
|
||||
|
||||
No coord events, no bg events. Nurse is `MAHOGANYPOKECENTER1F_NURSE`
|
||||
`SPRITE_NURSE` at `3,1`, script `MahoganyPokecenter1FNurseScript` ->
|
||||
`jumpstd PokecenterNurseScript`.
|
||||
|
||||
### MAP_MAHOGANY_MART_1F
|
||||
|
||||
- Script: `maps/MahoganyMart1F.asm` (`45:4000 MahoganyMart1F_MapScripts`)
|
||||
- Blocks: shares `maps/GiftShop.blk` (`data/maps/blocks.asm:700` -> `MahoganyMart1F_Blocks: MountMoonGiftShop_Blocks: INCBIN "maps/GiftShop.blk"`)
|
||||
- Header: `data/maps/maps.asm:118` -> `TILESET_TRADITIONAL_HOUSE, INDOOR, LANDMARK_MAHOGANY_TOWN, MUSIC_MAHOGANY_MART, TRUE (no phone), PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `map_const MAHOGANY_MART_1F, 4, 4` (group `DUNGEONS` = 3, map id 40)
|
||||
- Scene ids: `SCENE_MAHOGANYMART1F_NOOP` = 0, `SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS` = 1
|
||||
- Callback: `MAPCALLBACK_TILES, MahoganyMart1FStaircaseCallback` - if `EVENT_UNCOVERED_STAIRCASE_IN_MAHOGANY_MART` is set, `changeblock 6, 2, $1e ; stairs`
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 7 | `MAHOGANY_TOWN` | 1 |
|
||||
| 2 | 4 | 7 | `MAHOGANY_TOWN` | 1 |
|
||||
| 3 | 7 | 3 | `TEAM_ROCKET_BASE_B1F` | 1 |
|
||||
|
||||
`def_coord_events` and `def_bg_events` are both empty.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `MAHOGANYMART1F_PHARMACIST` | `SPRITE_PHARMACIST` | 4 | 3 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `MahoganyMart1FPharmacistScript` | `EVENT_TEAM_ROCKET_BASE_POPULATION` |
|
||||
| `MAHOGANYMART1F_BLACK_BELT` | `SPRITE_BLACK_BELT` | 1 | 6 | `SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `MahoganyMart1FBlackBeltScript` | `EVENT_TEAM_ROCKET_BASE_POPULATION` |
|
||||
| `MAHOGANYMART1F_LANCE` | `SPRITE_LANCE` | 4 | 6 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_MAHOGANY_MART_LANCE_AND_DRAGONITE` |
|
||||
| `MAHOGANYMART1F_DRAGONITE` | `SPRITE_DRAGON` | 3 | 6 | `STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_MAHOGANY_MART_LANCE_AND_DRAGONITE` |
|
||||
| `MAHOGANYMART1F_GRANNY` | `SPRITE_GRANNY` | 1 | 3 | `STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `MahoganyMart1FGrannyScript` | `EVENT_MAHOGANY_MART_OWNERS` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
`MahoganyMart1FLanceUncoversStaircaseScript` (`45:4046`), reached as
|
||||
`scene_script ... SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS` via `sdefer`, so it
|
||||
fires on map entry without any player input:
|
||||
|
||||
1. Dragonite `big_step LEFT / big_step RIGHT` into the Black Belt, `disappear MAHOGANYMART1F_DRAGONITE`
|
||||
2. Lance walks over (`MahoganyMart1FLanceApproachPlayerMovement`), `follow MAHOGANYMART1F_LANCE, PLAYER`, shoves the pharmacist aside
|
||||
3. `changeblock 6, 2, $1e` + `refreshmap` + `setevent EVENT_UNCOVERED_STAIRCASE_IN_MAHOGANY_MART`
|
||||
4. `disappear MAHOGANYMART1F_LANCE`, `setscene SCENE_MAHOGANYMART1F_NOOP`
|
||||
|
||||
`changeblock`'s operands are **map cell coordinates**, not block coordinates
|
||||
(`Script_changeblock` at `engine/overworld/scripting.asm:2031` adds 4 to each and
|
||||
calls `GetBlockLocation`, which halves them - `home/map.asm:2099`). `6,2` is
|
||||
therefore block (3,1), covering cells (6..7, 2..3); block `$1e` in
|
||||
`data/tilesets/traditional_house_collision.asm` is `FLOOR, FLOOR, FLOOR, LADDER`,
|
||||
putting the LADDER on cell `7,3` - exactly the warp row above.
|
||||
|
||||
Marts: `MahoganyMart1FPharmacistScript` -> `pokemart MARTTYPE_STANDARD, MART_MAHOGANY_1`
|
||||
(closed once `EVENT_DECIDED_TO_HELP_LANCE` is set),
|
||||
`MahoganyMart1FGrannyScript` -> `pokemart MARTTYPE_STANDARD, MART_MAHOGANY_2`.
|
||||
|
||||
### MAP_TEAM_ROCKET_BASE_B1F
|
||||
|
||||
- Script: `maps/TeamRocketBaseB1F.asm` (`45:430c TeamRocketBaseB1F_MapScripts`)
|
||||
- Blocks: `maps/TeamRocketBaseB1F.blk`
|
||||
- Header: `data/maps/maps.asm:119` -> `TILESET_UNDERGROUND, DUNGEON, LANDMARK_MAHOGANY_TOWN, MUSIC_ROCKET_HIDEOUT, TRUE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `map_const TEAM_ROCKET_BASE_B1F, 15, 9` (group 3, map id 41) = 30x18 cells
|
||||
- Scene ids: `SCENE_TEAMROCKETBASEB1F_TRAPS` = 0 (the only one)
|
||||
- Callback: `MAPCALLBACK_OBJECTS, TeamRocketBaseB1FHideSecurityGruntCallback` -> `disappear TEAMROCKETBASEB1F_ROCKET1` (the camera grunt is parked off-screen at `0,0` and teleported in by `moveobject`)
|
||||
|
||||
The whole floor is one connected walkable region (flood-filled from
|
||||
`maps/TeamRocketBaseB1F.blk` against `data/tilesets/underground_collision.asm`).
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 27 | 2 | `MAHOGANY_MART_1F` | 3 |
|
||||
| 2 | 3 | 14 | `TEAM_ROCKET_BASE_B2F` | 1 |
|
||||
| 3 | 5 | 15 | `TEAM_ROCKET_BASE_B1F` | 4 |
|
||||
| 4 | 25 | 2 | `TEAM_ROCKET_BASE_B1F` | 3 |
|
||||
|
||||
Warps 3 and 4 are the warp panel pair Scientist Jed describes: cell `5,15` has
|
||||
collision `WARP_PANEL` and dumps you at `25,2`, two cells from the exit ladder.
|
||||
|
||||
**Coord events** (`def_coord_events`) - all on scene `SCENE_TEAMROCKETBASEB1F_TRAPS` (0)
|
||||
|
||||
Security cameras (each triggers `GRUNTM_20` then `GRUNTM_21`):
|
||||
|
||||
| x | y | script label | guarding statue (bg_event) |
|
||||
|---|---|---|---|
|
||||
| 24 | 2 | `SecurityCamera1a` | `24,1` |
|
||||
| 24 | 3 | `SecurityCamera1b` | `24,1` |
|
||||
| 6 | 2 | `SecurityCamera2a` | `6,1` |
|
||||
| 6 | 3 | `SecurityCamera2b` | `6,1` |
|
||||
| 24 | 6 | `SecurityCamera3a` | `24,5` |
|
||||
| 24 | 7 | `SecurityCamera3b` | `24,5` |
|
||||
| 22 | 16 | `SecurityCamera4` | `22,15` |
|
||||
| 8 | 16 | `SecurityCamera5` | `8,15` |
|
||||
|
||||
Exploding-trap tiles (each is a one-shot wild battle with
|
||||
`loadvar VAR_BATTLETYPE, BATTLETYPE_TRAP`):
|
||||
|
||||
| x | y | script | mon |
|
||||
|---|---|---|---|
|
||||
| 2 | 7 | `ExplodingTrap1` | `KoffingExplodingTrap` - KOFFING L21 |
|
||||
| 3 | 7 | `ExplodingTrap2` | `VoltorbExplodingTrap` - VOLTORB L23 |
|
||||
| 4 | 7 | `ExplodingTrap3` | `GeodudeExplodingTrap` - GEODUDE L21 |
|
||||
| 1 | 8 | `ExplodingTrap4` | VOLTORB L23 |
|
||||
| 3 | 8 | `ExplodingTrap5` | GEODUDE L21 |
|
||||
| 5 | 8 | `ExplodingTrap6` | KOFFING L21 |
|
||||
| 3 | 9 | `ExplodingTrap7` | VOLTORB L23 |
|
||||
| 4 | 9 | `ExplodingTrap8` | KOFFING L21 |
|
||||
| 1 | 10 | `ExplodingTrap9` | KOFFING L21 |
|
||||
| 2 | 10 | `ExplodingTrap10` | VOLTORB L23 |
|
||||
| 3 | 10 | `ExplodingTrap11` | GEODUDE L21 |
|
||||
| 5 | 10 | `ExplodingTrap12` | GEODUDE L21 |
|
||||
| 2 | 11 | `ExplodingTrap13` | GEODUDE L21 |
|
||||
| 4 | 11 | `ExplodingTrap14` | KOFFING L21 |
|
||||
| 1 | 12 | `ExplodingTrap15` | VOLTORB L23 |
|
||||
| 2 | 12 | `ExplodingTrap16` | KOFFING L21 |
|
||||
| 4 | 12 | `ExplodingTrap17` | VOLTORB L23 |
|
||||
| 5 | 12 | `ExplodingTrap18` | GEODUDE L21 |
|
||||
| 1 | 13 | `ExplodingTrap19` | GEODUDE L21 |
|
||||
| 3 | 13 | `ExplodingTrap20` | VOLTORB L23 |
|
||||
| 4 | 13 | `ExplodingTrap21` | KOFFING L21 |
|
||||
| 5 | 13 | `ExplodingTrap22` | VOLTORB L23 |
|
||||
|
||||
Safe cells inside the 5x7 trap field (x 1..5, y 7..13) are exactly those with no
|
||||
row above: `(1,7)`, `(5,7)`, `(2,8)`, `(4,8)`, `(1,9)`, `(2,9)`, `(5,9)`, `(4,10)`,
|
||||
`(1,11)`, `(3,11)`, `(5,11)`, `(3,12)`, `(2,13)`. `(3,11)` is also the hidden REVIVE.
|
||||
That is the "zig-zag" the walkthrough describes.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 19 | 11 | `BGEVENT_READ` | `TeamRocketBaseB1FSecretSwitch` |
|
||||
| 24 | 1 | `BGEVENT_UP` | `TeamRocketBaseB1FSecurityCamera` |
|
||||
| 6 | 1 | `BGEVENT_UP` | `TeamRocketBaseB1FSecurityCamera` |
|
||||
| 8 | 15 | `BGEVENT_UP` | `TeamRocketBaseB1FSecurityCamera` |
|
||||
| 22 | 15 | `BGEVENT_UP` | `TeamRocketBaseB1FSecurityCamera` |
|
||||
| 24 | 5 | `BGEVENT_UP` | `TeamRocketBaseB1FSecurityCamera` |
|
||||
| 20 | 11 | `BGEVENT_READ` | `TeamRocketBaseB1FBookshelf` (`jumpstd TeamRocketOathScript`) |
|
||||
| 21 | 11 | `BGEVENT_READ` | `TeamRocketBaseB1FBookshelf` |
|
||||
| 3 | 11 | `BGEVENT_ITEM` | `TeamRocketBaseB1FHiddenRevive` -> `hiddenitem REVIVE, EVENT_TEAM_ROCKET_BASE_B1F_HIDDEN_REVIVE` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | sight | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `TEAMROCKETBASEB1F_ROCKET1` | `SPRITE_ROCKET` | 0 | 0 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_SECURITY_GRUNTS` |
|
||||
| - | `SPRITE_ROCKET` | 2 | 4 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerGruntM16` | `EVENT_TEAM_ROCKET_BASE_POPULATION` |
|
||||
| - | `SPRITE_SCIENTIST` | 18 | 12 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` | 3 | `TrainerScientistJed` | `EVENT_TEAM_ROCKET_BASE_POPULATION` |
|
||||
| `TEAMROCKETBASEB1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 27 | 6 | `STILL` | `OBJECTTYPE_ITEMBALL` | 0 | `TeamRocketBaseB1FHyperPotion` | `EVENT_TEAM_ROCKET_BASE_B1F_HYPER_POTION` |
|
||||
| `TEAMROCKETBASEB1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 14 | 15 | `STILL` | `OBJECTTYPE_ITEMBALL` | 0 | `TeamRocketBaseB1FNugget` | `EVENT_TEAM_ROCKET_BASE_B1F_NUGGET` |
|
||||
| `TEAMROCKETBASEB1F_POKE_BALL3` | `SPRITE_POKE_BALL` | 21 | 12 | `STILL` | `OBJECTTYPE_ITEMBALL` | 0 | `TeamRocketBaseB1FXAccuracy` | `EVENT_TEAM_ROCKET_BASE_B1F_X_ACCURACY` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `SecurityCamera1a` .. `SecurityCamera5`: `checkevent EVENT_SECURITY_CAMERA_n` -> bail;
|
||||
`PlaySecurityCameraSounds` (6x `SFX_LICK`); `checkevent EVENT_TEAM_ROCKET_BASE_POPULATION`
|
||||
-> bail (never true, see below); `showemote EMOTE_SHOCK, PLAYER`;
|
||||
`playmusic MUSIC_ROCKET_ENCOUNTER`; `moveobject TEAMROCKETBASEB1F_ROCKET1, <x>, <y>`;
|
||||
`appear`; `applymovement SecurityCameraMovement*`; `scall TrainerCameraGrunt1`;
|
||||
repeat for `TrainerCameraGrunt2`; `setevent EVENT_SECURITY_CAMERA_n`.
|
||||
Both grunt scripts use `loadtrainer` + `startbattle` directly (no `trainer` macro,
|
||||
so no per-trainer beat flag) - the same two teams are re-fought at every camera.
|
||||
- `TeamRocketBaseB1FSecretSwitch` (`45:4757`): if `EVENT_TURNED_OFF_SECURITY_CAMERAS`
|
||||
is already set, prints "the switch is turned off"; otherwise
|
||||
`playsound SFX_TALLY` and sets
|
||||
`EVENT_TURNED_OFF_SECURITY_CAMERAS` + `EVENT_SECURITY_CAMERA_1..5` in one go.
|
||||
Cell `19,11` is a wall, read from `19,12` facing up.
|
||||
- `TrainerScientistJed` -> `.Script` after-battle text tells you about the warp panel.
|
||||
- `TrainerGruntM16` -> after-battle text about the traps.
|
||||
|
||||
**Flags and events** (B1F)
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_SECURITY_CAMERA_1` .. `_5` | `constants/event_flags.asm:403-407` | set by each camera script and by the switch | camera n is spent |
|
||||
| `EVENT_TURNED_OFF_SECURITY_CAMERAS` | `event_flags.asm:402` | `TeamRocketBaseB1FSecretSwitch`, `RocketBaseElectrodeScript` | switch state only |
|
||||
| `EVENT_TEAM_ROCKET_BASE_SECURITY_GRUNTS` | `event_flags.asm:1147` | `disappear`/`appear` of `TEAMROCKETBASEB1F_ROCKET1` | ambush grunt visibility |
|
||||
| `EVENT_TEAM_ROCKET_BASE_POPULATION` | `event_flags.asm:1148` | read by 8 camera scripts, shared visibility flag of 11 base NPCs | **never set anywhere in the ROM** - grep confirms no `setevent`/`disappear` on it, so those `checkevent` arms are dead and the base NPCs never vanish |
|
||||
| `EVENT_TEAM_ROCKET_BASE_B1F_HYPER_POTION` / `_NUGGET` / `_X_ACCURACY` | `event_flags.asm:1034-1036` | `itemball` | item ball taken |
|
||||
| `EVENT_TEAM_ROCKET_BASE_B1F_HIDDEN_REVIVE` | `event_flags.asm:144` | `hiddenitem` | hidden REVIVE at `3,11` taken |
|
||||
| `EVENT_EXPLODING_TRAP_1..22` | `constants/event_flags.asm` | each `ExplodingTrapN` | trap spent |
|
||||
|
||||
**Items** (B1F)
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `HYPER_POTION` | item ball at `27,6` | `TeamRocketBaseB1FHyperPotion` | `EVENT_TEAM_ROCKET_BASE_B1F_HYPER_POTION` |
|
||||
| `NUGGET` | item ball at `14,15` | `TeamRocketBaseB1FNugget` | `EVENT_TEAM_ROCKET_BASE_B1F_NUGGET` |
|
||||
| `X_ACCURACY` | item ball at `21,12` | `TeamRocketBaseB1FXAccuracy` | `EVENT_TEAM_ROCKET_BASE_B1F_X_ACCURACY` |
|
||||
| `REVIVE` | hidden, face cell `3,11` | `bg_event 3, 11, BGEVENT_ITEM` | `EVENT_TEAM_ROCKET_BASE_B1F_HIDDEN_REVIVE` |
|
||||
|
||||
**Trainers** (B1F)
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm`) | script label | notes |
|
||||
|---|---|---|---|---|---|
|
||||
| `GRUNTM`, `GRUNTM_16` | `GRUNTM` (`$1f`) | 16 | RATTATA 16, RATTATA 16, RATTATA 16, RATTATA 16 | `TrainerGruntM16` | `EVENT_BEAT_ROCKET_GRUNTM_16`, sight 3 |
|
||||
| `SCIENTIST`, `JED` | `SCIENTIST` (`$0e`) | 3 | MAGNEMITE 20 x3 | `TrainerScientistJed` | `EVENT_BEAT_SCIENTIST_JED`, sight 3 |
|
||||
| `GRUNTM`, `GRUNTM_20` | `GRUNTM` | 20 | DROWZEE 17, ZUBAT 19 | `TrainerCameraGrunt1` | no beat flag, repeats per camera |
|
||||
| `GRUNTM`, `GRUNTM_21` | `GRUNTM` | 21 | ZUBAT 16, GRIMER 17, RATTATA 18 | `TrainerCameraGrunt2` | no beat flag, repeats per camera |
|
||||
|
||||
**Wild encounters**: none. `TEAM_ROCKET_BASE_B1F` has no entry in
|
||||
`data/wild/johto_grass.asm`, `johto_water.asm`, `fish.asm` or `treemons.asm`. The
|
||||
only wild battles are the `loadwildmon` traps above and the three Electrode on B2F.
|
||||
|
||||
### MAP_TEAM_ROCKET_BASE_B2F
|
||||
|
||||
- Script: `maps/TeamRocketBaseB2F.asm` (`45:4c2b TeamRocketBaseB2F_MapScripts`)
|
||||
- Blocks: `maps/TeamRocketBaseB2F.blk`
|
||||
- Header: `data/maps/maps.asm:120` -> `TILESET_FACILITY, DUNGEON, LANDMARK_MAHOGANY_TOWN, MUSIC_ROCKET_HIDEOUT, TRUE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `map_const TEAM_ROCKET_BASE_B2F, 15, 9` (group 3, map id 42) = 30x18 cells
|
||||
- Scene ids: `SCENE_TEAMROCKETBASEB2F_LANCE_HEALS` = 0, `..._ROCKET_BOSS` = 1, `..._ELECTRODES` = 2, `..._NOOP` = 3
|
||||
- Callback: `MAPCALLBACK_TILES, TeamRocketBaseB2FTransmitterDoorCallback` - if `EVENT_OPENED_DOOR_TO_ROCKET_HIDEOUT_TRANSMITTER`, `changeblock 14, 12, $07 ; floor`
|
||||
|
||||
Walkable regions (flood fill of `maps/TeamRocketBaseB2F.blk` against
|
||||
`data/tilesets/facility_collision.asm`) - this floor is four disconnected rooms:
|
||||
|
||||
| region | contents |
|
||||
|---|---|
|
||||
| top corridor | warp 2 `3,2`, warp 3 `27,2`, GruntM18 `2,1` |
|
||||
| transmitter room | boss coord `14,11`/`15,11`, Electrodes `7,5`/`7,7`/`7,9` and `22,5`/`22,7`/`22,9`; sealed until the password door opens |
|
||||
| TM room | warp 4 `3,6`, TM_THIEF item ball `3,10` |
|
||||
| bottom corridor | warp 1 `3,14` (to B1F), warp 5 `27,14`, Lance heal coords, GruntM19 `21,14`, GruntM17 `25,13` |
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 14 | `TEAM_ROCKET_BASE_B1F` | 2 |
|
||||
| 2 | 3 | 2 | `TEAM_ROCKET_BASE_B3F` | 1 |
|
||||
| 3 | 27 | 2 | `TEAM_ROCKET_BASE_B3F` | 2 |
|
||||
| 4 | 3 | 6 | `TEAM_ROCKET_BASE_B3F` | 3 |
|
||||
| 5 | 27 | 14 | `TEAM_ROCKET_BASE_B3F` | 4 |
|
||||
|
||||
**Coord events**
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `LANCE_HEALS` (0) | 5 | 14 | `LanceHealsScript1` | face UP, then `LanceHealsCommon` |
|
||||
| `LANCE_HEALS` (0) | 4 | 13 | `LanceHealsScript2` | face RIGHT, then `LanceHealsCommon` |
|
||||
| `ROCKET_BOSS` (1) | 14 | 11 | `RocketBaseBossFLeft` | Executive F ambush (Lance spawns at `9,13`) |
|
||||
| `ROCKET_BOSS` (1) | 15 | 11 | `RocketBaseBossFRight` | same, actors nudged one cell right |
|
||||
| `ELECTRODES` (2) | 14 | 12 | `RocketBaseCantLeaveScript` | shoves you back into the room |
|
||||
| `ELECTRODES` (2) | 15 | 12 | `RocketBaseCantLeaveScript` | same |
|
||||
| `ELECTRODES` (2) | 12 | 3 | `RocketBaseLancesSideScript` | "Leave this side to me", step LEFT |
|
||||
| `ELECTRODES` (2) | 12 | 10 | `RocketBaseLancesSideScript` | same |
|
||||
| `ELECTRODES` (2) | 12 | 11 | `RocketBaseLancesSideScript` | same |
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 14 | 12 | `BGEVENT_IFNOTSET` | `conditional_event EVENT_OPENED_DOOR_TO_ROCKET_HIDEOUT_TRANSMITTER, TeamRocketBaseB2FLockedDoor.Script` |
|
||||
| 15 | 12 | `BGEVENT_IFNOTSET` | same |
|
||||
| 12..17, 4 and 9; 12, 5..8; 17, 5..8 (20 cells) | | `BGEVENT_READ` | `TeamRocketBaseB2FTransmitterScript` |
|
||||
| 26 | 7 | `BGEVENT_ITEM` | `TeamRocketBaseB2FHiddenFullHeal` -> `hiddenitem FULL_HEAL, EVENT_TEAM_ROCKET_BASE_B2F_HIDDEN_FULL_HEAL` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | sight | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `TEAMROCKETBASEB2F_ROCKET1` | `SPRITE_ROCKET` | 20 | 16 | `STANDING_UP` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B2F_GRUNT_WITH_EXECUTIVE` |
|
||||
| `TEAMROCKETBASEB2F_ROCKET_GIRL` | `SPRITE_ROCKET_GIRL` | 20 | 16 | `STANDING_UP` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B2F_EXECUTIVE` |
|
||||
| `TEAMROCKETBASEB2F_LANCE` | `SPRITE_LANCE` | 5 | 13 | `STANDING_DOWN` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B2F_LANCE` |
|
||||
| `TEAMROCKETBASEB2F_DRAGON` | `SPRITE_DRAGON` | 9 | 13 | `STANDING_RIGHT` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B2F_DRAGONITE` |
|
||||
| `TEAMROCKETBASEB2F_ELECTRODE1` | `SPRITE_VOLTORB` | 7 | 5 | `POKEMON` | `SCRIPT` | 0 | `RocketElectrode1` | `EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_1` |
|
||||
| `TEAMROCKETBASEB2F_ELECTRODE2` | `SPRITE_VOLTORB` | 7 | 7 | `POKEMON` | `SCRIPT` | 0 | `RocketElectrode2` | `EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_2` |
|
||||
| `TEAMROCKETBASEB2F_ELECTRODE3` | `SPRITE_VOLTORB` | 7 | 9 | `POKEMON` | `SCRIPT` | 0 | `RocketElectrode3` | `EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_3` |
|
||||
| `TEAMROCKETBASEB2F_ELECTRODE4` | `SPRITE_VOLTORB` | 22 | 5 | `POKEMON` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_1` |
|
||||
| `TEAMROCKETBASEB2F_ELECTRODE5` | `SPRITE_VOLTORB` | 22 | 7 | `POKEMON` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_2` |
|
||||
| `TEAMROCKETBASEB2F_ELECTRODE6` | `SPRITE_VOLTORB` | 22 | 9 | `POKEMON` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_3` |
|
||||
| `TEAMROCKETBASEB2F_ROCKET2` | `SPRITE_ROCKET` | 25 | 13 | `STANDING_DOWN` | `TRAINER` | 3 | `TrainerGruntM17` | `EVENT_TEAM_ROCKET_BASE_POPULATION` |
|
||||
| `TEAMROCKETBASEB2F_ROCKET3` | `SPRITE_ROCKET` | 2 | 1 | `STANDING_RIGHT` | `TRAINER` | 3 | `TrainerGruntM18` | `EVENT_TEAM_ROCKET_BASE_POPULATION` |
|
||||
| `TEAMROCKETBASEB2F_ROCKET4` | `SPRITE_ROCKET` | 21 | 14 | `STANDING_LEFT` | `TRAINER` | 4 | `TrainerGruntM19` | `EVENT_TEAM_ROCKET_BASE_POPULATION` |
|
||||
| `TEAMROCKETBASEB2F_POKE_BALL` | `SPRITE_POKE_BALL` | 3 | 10 | `STILL` | `ITEMBALL` | 0 | `TeamRocketBaseB2FTMThief` | `EVENT_TEAM_ROCKET_BASE_B2F_TM_THIEF` |
|
||||
|
||||
The Electrodes on the right (`22,5/7/9`) mirror the left three and share their flags;
|
||||
`RocketElectrodeN` disappears both of a pair, so Lance's three "count" without ever
|
||||
being battled.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `LanceHealsCommon` (`45:4d4c`): `special FadeOutToWhite`, `playsound SFX_FULL_HEAL`,
|
||||
`special HealParty`, `special FadeInFromWhite`,
|
||||
`setscene SCENE_TEAMROCKETBASEB2F_ROCKET_BOSS`,
|
||||
`setevent EVENT_LANCE_HEALED_YOU_IN_TEAM_ROCKET_BASE`, then `readvar VAR_FACING`
|
||||
picks one of two exit movements and `disappear TEAMROCKETBASEB2F_LANCE`.
|
||||
**This is a hard ordering dependency**: the boss ambush coord events only exist
|
||||
on scene 1, so the transmitter room cannot be entered until Lance has healed you.
|
||||
- `TeamRocketBaseB2FLockedDoor` (`45:4e8f`): `conditional_event` on
|
||||
`EVENT_OPENED_DOOR_TO_ROCKET_HIDEOUT_TRANSMITTER`; `.Script` checks
|
||||
`EVENT_LEARNED_HAIL_GIOVANNI`. If known: `playsound SFX_ENTER_DOOR`,
|
||||
`changeblock 14, 12, $07`, `refreshmap`, `setevent EVENT_OPENED_DOOR_TO_ROCKET_HIDEOUT_TRANSMITTER`.
|
||||
- `RocketBaseBossFScript` (`45:4c67`): cutscene, then
|
||||
`loadtrainer EXECUTIVEF, EXECUTIVEF_2` + `startbattle`;
|
||||
sets `EVENT_TEAM_ROCKET_BASE_B2F_EXECUTIVE`, `..._GRUNT_WITH_EXECUTIVE`,
|
||||
`..._LANCE`, `EVENT_BEAT_ROCKET_EXECUTIVEF_2`; then
|
||||
`setscene SCENE_TEAMROCKETBASEB2F_ELECTRODES`, `clearevent EVENT_TEAM_ROCKET_BASE_B2F_LANCE`
|
||||
(re-showing Lance), the Electrode briefing, `disappear TEAMROCKETBASEB2F_LANCE`.
|
||||
- `RocketElectrode1/2/3`: `cry ELECTRODE`, `loadwildmon ELECTRODE, 23`, `startbattle`.
|
||||
`iftrue TeamRocketBaseB2FReloadMap` covers the "you lost/fled" path. After all three
|
||||
events are set, the player is auto-walked out by
|
||||
`RocketBasePlayerLeavesElectrodesMovement1/2/3` into `RocketBaseElectrodeScript`.
|
||||
No `BATTLETYPE_*` is armed here, so these are catchable ordinary wild battles.
|
||||
- `RocketBaseElectrodeScript` (`45:4e3c`) - the section's payoff:
|
||||
`verbosegiveitem HM_WHIRLPOOL`, `setevent EVENT_GOT_HM06_WHIRLPOOL`, then
|
||||
`setevent EVENT_CLEARED_ROCKET_HIDEOUT`,
|
||||
`clearflag ENGINE_ROCKET_SIGNAL_ON_CH20`,
|
||||
`setevent EVENT_ROUTE_43_GATE_ROCKETS`,
|
||||
`setevent EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM` (hides the fisher -> gym opens),
|
||||
`setscene SCENE_TEAMROCKETBASEB2F_NOOP`,
|
||||
`clearevent EVENT_LAKE_OF_RAGE_CIVILIANS`,
|
||||
`setevent EVENT_TURNED_OFF_SECURITY_CAMERAS` + `EVENT_SECURITY_CAMERA_1..5`.
|
||||
|
||||
**Items** (B2F)
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `TM_THIEF` (TM46) | item ball at `3,10` (TM room) | `TeamRocketBaseB2FTMThief` | `EVENT_TEAM_ROCKET_BASE_B2F_TM_THIEF` |
|
||||
| `FULL_HEAL` | hidden, face cell `26,7` | `bg_event 26, 7, BGEVENT_ITEM` | `EVENT_TEAM_ROCKET_BASE_B2F_HIDDEN_FULL_HEAL` |
|
||||
| `HM_WHIRLPOOL` (HM06) | `verbosegiveitem` from Lance | `RocketBaseElectrodeScript` | `EVENT_GOT_HM06_WHIRLPOOL` |
|
||||
|
||||
TM numbering check: `constants/item_constants.asm` `add_tm THIEF ; ee` is the 46th
|
||||
`add_tm` (the `const ITEM_C3` / `const ITEM_DC` holes do not consume TM numbers), so
|
||||
TM46 = Thief, matching the walkthrough. `add_tm ICY_WIND ; cf` is the 16th = TM16.
|
||||
|
||||
**Trainers** (B2F)
|
||||
|
||||
| const | class | id | party | script label | sight |
|
||||
|---|---|---|---|---|---|
|
||||
| `GRUNTM_17` | `GRUNTM` | 17 | GOLBAT 18 | `TrainerGruntM17` | 3 |
|
||||
| `GRUNTM_18` | `GRUNTM` | 18 | RATTATA 17, ZUBAT 17, RATTATA 17 | `TrainerGruntM18` | 3 |
|
||||
| `GRUNTM_19` | `GRUNTM` | 19 | VENONAT 18, VENONAT 18 | `TrainerGruntM19` | 4 |
|
||||
| `EXECUTIVEF_2` | `EXECUTIVEF` (`$37`) | 2 | ARBOK 23 (WRAP/LEER/POISON_STING/BITE), GLOOM 23 (ABSORB/SWEET_SCENT/SLEEP_POWDER/ACID), MURKROW 25 (PECK/PURSUIT/HAZE/-) | `RocketBaseBossFScript` | cutscene |
|
||||
|
||||
**Wild encounters**: none in the tables; only `loadwildmon ELECTRODE, 23` x3.
|
||||
|
||||
### MAP_TEAM_ROCKET_BASE_B3F
|
||||
|
||||
- Script: `maps/TeamRocketBaseB3F.asm` (`45:5c8c TeamRocketBaseB3F_MapScripts`)
|
||||
- Blocks: `maps/TeamRocketBaseB3F.blk`
|
||||
- Header: `data/maps/maps.asm:121` -> `TILESET_FACILITY, DUNGEON, LANDMARK_MAHOGANY_TOWN, MUSIC_ROCKET_HIDEOUT, TRUE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `map_const TEAM_ROCKET_BASE_B3F, 15, 9` (group 3, map id 43) = 30x18 cells
|
||||
- Scene ids: `SCENE_TEAMROCKETBASEB3F_LANCE_GETS_PASSWORD` = 0, `..._RIVAL_ENCOUNTER` = 1, `..._ROCKET_BOSS` = 2, `..._NOOP` = 3
|
||||
- Callback: `MAPCALLBACK_TILES, TeamRocketBaseB3FCheckGiovanniDoorCallback` - if `EVENT_OPENED_DOOR_TO_GIOVANNIS_OFFICE`, `changeblock 10, 8, $07 ; floor`
|
||||
|
||||
Walkable regions (same flood-fill method):
|
||||
|
||||
| region | contents |
|
||||
|---|---|
|
||||
| left | warp 1 `3,2`, warp 3 `3,6`, rival coord `8,10` |
|
||||
| Giovanni's office | boss coords `10,8`/`11,8`, ExecutiveM `8,3`, Murkrow `7,2`; sealed until both passwords |
|
||||
| right / main | warp 2 `27,2`, warp 4 `27,14`, Lance `25,14`, GruntF5 `21,7`, GruntM28 `5,15`, Ross `25,12`, Mitch `14,15`, all four item balls |
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 2 | `TEAM_ROCKET_BASE_B2F` | 2 |
|
||||
| 2 | 27 | 2 | `TEAM_ROCKET_BASE_B2F` | 3 |
|
||||
| 3 | 3 | 6 | `TEAM_ROCKET_BASE_B2F` | 4 |
|
||||
| 4 | 27 | 14 | `TEAM_ROCKET_BASE_B2F` | 5 |
|
||||
|
||||
**Coord events**
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `ROCKET_BOSS` (2) | 10 | 8 | `RocketBaseBossLeft` | auto-walk `UP UP UP LEFT LEFT`, then `RocketBaseBoss` |
|
||||
| `ROCKET_BOSS` (2) | 11 | 8 | `RocketBaseBossRight` | auto-walk `UP UP LEFT UP LEFT LEFT`, then `RocketBaseBoss` |
|
||||
| `RIVAL_ENCOUNTER` (1) | 8 | 10 | `RocketBaseRival` | rival cutscene, no battle |
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 10 | 9 | `BGEVENT_IFNOTSET` | `conditional_event EVENT_OPENED_DOOR_TO_GIOVANNIS_OFFICE, TeamRocketBaseB3FLockedDoor.Script` |
|
||||
| 11 | 9 | `BGEVENT_IFNOTSET` | same |
|
||||
| 10..13, 1 | | `BGEVENT_READ` | `TeamRocketBaseB3FOathScript` (`jumpstd TeamRocketOathScript`) |
|
||||
| 4..7, 13 | | `BGEVENT_READ` | `TeamRocketBaseB3FOathScript` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | sight | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `TEAMROCKETBASEB3F_LANCE` | `SPRITE_LANCE` | 25 | 14 | `STANDING_DOWN` | `SCRIPT` | 0 | `LanceGetPasswordScript` | `EVENT_TEAM_ROCKET_BASE_B3F_LANCE_PASSWORDS` |
|
||||
| `TEAMROCKETBASEB3F_ROCKET1` | `SPRITE_ROCKET` | 8 | 3 | `STANDING_UP` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_TEAM_ROCKET_BASE_B3F_EXECUTIVE` |
|
||||
| `TEAMROCKETBASEB3F_MOLTRES` | `SPRITE_MOLTRES` | 7 | 2 | `POKEMON` | `SCRIPT` | 0 | `RocketBaseMurkrow` | `EVENT_TEAM_ROCKET_BASE_POPULATION` |
|
||||
| `TEAMROCKETBASEB3F_ROCKET_GIRL` | `SPRITE_ROCKET_GIRL` | 21 | 7 | `STANDING_UP` | `TRAINER` | **0** | `SlowpokeTailGrunt` | `EVENT_TEAM_ROCKET_BASE_POPULATION` |
|
||||
| `TEAMROCKETBASEB3F_ROCKET2` | `SPRITE_ROCKET` | 5 | 15 | `SPINRANDOM_FAST` | `TRAINER` | 3 | `RaticateTailGrunt` | `EVENT_TEAM_ROCKET_BASE_POPULATION` |
|
||||
| `TEAMROCKETBASEB3F_SCIENTIST1` | `SPRITE_SCIENTIST` | 25 | 12 | `STANDING_LEFT` | `TRAINER` | 4 | `TrainerScientistRoss` | `EVENT_TEAM_ROCKET_BASE_POPULATION` |
|
||||
| `TEAMROCKETBASEB3F_SCIENTIST2` | `SPRITE_SCIENTIST` | 14 | 15 | `STANDING_UP` | `TRAINER` | 3 | `TrainerScientistMitch` | `EVENT_TEAM_ROCKET_BASE_POPULATION` |
|
||||
| `TEAMROCKETBASEB3F_ROCKET3` | `SPRITE_ROCKET` | 24 | 14 | `STANDING_DOWN` | `SCRIPT` | 0 | `TeamRocketBaseB3FRocketScript` | `EVENT_TEAM_ROCKET_BASE_POPULATION` |
|
||||
| `TEAMROCKETBASEB3F_RIVAL` | `SPRITE_RIVAL` | 4 | 5 | `STANDING_DOWN` | `SCRIPT` | 0 | `ObjectEvent` | `EVENT_RIVAL_TEAM_ROCKET_BASE` |
|
||||
| `TEAMROCKETBASEB3F_POKE_BALL1` | `SPRITE_POKE_BALL` | 1 | 12 | `STILL` | `ITEMBALL` | 0 | `TeamRocketBaseB3FFullHeal` | `EVENT_TEAM_ROCKET_BASE_B3F_FULL_HEAL` |
|
||||
| `TEAMROCKETBASEB3F_POKE_BALL2` | `SPRITE_POKE_BALL` | 3 | 12 | `STILL` | `ITEMBALL` | 0 | `TeamRocketBaseB3FDireHit` | `EVENT_TEAM_ROCKET_BASE_B3F_DIRE_HIT` |
|
||||
| `TEAMROCKETBASEB3F_POKE_BALL3` | `SPRITE_POKE_BALL` | 28 | 9 | `STILL` | `ITEMBALL` | 0 | `TeamRocketBaseB3FProtein` | `EVENT_TEAM_ROCKET_BASE_B3F_PROTEIN` |
|
||||
| `TEAMROCKETBASEB3F_POKE_BALL4` | `SPRITE_POKE_BALL` | 17 | 2 | `STILL` | `ITEMBALL` | 0 | `TeamRocketBaseB3FIceHeal` | `EVENT_TEAM_ROCKET_BASE_B3F_ICE_HEAL` |
|
||||
|
||||
The Murkrow the walkthrough talks to is the object using `SPRITE_MOLTRES` at `7,2`;
|
||||
its script is `RocketBaseMurkrow` and its text says MURKROW. The sprite constant is
|
||||
the generic large-bird overworld sprite, not a Moltres encounter.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `LanceGetPasswordScript` (`45:5cb4`): both the `sdefer` scene script for scene 0
|
||||
and the Lance object's own script. Runs on entry to the right region, prints
|
||||
`LanceGetPasswordText`, `disappear TEAMROCKETBASEB3F_LANCE` (which sets
|
||||
`EVENT_TEAM_ROCKET_BASE_B3F_LANCE_PASSWORDS`), `setscene SCENE_TEAMROCKETBASEB3F_RIVAL_ENCOUNTER`.
|
||||
- `SlowpokeTailGrunt` -> `trainer GRUNTF, GRUNTF_5, EVENT_BEAT_ROCKET_GRUNTF_5, ...`;
|
||||
`GruntF5Script` (the after-battle arm) does `setevent EVENT_LEARNED_SLOWPOKETAIL`.
|
||||
**Sight range 0** - she never initiates; the bot must walk up and talk, then talk
|
||||
a second time for the password.
|
||||
- `RaticateTailGrunt` -> `trainer GRUNTM, GRUNTM_28, EVENT_BEAT_ROCKET_GRUNTM_28, ...`;
|
||||
`GruntM28Script` does `setevent EVENT_LEARNED_RATICATE_TAIL`. Sight 3,
|
||||
`SPINRANDOM_FAST` (the "spinning Team Rocket member").
|
||||
- `TeamRocketBaseB3FLockedDoor` (`45:5da9`): `.Script` requires
|
||||
`EVENT_LEARNED_SLOWPOKETAIL` **and** `EVENT_LEARNED_RATICATE_TAIL`; on success
|
||||
`changeblock 10, 8, $07`, `refreshmap`, `setevent EVENT_OPENED_DOOR_TO_GIOVANNIS_OFFICE`.
|
||||
- `RocketBaseRival` (`45:5cd1`): `appear TEAMROCKETBASEB3F_RIVAL`, walk-in, dialogue,
|
||||
`applymovement PLAYER, RocketBaseRivalShovesPlayerMovement` (a `big_step RIGHT`
|
||||
with `fix_facing`), `disappear`, `setscene SCENE_TEAMROCKETBASEB3F_ROCKET_BOSS`.
|
||||
No battle - matches the walkthrough ("He can't battle you though").
|
||||
- `RocketBaseBoss` (`45:5d0e`): `loadtrainer EXECUTIVEM, EXECUTIVEM_4`, `startbattle`,
|
||||
`setevent EVENT_BEAT_ROCKET_EXECUTIVEM_4`, long exit movement,
|
||||
`disappear TEAMROCKETBASEB3F_ROCKET1`, `setscene SCENE_TEAMROCKETBASEB3F_NOOP`.
|
||||
- `RocketBaseMurkrow` (`45:5d49`): text, then `setevent EVENT_LEARNED_HAIL_GIOVANNI`.
|
||||
|
||||
**Items** (B3F)
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `FULL_HEAL` | item ball `1,12` | `TeamRocketBaseB3FFullHeal` | `EVENT_TEAM_ROCKET_BASE_B3F_FULL_HEAL` |
|
||||
| `DIRE_HIT` | item ball `3,12` | `TeamRocketBaseB3FDireHit` | `EVENT_TEAM_ROCKET_BASE_B3F_DIRE_HIT` |
|
||||
| `PROTEIN` | item ball `28,9` | `TeamRocketBaseB3FProtein` | `EVENT_TEAM_ROCKET_BASE_B3F_PROTEIN` |
|
||||
| `ICE_HEAL` | item ball `17,2` | `TeamRocketBaseB3FIceHeal` | `EVENT_TEAM_ROCKET_BASE_B3F_ICE_HEAL` |
|
||||
|
||||
**Trainers** (B3F)
|
||||
|
||||
| const | class | id | party | script label | sight |
|
||||
|---|---|---|---|---|---|
|
||||
| `SCIENTIST`, `ROSS` | `SCIENTIST` (`$0e`) | 1 | KOFFING 22, KOFFING 22 | `TrainerScientistRoss` | 4 |
|
||||
| `SCIENTIST`, `MITCH` | `SCIENTIST` | 2 | DITTO 24 | `TrainerScientistMitch` | 3 |
|
||||
| `GRUNTF`, `GRUNTF_5` | `GRUNTF` (`$42`) | 5 | EKANS 18 (WRAP/LEER/POISON_STING/BITE), GLOOM 18 (ABSORB/SWEET_SCENT/STUN_SPORE/SLEEP_POWDER) | `SlowpokeTailGrunt` | 0 |
|
||||
| `GRUNTM`, `GRUNTM_28` | `GRUNTM` (`$1f`) | 28 | RATICATE 19 | `RaticateTailGrunt` | 3 |
|
||||
| `EXECUTIVEM`, `EXECUTIVEM_4` | `EXECUTIVEM` (`$33`) | 4 | ZUBAT 22, RATICATE 24, KOFFING 22 | `RocketBaseBoss` | cutscene |
|
||||
|
||||
**Wild encounters**: none.
|
||||
|
||||
### MAP_MAHOGANY_GYM
|
||||
|
||||
- Script: `maps/MahoganyGym.asm` (`51:536c MahoganyGym_MapScripts`)
|
||||
- Blocks: `maps/MahoganyGym.blk`
|
||||
- Header: `data/maps/maps.asm:69` -> `map MahoganyGym, TILESET_ELITE_FOUR_ROOM, INDOOR, LANDMARK_MAHOGANY_TOWN, MUSIC_GYM, TRUE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `map_const MAHOGANY_GYM, 5, 9` (group 2, map id 2) = 10x18 cells
|
||||
- `def_scene_scripts` and `def_callbacks` are both **empty**
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 17 | `MAHOGANY_TOWN` | 3 |
|
||||
| 2 | 5 | 17 | `MAHOGANY_TOWN` | 3 |
|
||||
|
||||
`def_coord_events` is empty.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 3 | 15 | `BGEVENT_READ` | `MahoganyGymStatue` |
|
||||
| 6 | 15 | `BGEVENT_READ` | `MahoganyGymStatue` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | sight | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `MAHOGANYGYM_PRYCE` | `SPRITE_PRYCE` | 5 | 3 | `STANDING_DOWN` | `SCRIPT` | 0 | `MahoganyGymPryceScript` | -1 |
|
||||
| `MAHOGANYGYM_BEAUTY1` | `SPRITE_BEAUTY` | 4 | 6 | `STANDING_DOWN` | `TRAINER` | 1 | `TrainerSkierRoxanne` | -1 |
|
||||
| `MAHOGANYGYM_ROCKER1` | `SPRITE_ROCKER` | 0 | 17 | `STANDING_UP` | `TRAINER` | 1 | `TrainerBoarderRonald` | -1 |
|
||||
| `MAHOGANYGYM_BEAUTY2` | `SPRITE_BEAUTY` | 9 | 17 | `STANDING_UP` | `TRAINER` | 1 | `TrainerSkierClarissa` | -1 |
|
||||
| `MAHOGANYGYM_ROCKER2` | `SPRITE_ROCKER` | 5 | 9 | `STANDING_DOWN` | `TRAINER` | 1 | `TrainerBoarderBrad` | -1 |
|
||||
| `MAHOGANYGYM_ROCKER3` | `SPRITE_ROCKER` | 2 | 4 | `SPINRANDOM_FAST` | `TRAINER` | 1 | `TrainerBoarderDouglas` | -1 |
|
||||
| `MAHOGANYGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 15 | `STANDING_DOWN` | `SCRIPT` | 0 | `MahoganyGymGuideScript` | -1 |
|
||||
|
||||
**Ice-floor map**, decoded from `maps/MahoganyGym.blk` against
|
||||
`data/tilesets/elite_four_room_collision.asm`. `I` = `COLL_ICE` (`$23`),
|
||||
`.` = walkable floor, `#` = wall, `v` = warp carpet:
|
||||
|
||||
```
|
||||
x0123456789
|
||||
y0 ##########
|
||||
y1 ##########
|
||||
y2 .IIIII.I#I
|
||||
y3 IIIIIIII#I
|
||||
y4 I#I.I.IIII
|
||||
y5 I#IIII.III
|
||||
y6 I#IIIIIIII
|
||||
y7 I#II.IIII.
|
||||
y8 .IIIIIII#I
|
||||
y9 IIIIIIII#I
|
||||
y10 I#.II.III.
|
||||
y11 I#IIIII.II
|
||||
y12 I#IIIIII#I
|
||||
y13 I#I.II.I#I
|
||||
y14 I#.#..#.#I
|
||||
y15 I#.#..#.#I
|
||||
y16 .I......I.
|
||||
y17 II..vv..II
|
||||
```
|
||||
|
||||
Sliding rule (`engine/overworld/player_movement.asm`): `CheckIceTile` on the tile
|
||||
you land on -> `STEP_ICE`; `.CheckForced` / `CheckStandingOnIce` re-injects the same
|
||||
d-pad direction, so a single press slides until you land on a non-ice tile, hit a
|
||||
wall, or hit an NPC.
|
||||
|
||||
Simulating that rule against the grid above (NPCs treated as blockers) reproduces
|
||||
the walkthrough's directions exactly:
|
||||
|
||||
| from | press | lands on | who |
|
||||
|---|---|---|---|
|
||||
| `4,17` (warp) | `UP` | `4,16` | - |
|
||||
| `4,16` | `LEFT`, `LEFT`, `LEFT` | `3,16` -> `2,16` -> `0,16` | faces Boarder Ronald `0,17` |
|
||||
| `0,16` | `UP`,`UP`,`RIGHT`,`DOWN`,`LEFT` | `0,8` -> `0,2` -> `6,2` -> `6,5` -> `2,5` | faces Boarder Douglas `2,4` |
|
||||
| `2,5` | `DOWN`,`RIGHT` | `2,10` -> `5,10` | faces Boarder Brad `5,9` |
|
||||
| `5,10` | `RIGHT`,`DOWN` | `9,10` -> `9,16` | faces Skier Clarissa `9,17` |
|
||||
| `9,16` | `UP`,`UP`,`LEFT` | `9,10` -> `9,7` -> `4,7` | faces Skier Roxanne `4,6` |
|
||||
| `4,7` | `DOWN` | `4,14` | back toward the exit |
|
||||
| `2,14` ("just left of the left pillar", pillar block at `3,14`/`3,15`) | `UP`,`UP`,`RIGHT`,`DOWN`,`LEFT`,`UP`,`RIGHT` | `2,10` -> `2,5` -> `6,5` -> `6,13` -> `3,13` -> `3,4` -> `5,4` | standing directly below Pryce `5,3` |
|
||||
|
||||
Reaching `2,14` from the entrance: `UP` (to `4,16`), `LEFT` (`3,16`), `LEFT`
|
||||
(`2,16`), `UP` (`2,15`), `UP` (`2,14`) - `2,15`/`2,16`/`2,14` are ordinary floor,
|
||||
not ice, so those are single steps.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
`MahoganyGymPryceScript` (`51:536e`):
|
||||
|
||||
1. `checkevent EVENT_BEAT_PRYCE` -> `.FightDone`
|
||||
2. `writetext PryceText_Intro`; `winlosstext PryceText_Impressed, 0`;
|
||||
`loadtrainer PRYCE, PRYCE1`; `startbattle`; `reloadmapafterbattle`
|
||||
3. `setevent EVENT_BEAT_PRYCE`; `Text_ReceivedGlacierBadge`;
|
||||
`playsound SFX_GET_BADGE`; `setflag ENGINE_GLACIERBADGE`
|
||||
4. `readvar VAR_BADGES`; `scall MahoganyGymActivateRockets` -
|
||||
`ifequal 7, .RadioTowerRockets` / `ifequal 6, .GoldenrodRockets`.
|
||||
Badge count is read **after** GLACIERBADGE is set, so the normal route
|
||||
(Glacier as the 7th badge) takes `jumpstd RadioTowerRocketsScript`
|
||||
(`engine/events/std_scripts.asm:255`), which sets
|
||||
`ENGINE_ROCKETS_IN_RADIO_TOWER`, `EVENT_GOLDENROD_CITY_CIVILIANS`,
|
||||
`EVENT_RADIO_TOWER_BLACKBELT_BLOCKS_STAIRS`,
|
||||
`EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_EAST`,
|
||||
`specialphonecall SPECIALCALL_WEIRDBROADCAST`, and
|
||||
`setmapscene MAHOGANY_TOWN, SCENE_MAHOGANYTOWN_NOOP`.
|
||||
Skipping Jasmine first (Glacier as the 6th) takes
|
||||
`GoldenrodRocketsScript` instead, which only clears
|
||||
`EVENT_GOLDENROD_CITY_ROCKET_TAKEOVER`.
|
||||
5. `.FightDone`: if `EVENT_GOT_TM16_ICY_WIND` is set -> farewell text. Otherwise
|
||||
`setevent EVENT_BEAT_SKIER_ROXANNE / _CLARISSA / _BOARDER_RONALD / _BRAD / _DOUGLAS`
|
||||
(so the five gym trainers never battle again), `PryceText_GlacierBadgeSpeech`,
|
||||
`verbosegiveitem TM_ICY_WIND`, `setevent EVENT_GOT_TM16_ICY_WIND`.
|
||||
|
||||
`MahoganyGymStatue`: `checkflag ENGINE_GLACIERBADGE`; before the badge
|
||||
`jumpstd GymStatue1Script`, after it `gettrainername STRING_BUFFER_4, PRYCE, PRYCE1`
|
||||
+ `jumpstd GymStatue2Script`.
|
||||
|
||||
**Flags and events** (gym)
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BEAT_PRYCE` | `constants/event_flags.asm:712` | `MahoganyGymPryceScript` | leader beaten |
|
||||
| `EVENT_GOT_TM16_ICY_WIND` | `event_flags.asm:20` | `MahoganyGymPryceScript` | TM handed over |
|
||||
| `ENGINE_GLACIERBADGE` | `constants/engine_flags.asm:44` | set here; read by `WhirlpoolFunction` and `MahoganyGymStatue` | badge 7 |
|
||||
| `EVENT_BEAT_SKIER_ROXANNE`, `_CLARISSA`, `EVENT_BEAT_BOARDER_RONALD`, `_BRAD`, `_DOUGLAS` | `constants/event_flags.asm` | the five `trainer` macros, force-set by Pryce | gym trainer beaten |
|
||||
|
||||
**Trainers** (gym)
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm`) | script label |
|
||||
|---|---|---|---|---|
|
||||
| `BOARDER`, `RONALD` | `BOARDER` (`$3a`) | 1 | SEEL 24, DEWGONG 25, SEEL 24 | `TrainerBoarderRonald` |
|
||||
| `BOARDER`, `BRAD` | `BOARDER` | 2 | SWINUB 26, SWINUB 26 | `TrainerBoarderBrad` |
|
||||
| `BOARDER`, `DOUGLAS` | `BOARDER` | 3 | SHELLDER 24, CLOYSTER 25, SHELLDER 24 | `TrainerBoarderDouglas` |
|
||||
| `SKIER`, `ROXANNE` | `SKIER` (`$21`) | 1 | JYNX 28 | `TrainerSkierRoxanne` |
|
||||
| `SKIER`, `CLARISSA` | `SKIER` | 2 | DEWGONG 28 | `TrainerSkierClarissa` |
|
||||
| `PRYCE`, `PRYCE1` | `PRYCE` (`$05`) | 1 | SEEL 27 (HEADBUTT/ICY_WIND/AURORA_BEAM/REST), DEWGONG 29 (HEADBUTT/ICY_WIND/AURORA_BEAM/REST), PILOSWINE 31 (ICY_WIND/FURY_ATTACK/MIST/BLIZZARD) | `MahoganyGymPryceScript` |
|
||||
|
||||
**Wild encounters**: none.
|
||||
|
||||
---
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Hideout entrance does not exist | `maps/MahoganyMart1F.asm:MahoganyMart1FStaircaseCallback` + `MahoganyMart1FLanceUncoversStaircaseScript` (`45:4046`) | `EVENT_UNCOVERED_STAIRCASE_IN_MAHOGANY_MART` for the block; the cutscene needs `SCENE_MAHOGANYMART1F_LANCE_UNCOVERS_STAIRS` | `maps/LakeOfRage.asm:LakeOfRageLanceScript.AgreedToHelp` (previous section) |
|
||||
| Camera grunt ambushes | `maps/TeamRocketBaseB1F.asm:SecurityCamera1a..5` | `EVENT_SECURITY_CAMERA_n` clear | beat both grunts at that camera, or press the switch at `19,11` (`TeamRocketBaseB1FSecretSwitch`, `45:4757`) |
|
||||
| Exploding floor traps | `maps/TeamRocketBaseB1F.asm:ExplodingTrap1..22` | `EVENT_EXPLODING_TRAP_n` clear | step on the tile once (or route around; safe cells listed above) |
|
||||
| B2F transmitter room sealed (block) | `maps/TeamRocketBaseB2F.asm:TeamRocketBaseB2FLockedDoor` (`45:4e8f`) + `TeamRocketBaseB2FTransmitterDoorCallback` | `EVENT_LEARNED_HAIL_GIOVANNI` | talk to the Murkrow object at B3F `7,2` (`RocketBaseMurkrow`, `45:5d49`) after beating ExecutiveM 4 |
|
||||
| B2F boss ambush will not fire | `maps/TeamRocketBaseB2F.asm` coord events on `SCENE_TEAMROCKETBASEB2F_ROCKET_BOSS` | map scene must be 1 | trip `LanceHealsScript1/2` at `5,14` or `4,13` first |
|
||||
| B3F Giovanni's office sealed | `maps/TeamRocketBaseB3F.asm:TeamRocketBaseB3FLockedDoor` (`45:5da9`) + `TeamRocketBaseB3FCheckGiovanniDoorCallback` | `EVENT_LEARNED_SLOWPOKETAIL` **and** `EVENT_LEARNED_RATICATE_TAIL` | beat `GRUNTF_5` at `21,7` then talk again; beat `GRUNTM_28` at `5,15` then talk again |
|
||||
| Cannot leave the Electrode room | `maps/TeamRocketBaseB2F.asm:RocketBaseCantLeaveScript` / `RocketBaseLancesSideScript` | scene `SCENE_TEAMROCKETBASEB2F_ELECTRODES` | faint all three left-hand Electrodes |
|
||||
| Mahogany Gym door blocked | `maps/MahoganyTown.asm` object `SPRITE_FISHER` at `6,14` gated on `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM`; visibility rule in `engine/overworld/map_objects_2.asm:CheckObjectFlag` | flag set = NPC hidden | `RocketBaseElectrodeScript` (`45:4e3c`) sets it when the hideout is cleared |
|
||||
| Whirlpool unusable in the field | `engine/events/overworld.asm:WhirlpoolFunction.TryWhirlpool` (`03:4da0`, `ld de, ENGINE_GLACIERBADGE / call CheckBadge`) and `TryWhirlpoolOW` (`03:4e41`, `CheckPartyMove WHIRLPOOL` then `CheckEngineFlag ENGINE_GLACIERBADGE`) | GLACIERBADGE + a party member that knows WHIRLPOOL | beat Pryce |
|
||||
| Rage Candy Bar merchant trip-wire | `maps/MahoganyTown.asm` coord events `19,8`/`19,9` on scene 0 | scene `SCENE_MAHOGANYTOWN_TRY_RAGECANDYBAR` | only cleared later, by `RadioTowerRocketsScript` `setmapscene MAHOGANY_TOWN, SCENE_MAHOGANYTOWN_NOOP` |
|
||||
|
||||
---
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
Coordinates are `(x, y)` map cells as written in the asm. "talk" = face the target
|
||||
and press A.
|
||||
|
||||
1. `MAHOGANY_TOWN`: warp 4 at `(15,13)` -> `MAHOGANY_POKECENTER_1F`; talk to nurse at `(3,1)`. Pre: none. Post: party healed.
|
||||
2. `MAHOGANY_TOWN`: warp 1 at `(11,7)` -> `MAHOGANY_MART_1F`. Pre: `EVENT_DECIDED_TO_HELP_LANCE` set and map scene = 1. Post: `MahoganyMart1FLanceUncoversStaircaseScript` runs unattended; wait for `EVENT_UNCOVERED_STAIRCASE_IN_MAHOGANY_MART`.
|
||||
3. `MAHOGANY_MART_1F`: walk to `(7,3)` -> `TEAM_ROCKET_BASE_B1F` warp 1, arriving at `(27,2)`.
|
||||
4. B1F: walk west along y=2. Crossing `(24,2)` or `(24,3)` fires `SecurityCamera1a/1b` -> battle `GRUNTM_20` then `GRUNTM_21`. Post: `EVENT_SECURITY_CAMERA_1`.
|
||||
5. B1F: item ball at `(27,6)` = HYPER_POTION. Post: `EVENT_TEAM_ROCKET_BASE_B1F_HYPER_POTION`.
|
||||
6. B1F: continue west; `(6,2)`/`(6,3)` fires camera 2, `(24,6)`/`(24,7)` camera 3, `(22,16)` camera 4, `(8,16)` camera 5. Each is the same two grunts.
|
||||
7. B1F: talk to `GRUNTM_16` at `(2,4)` (sight 3, faces RIGHT - approaching from the east triggers him). Post: `EVENT_BEAT_ROCKET_GRUNTM_16`.
|
||||
8. B1F: cross the trap field x1..5 / y7..13 using only the safe cells listed in section 2, or accept the one-shot `BATTLETYPE_TRAP` battles. Optional: hidden REVIVE by facing `(3,11)`.
|
||||
9. B1F: battle `SCIENTIST JED` at `(18,12)` (sight 3, faces LEFT). Post: `EVENT_BEAT_SCIENTIST_JED`. Talk again for the warp-panel hint.
|
||||
10. B1F: stand at `(19,12)`, face UP, press A on the bg event at `(19,11)`. Post: `EVENT_TURNED_OFF_SECURITY_CAMERAS` + `EVENT_SECURITY_CAMERA_1..5`.
|
||||
11. B1F: item balls `(21,12)` = X_ACCURACY, `(14,15)` = NUGGET.
|
||||
12. B1F: walk to `(3,14)` -> `TEAM_ROCKET_BASE_B2F` arriving at `(3,14)`.
|
||||
13. B2F: step on `(5,14)` or `(4,13)`. Pre: map scene 0. Post: party healed, `EVENT_LANCE_HEALED_YOU_IN_TEAM_ROCKET_BASE`, scene -> 1.
|
||||
14. B2F: battle `GRUNTM_19` at `(21,14)` (sight 4, faces LEFT), then `GRUNTM_17` at `(25,13)` (sight 3, faces DOWN).
|
||||
15. B2F: walk to `(27,14)` -> `TEAM_ROCKET_BASE_B3F` arriving at `(27,14)`. Pre: none. Post: `LanceGetPasswordScript` auto-runs, scene -> 1, `EVENT_TEAM_ROCKET_BASE_B3F_LANCE_PASSWORDS`.
|
||||
16. B3F: item balls `(28,9)` = PROTEIN, `(17,2)` = ICE_HEAL.
|
||||
17. B3F: battle `SCIENTIST ROSS` at `(25,12)` (sight 4, faces LEFT).
|
||||
18. B3F: walk to `(21,8)` and talk UP to `GRUNTF_5` at `(21,7)` - **sight 0, she will not initiate**. Post: `EVENT_BEAT_ROCKET_GRUNTF_5`; talk again -> `EVENT_LEARNED_SLOWPOKETAIL`.
|
||||
19. B3F: battle `SCIENTIST MITCH` at `(14,15)` (sight 3, faces UP).
|
||||
20. B3F: battle `GRUNTM_28` at `(5,15)` (sight 3, spinning). Post: `EVENT_BEAT_ROCKET_GRUNTM_28`; talk again -> `EVENT_LEARNED_RATICATE_TAIL`.
|
||||
21. B3F: item balls `(3,12)` = DIRE_HIT, `(1,12)` = FULL_HEAL.
|
||||
22. B3F: walk to `(27,2)` -> `TEAM_ROCKET_BASE_B2F` arriving at `(27,2)`.
|
||||
23. B2F top corridor: walk west along y=1..2; battle `GRUNTM_18` at `(2,1)` (sight 3, faces RIGHT).
|
||||
24. B2F: walk to `(3,2)` -> `TEAM_ROCKET_BASE_B3F` arriving at `(3,2)`.
|
||||
25. B3F left region: walk to `(8,10)`. Pre: scene 1. Post: rival cutscene, no battle, scene -> 2, `EVENT_RIVAL_TEAM_ROCKET_BASE` set by `disappear`.
|
||||
26. B3F: stand at `(10,10)` or `(11,10)`, face UP, press A on the bg event at `(10,9)`/`(11,9)`. Pre: both password events. Post: `EVENT_OPENED_DOOR_TO_GIOVANNIS_OFFICE`, block at `(10,8)` becomes floor.
|
||||
27. B3F: step onto `(10,8)` or `(11,8)`. Pre: scene 2. Post: auto-walk, battle `EXECUTIVEM_4`, `EVENT_BEAT_ROCKET_EXECUTIVEM_4`, scene -> 3.
|
||||
28. B3F: talk to the bird object at `(7,2)`. Post: `EVENT_LEARNED_HAIL_GIOVANNI`.
|
||||
29. B3F: walk to `(3,6)` -> `TEAM_ROCKET_BASE_B2F` arriving at `(3,6)`; item ball `(3,10)` = TM_THIEF (TM46). Return via `(3,6)`.
|
||||
30. Back-track: B3F `(3,2)` -> B2F `(3,2)`; B2F top corridor east to `(27,2)` -> B3F `(27,2)`; B3F right region south to `(27,14)` -> B2F `(27,14)`; walk west to the door.
|
||||
31. B2F: stand at `(14,13)`/`(15,13)`, face UP, press A on `(14,12)`/`(15,12)`. Pre: `EVENT_LEARNED_HAIL_GIOVANNI`. Post: `EVENT_OPENED_DOOR_TO_ROCKET_HIDEOUT_TRANSMITTER`, block becomes floor.
|
||||
32. B2F: step onto `(14,11)` or `(15,11)`. Pre: scene 1. Post: `EXECUTIVEF_2` battle, `EVENT_BEAT_ROCKET_EXECUTIVEF_2`, scene -> 2, Lance briefing.
|
||||
33. B2F: talk to the three left Electrodes at `(7,5)`, `(7,7)`, `(7,9)`. Each is a wild ELECTRODE L23 that may Selfdestruct. Do **not** walk east past x=12 (`RocketBaseLancesSideScript` bounces you back). Post: three `EVENT_TEAM_ROCKET_BASE_B2F_ELECTRODE_n`.
|
||||
34. After the third, `RocketBaseElectrodeScript` auto-runs: accept `HM_WHIRLPOOL`. Post: `EVENT_GOT_HM06_WHIRLPOOL`, `EVENT_CLEARED_ROCKET_HIDEOUT`, `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM`, `clearflag ENGINE_ROCKET_SIGNAL_ON_CH20`, scene -> 3.
|
||||
35. Exit: B2F `(3,14)` -> B1F `(3,14)`; B1F warp panel `(5,15)` -> `(25,2)`; `(27,2)` -> mart; mart `(3,7)`/`(4,7)` -> town.
|
||||
36. `MAHOGANY_TOWN`: heal at the Pokecenter, then warp 3 at `(6,13)` -> `MAHOGANY_GYM`, arriving at `(4,17)`/`(5,17)`. Pre: `EVENT_MAHOGANY_TOWN_POKEFAN_M_BLOCKS_GYM` set (otherwise the fisher stands on `(6,14)`).
|
||||
37. Gym: `UP`, `LEFT`, `LEFT`, `LEFT` -> `(0,16)`, Boarder Ronald spots you from `(0,17)`.
|
||||
38. Gym: `UP`,`UP`,`RIGHT`,`DOWN`,`LEFT` -> `(2,5)`, Boarder Douglas at `(2,4)`. Talk again for the Pryce-waterfall line.
|
||||
39. Gym: `DOWN`,`RIGHT` -> `(5,10)`, Boarder Brad at `(5,9)`.
|
||||
40. Gym: `RIGHT`,`DOWN` -> `(9,16)`, Skier Clarissa at `(9,17)`.
|
||||
41. Gym: `UP`,`UP`,`LEFT` -> `(4,7)`, Skier Roxanne at `(4,6)`.
|
||||
42. Gym: `DOWN` -> `(4,14)`, then walk to `(2,14)`.
|
||||
43. Gym: from `(2,14)`: `UP`,`UP`,`RIGHT`,`DOWN`,`LEFT`,`UP`,`RIGHT` -> `(5,4)`. Talk UP to Pryce at `(5,3)`.
|
||||
44. Battle `PRYCE1`. Post: `EVENT_BEAT_PRYCE`, `ENGINE_GLACIERBADGE`, the five gym-trainer beat flags, `EVENT_GOT_TM16_ICY_WIND`, and (at 7 badges) `RadioTowerRocketsScript`.
|
||||
45. Gym: leave via `(4,17)`/`(5,17)`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Map headers / warps / coord events / bg events / objects for all six maps | `src/import/RomExtractorGen2.lua`, `src/world/gen2/Map.lua` | implemented (generic extraction, nothing map-specific needed) |
|
||||
| Map scenes + `sdefer` scene scripts | `src/world/gen2/World.lua` (`World:scene`, `mapScenes`), `src/script/gen2/Vm.lua` (`sdefer`, `setscene`, `setmapscene`) | implemented |
|
||||
| Coord-event trip-wires | `src/world/gen2/World.lua` (coord event dispatch) | implemented |
|
||||
| `changeblock` (mart staircase, both password doors) | `src/script/gen2/Vm.lua:1006` - the comment cites `MahoganyMart1F`'s `changeblock 6, 2, $1e` and cell `(7,3)` by name | implemented |
|
||||
| `BGEVENT_IFNOTSET` / `conditional_event` (both password doors) | `src/world/gen2/World.lua:5147 World:bgEventAt` only matches `(ev.kind or 0) == 0`; `src/world/gen2/HiddenItems.lua` handles kind 7 | **missing** - pressing A on the transmitter door or Giovanni's door does nothing in the port |
|
||||
| Ice sliding (`STEP_ICE`, `CheckStandingOnIce` forced d-pad) | `src/world/gen2/Permissions.lua:128 ICE` exists but is only consumed by `FieldMoves.canEncounterWildMon`; `World:movePlayer` picks step frames from `Bike.stepFrames` only | **missing** - Mahogany Gym floor behaves as ordinary floor, so the puzzle and the trainer sight lines are wrong |
|
||||
| Trainer sight range / approach | `src/world/gen2/Trainers.lua` (`Trainers.sees`, `Trainers.approach`) | implemented (`sight 0` on GruntF5 falls out naturally) |
|
||||
| `itemball` / `hiddenitem` | `src/script/gen2/CallAsm.lua`, `src/world/gen2/HiddenItems.lua` | implemented |
|
||||
| `verbosegiveitem HM_WHIRLPOOL` | `src/script/gen2/Vm.lua`, `src/script/gen2/Opcodes.lua` | implemented |
|
||||
| Whirlpool field move + GLACIERBADGE gate | `src/world/gen2/FieldMoves.lua:109` (`WHIRLPOOL = "GLACIER"`), `.somethingToWhirlpool`, `.whirlpoolFromMenu`, `.tryWhirlpoolOW`, `WHIRLPOOL_BLOCKS` | implemented |
|
||||
| `loadwildmon` + `BATTLETYPE_TRAP` | `src/world/gen2/World.lua:4587` (names "TRAP 9 (the Rocket base)"), `src/script/gen2/Vm.lua:699` | implemented |
|
||||
| `special HealParty` / `FadeOutToWhite` / `FadeInFromWhite` / `PlayMapMusic` / `ReloadSpritesNoPalettes` / `FadeOutToBlack` / `FadeInFromBlack` / `RestartMapMusic` / `FadeOutMusic` | `src/script/gen2/Specials.lua:450, 992-1066` | implemented |
|
||||
| `moveobject` / `appear` / `disappear` / `applymovement` / `follow` / `showemote` | `src/script/gen2/Vm.lua`, `src/script/gen2/Movement.lua`, `src/world/gen2/World.lua` | implemented |
|
||||
| `jumpstd` (`TeamRocketOathScript`, `GymStatue1/2Script`, `PokecenterSignScript`, `RadioTowerRocketsScript`) | `src/script/gen2/Vm.lua:742`, `data/generated/std_scripts.lua` via `World.lua:635` | implemented (generic) |
|
||||
| `readvar VAR_BADGES` for `MahoganyGymActivateRockets` | `src/world/gen2/World.lua:117,1240` | implemented |
|
||||
| `specialphonecall SPECIALCALL_WEIRDBROADCAST` | `src/script/gen2/Vm.lua:1364` stores the id; `src/core/gen2/Phone.lua` delivers | implemented |
|
||||
| `ENGINE_ROCKET_SIGNAL_ON_CH20` radio behaviour | `src/ui/gen2/Pokegear.lua:816-821, 1097` | implemented |
|
||||
| Driver coverage for this stretch | `tests/drivers/gold_*.lua` - none touches Mahogany, the hideout or Pryce (`gold_icepath_boulder.lua` is the only Mahogany-adjacent one, and it is Ice Path boulders) | **missing** |
|
||||
|
||||
The two "missing" rows are the blocking ones: without `BGEVENT_IFNOTSET` the
|
||||
hideout cannot be completed, and without ice sliding the gym cannot be crossed.
|
||||
|
||||
---
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
- **Floor labels in the walkthrough are wrong past the first password.** It says
|
||||
"go up the stairs at the upper-right part of the floor, then head left on Negative
|
||||
Floor 2 ... Head up the stairs to Negative Floor 1. Head right and then before you
|
||||
reach the locked door, your rival will come." The rival coord event
|
||||
(`8,10`) and Giovanni's office are both on **B3F**, not B1F. The route is
|
||||
B3F(right) -> B2F(top) -> B3F(left). Verified by flood-filling
|
||||
`maps/TeamRocketBaseB2F.blk` / `maps/TeamRocketBaseB3F.blk`: the four B2F warps
|
||||
and four B3F warps sit in disconnected rooms, and B2F warp 2 `(3,2)` reaches
|
||||
B3F `(3,2)` which is the region containing `8,10`.
|
||||
- **Boarder Ronald's party order.** Walkthrough lists Seel 24 / Seel 24 / Dewgong 25;
|
||||
`data/trainers/parties.asm` `BOARDER (1)` is SEEL 24, DEWGONG 25, SEEL 24.
|
||||
Same three mon, different lead order. Same class of discrepancy for
|
||||
`GRUNTM_18` (walkthrough Rattata/Rattata/Zubat, asm Rattata/Zubat/Rattata).
|
||||
- **Item list is incomplete.** The walkthrough's "Items in the Team Rocket Hideout"
|
||||
block lists six; the asm has ten: the six named plus `FULL_HEAL` and `DIRE_HIT`
|
||||
item balls on B3F (mentioned later in the prose) and two hidden items the
|
||||
walkthrough never mentions - `REVIVE` at B1F `(3,11)` and `FULL_HEAL` at B2F
|
||||
`(26,7)`.
|
||||
- **"Every time you pass one of those statues, two Team Rocket grunts will fight
|
||||
you"** is right, but the walkthrough implies only two statues on the first floor.
|
||||
There are five (`bg_event` at `24,1`, `6,1`, `24,5`, `8,15`, `22,15`) with eight
|
||||
trigger cells, i.e. up to ten grunt battles if none are skipped.
|
||||
- **`EVENT_TEAM_ROCKET_BASE_POPULATION` is dead.** It is the shared visibility flag
|
||||
of eleven base NPCs and is read by all eight `SecurityCamera*` scripts, but a
|
||||
full-tree grep finds no `setevent`, `clearevent` or `disappear` that writes it.
|
||||
Those `checkevent ... iftrue NoSecurityCamera` arms therefore never take, and the
|
||||
Rocket NPCs stay on the maps forever - including after the hideout is cleared.
|
||||
Flagged in case a bot author expects the base to empty out.
|
||||
- **Lance's three right-hand Electrodes are never battled.** `RocketElectrodeN`
|
||||
disappears both members of a mirrored pair, and the objects at `22,5`/`22,7`/`22,9`
|
||||
point at the no-op `ObjectEvent` script. The walkthrough's "Lance runs to the right
|
||||
to take the Electrodes on the right" is flavour; the game just hides them.
|
||||
- **Exact reachability of `19,11` (the secret switch)** was checked by collision
|
||||
(`19,11` is a WALL, `19,12` is FLOOR and in the single B1F region), but the
|
||||
in-game facing direction for the `BGEVENT_READ` was not observed running - the
|
||||
bg event type is `BGEVENT_READ`, not `BGEVENT_UP`, so any adjacent facing should
|
||||
work; worth confirming on hardware if a driver misses it.
|
||||
- The gym slide table in section 2 was produced by simulating
|
||||
`engine/overworld/player_movement.asm`'s ice rule against the decoded
|
||||
`maps/MahoganyGym.blk`, treating NPCs as blockers. It reproduces every direction
|
||||
string in the walkthrough, but it has not been executed in an emulator.
|
||||
@@ -0,0 +1,762 @@
|
||||
# Section 15 - Mount Mortar and Dark Cave
|
||||
|
||||
Source: `../section-15-mount-mortar-and-dark-cave.txt`
|
||||
Maps covered: `MAP_MOUNT_MORTAR_1F_OUTSIDE`, `MAP_MOUNT_MORTAR_1F_INSIDE`,
|
||||
`MAP_MOUNT_MORTAR_2F_INSIDE`, `MAP_MOUNT_MORTAR_B1F`,
|
||||
`MAP_DARK_CAVE_BLACKTHORN_ENTRANCE`, `MAP_DARK_CAVE_VIOLET_ENTRANCE`.
|
||||
Touched in passing (owned by neighbouring sections): `MAP_MAHOGANY_TOWN`,
|
||||
`MAP_ROUTE_42`, `MAP_BLACKTHORN_CITY`, `MAP_ROUTE_45`, `MAP_ROUTE_31`,
|
||||
`MAP_ROUTE_46`.
|
||||
|
||||
Badges / key milestones in this section: **no badge**. The milestones are
|
||||
`EVENT_BEAT_BLACKBELT_KIYO` + `EVENT_GOT_TYROGUE_FROM_KIYO` (a free level 10
|
||||
`TYROGUE`, the only one in the game) and `EVENT_GOT_BLACKGLASSES_IN_DARK_CAVE`.
|
||||
Everything else here is optional loot. Nothing in this section gates any later
|
||||
section.
|
||||
|
||||
A note on coordinates: every table below is transcribed verbatim from the map's
|
||||
`_MapEvents` block in the named `maps/*.asm` file. `warp_event`, `coord_event`,
|
||||
`bg_event` and `object_event` all share one coordinate space (the
|
||||
`object_event` macro in `macros/scripts/maps.asm` applies the +4 border offset
|
||||
itself), so the numbers are directly comparable. A map declared
|
||||
`map_const NAME, W, H` spans x in `0 .. 2W-1` and y in `0 .. 2H-1`.
|
||||
|
||||
A note on the `event flag` column of `object_event`:
|
||||
`engine/overworld/map_objects_2.asm` `CheckObjectFlag` **hides** the object when
|
||||
the flag is set, and `-1` means always visible. So for a Poke Ball, "flag set =
|
||||
already taken = gone".
|
||||
|
||||
A note on darkness: all six maps in this section are `PALETTE_DARK` in
|
||||
`data/maps/maps.asm`. `engine/tilesets/timeofday_pals.asm`
|
||||
`ReplaceTimeOfDayPals` (`23:43e9`) sends `PALETTE_DARK` down `.NeedsFlash`,
|
||||
which writes `DARKNESS_PALSET` unless `STATUSFLAGS_FLASH_F` is set in
|
||||
`wStatusFlags`. That bit is set by `BlindingFlash` (`engine/events/field_moves.asm`)
|
||||
and cleared by `ResetFlashIfOutOfCave` (`home/flag.asm`, `00:2f1d`) on any map
|
||||
whose `wEnvironment` is `ROUTE` or `TOWN`. **A bot must re-cast Flash every time
|
||||
it re-enters, including after every Fly.**
|
||||
|
||||
---
|
||||
|
||||
## 1. Route order
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `MAP_MAHOGANY_TOWN` | `data/maps/maps.asm:74`, `data/maps/spawn_points.asm` `spawn MAHOGANY_TOWN, 15, 14` | Fly (`ENGINE_FLYPOINT_MAHOGANY`) | west map connection -> `ROUTE_42` | heal, buy Max Repels / Escape Ropes, drop to 5 party members so Tyrogue fits |
|
||||
| 2 | `MAP_ROUTE_42` | `maps/Route42.asm` | east connection from Mahogany, or warps 1/2 at (0,8)/(0,9) from the Ecruteak gate | warp 3 (10,5) west entrance, warp 4 (28,9) middle entrance, warp 5 (46,7) east entrance -> `MOUNT_MORTAR_1F_OUTSIDE` warps 1/2/3 | "three cave entrances to Mt. Mortar" |
|
||||
| 3 | `MAP_MOUNT_MORTAR_1F_OUTSIDE` | `maps/MountMortar1FOutside.asm` | warps 1/2/3 at (3,33)/(17,33)/(37,33) | warp 4 (17,5) -> 2F after Waterfall; warp 7 (17,29) -> B1F | all three Route 42 doors land on this one map; the Surf lake and the waterfall live here |
|
||||
| 4 | `MAP_MOUNT_MORTAR_1F_INSIDE` | `maps/MountMortar1FInside.asm` | 1F Outside warps 5/6 (11,21)/(29,21) or 8/9 (7,13)/(33,13) | warp 5 (3,19) -> B1F; warp 6 (9,9) -> 2F | the "giant room" with the Strength boulder, Escape Rope and Hyper Potion |
|
||||
| 5 | `MAP_MOUNT_MORTAR_2F_INSIDE` | `maps/MountMortar2FInside.asm` | warp 1 (17,33) from 1F Outside warp 4 (top of the waterfall) | warp 2 (3,5) -> `MOUNT_MORTAR_1F_INSIDE` warp 6 | Rare Candy, Max Potion, TM40, Dragon Scale, Elixer, Escape Rope, hidden Full Restore |
|
||||
| 6 | `MAP_MOUNT_MORTAR_B1F` | `maps/MountMortarB1F.asm` | warp 1 (3,3) from 1F Inside warp 5 | Escape Rope, or Strength the boulder at (9,10) and take warp 2 (19,29) -> 1F Outside warp 7 | **Blackbelt Kiyo, then the free Tyrogue** |
|
||||
| 7 | `MAP_BLACKTHORN_CITY` | `data/maps/maps.asm:187`, `data/maps/spawn_points.asm` `spawn BLACKTHORN_CITY, 21, 30` | Fly (`ENGINE_FLYPOINT_BLACKTHORN`) | south map connection (`data/maps/attributes.asm:161` `connection south, Route45, ROUTE_45, 0`) | heal, then head south |
|
||||
| 8 | `MAP_ROUTE_45` | `maps/Route45.asm` | north connection from Blackthorn | warp 1 (2,5) -> `DARK_CAVE_BLACKTHORN_ENTRANCE` warp 1 | "enter the Dark Cave to the left before there are even any jumps" |
|
||||
| 9 | `MAP_DARK_CAVE_BLACKTHORN_ENTRANCE` | `maps/DarkCaveBlackthornEntrance.asm` | warp 1 (23,3) | warp 2 (3,25) -> `DARK_CAVE_VIOLET_ENTRANCE` warp 2 | Blackglasses NPC, Revive, TM13 Snore, Wobbuffet |
|
||||
| 10 | `MAP_DARK_CAVE_VIOLET_ENTRANCE` | `maps/DarkCaveVioletEntrance.asm` | warp 2 (17,1) | warp 1 (3,15) -> `ROUTE_31` warp 3 | "surf down, rock smash the rock, grab the Potion, head out onto Route 31" |
|
||||
| 11 | `MAP_ROUTE_31` | `maps/Route31.asm` | warp 3 (34,5) | Fly back to Blackthorn | walkthrough exit |
|
||||
|
||||
Spill into the next section: the walkthrough's video links name Blackthorn Gym,
|
||||
Route 45/46, Tohjo Falls, Routes 26/27, Victory Road and the Elite Four, but its
|
||||
**prose** stops at "head back south to actually take Route 45". `MAP_ROUTE_45`
|
||||
proper, `MAP_ROUTE_46`, `MAP_BLACKTHORN_GYM` and everything past them belong to
|
||||
the next section; only the Route 45 warp that reaches Dark Cave is documented
|
||||
here.
|
||||
|
||||
`MAP_DARK_CAVE_VIOLET_ENTRANCE` is also reachable from `ROUTE_46` warp 3 at
|
||||
(14,5) (-> Dark Cave warp 3 at (35,33)) and from `ROUTE_31` warp 3; the earlier
|
||||
sections that visit Route 31 already touch the north end of this map. This
|
||||
section only walks it top-to-bottom-left, from the Blackthorn side out to
|
||||
Route 31.
|
||||
|
||||
---
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_MOUNT_MORTAR_1F_OUTSIDE
|
||||
|
||||
- Script: `maps/MountMortar1FOutside.asm`
|
||||
- Blocks: `maps/MountMortar1FOutside.blk`
|
||||
- Header: `data/maps/maps.asm:127` -> `TILESET_DARK_CAVE`, `CAVE`,
|
||||
`LANDMARK_MT_MORTAR`, `MUSIC_UNION_CAVE`, phone `TRUE`, `PALETTE_DARK`,
|
||||
`FISHGROUP_LAKE`
|
||||
- Dimensions: `constants/map_constants.asm:114` `map_const MOUNT_MORTAR_1F_OUTSIDE, 20, 18`
|
||||
(x 0..39, y 0..35), map id 49, group 3
|
||||
- Attributes: `data/maps/attributes.asm:446`, border block `$09`, **no connections**
|
||||
- Map events block: `MountMortar1FOutside_MapEvents` = `46:5db9`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 33 | `ROUTE_42` | 3 |
|
||||
| 2 | 17 | 33 | `ROUTE_42` | 4 |
|
||||
| 3 | 37 | 33 | `ROUTE_42` | 5 |
|
||||
| 4 | 17 | 5 | `MOUNT_MORTAR_2F_INSIDE` | 1 |
|
||||
| 5 | 11 | 21 | `MOUNT_MORTAR_1F_INSIDE` | 1 |
|
||||
| 6 | 29 | 21 | `MOUNT_MORTAR_1F_INSIDE` | 2 |
|
||||
| 7 | 17 | 29 | `MOUNT_MORTAR_B1F` | 2 |
|
||||
| 8 | 7 | 13 | `MOUNT_MORTAR_1F_INSIDE` | 3 |
|
||||
| 9 | 33 | 13 | `MOUNT_MORTAR_1F_INSIDE` | 4 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
`def_coord_events` is **empty**. No scripted trip-wires anywhere in Mt. Mortar.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 25 | 22 | `BGEVENT_ITEM` | `MountMortar1FOutsideHiddenHyperPotion` -> `hiddenitem HYPER_POTION, EVENT_MOUNT_MORTAR_1F_OUTSIDE_HIDDEN_HYPER_POTION` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `MOUNTMORTAR1FOUTSIDE_POKE_BALL1` | `SPRITE_POKE_BALL` | 13 | 15 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar1FOutsideEther` (`itemball ETHER`) | `EVENT_MOUNT_MORTAR_1F_OUTSIDE_ETHER` |
|
||||
| `MOUNTMORTAR1FOUTSIDE_POKE_BALL2` | `SPRITE_POKE_BALL` | 31 | 18 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar1FOutsideGuardSpec` (`itemball GUARD_SPEC`) | `EVENT_MOUNT_MORTAR_1F_OUTSIDE_REVIVE` |
|
||||
|
||||
Note the flag/item mismatch on the second ball: the item is `GUARD_SPEC` but the
|
||||
flag is named `..._REVIVE`. That is verbatim from the disassembly - the label
|
||||
name is cosmetic, the flag bit is what matters.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `MountMortar1FOutsideEther` / `MountMortar1FOutsideGuardSpec` - bare
|
||||
`itemball ITEM` rows (two bytes, no bytecode). Picked up by the shared
|
||||
`FindItemInBallScript` path, not by a per-map script.
|
||||
- `MountMortar1FOutsideHiddenHyperPotion` - `hiddenitem` data behind a
|
||||
`BGEVENT_ITEM` row; A-pressed on (25,22) or found by the ITEMFINDER.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
`data/wild/johto_grass.asm:686` `def_grass_wildmons MOUNT_MORTAR_1F_OUTSIDE`,
|
||||
encounter rate `6 percent` for all three of morn/day/nite, and **all three
|
||||
slot lists are identical**:
|
||||
|
||||
| slot | lvl | species |
|
||||
|---|---|---|
|
||||
| 1 | 13 | ZUBAT |
|
||||
| 2 | 15 | ZUBAT |
|
||||
| 3 | 14 | MACHOP |
|
||||
| 4 | 14 | RATTATA |
|
||||
| 5 | 14 | GEODUDE |
|
||||
| 6 | 16 | RATTATA |
|
||||
| 7 | 15 | MARILL |
|
||||
|
||||
`data/wild/johto_water.asm:58` `def_water_wildmons MOUNT_MORTAR_1F_OUTSIDE`,
|
||||
rate `4 percent`: 20 GOLDEEN / 15 GOLDEEN / 20 SEAKING.
|
||||
|
||||
Fishing: `FISHGROUP_LAKE`, `data/wild/fish.asm:57` - Old rod MAGIKARP 10 /
|
||||
GOLDEEN 10; Good rod MAGIKARP 20 / GOLDEEN 20 / `time_group 4`; Super rod
|
||||
GOLDEEN 40 / `time_group 5` / MAGIKARP 40 / SEAKING 40.
|
||||
|
||||
No headbutt or rock-smash table (`data/wild/treemon_maps.asm` does not list this
|
||||
map).
|
||||
|
||||
---
|
||||
|
||||
### MAP_MOUNT_MORTAR_1F_INSIDE
|
||||
|
||||
- Script: `maps/MountMortar1FInside.asm`
|
||||
- Blocks: `maps/MountMortar1FInside.blk`
|
||||
- Header: `data/maps/maps.asm:128` -> `TILESET_DARK_CAVE`, `CAVE`,
|
||||
`LANDMARK_MT_MORTAR`, `MUSIC_UNION_CAVE`, phone `TRUE`, `PALETTE_DARK`,
|
||||
`FISHGROUP_LAKE`
|
||||
- Dimensions: `constants/map_constants.asm:115` `map_const MOUNT_MORTAR_1F_INSIDE, 20, 27`
|
||||
(x 0..39, y 0..53), map id 50, group 3
|
||||
- Attributes: `data/maps/attributes.asm:447`, border block `$09`, **no connections**
|
||||
- Map events block: `MountMortar1FInside_MapEvents` = `46:5e19`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 11 | 47 | `MOUNT_MORTAR_1F_OUTSIDE` | 5 |
|
||||
| 2 | 29 | 47 | `MOUNT_MORTAR_1F_OUTSIDE` | 6 |
|
||||
| 3 | 5 | 39 | `MOUNT_MORTAR_1F_OUTSIDE` | 8 |
|
||||
| 4 | 33 | 41 | `MOUNT_MORTAR_1F_OUTSIDE` | 9 |
|
||||
| 5 | 3 | 19 | `MOUNT_MORTAR_B1F` | 1 |
|
||||
| 6 | 9 | 9 | `MOUNT_MORTAR_2F_INSIDE` | 2 |
|
||||
|
||||
**Coord events** - empty.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 31 | 9 | `BGEVENT_ITEM` | `MountMortar1FInsideHiddenMaxRepel` -> `hiddenitem MAX_REPEL, EVENT_MOUNT_MORTAR_1F_INSIDE_HIDDEN_MAX_REPEL` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `MOUNTMORTAR1FINSIDE_BOULDER` | `SPRITE_BOULDER` | 21 | 43 | `SPRITEMOVEDATA_STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | `MountMortar1FBoulder` (`jumpstd StrengthBoulderScript`) | `-1` |
|
||||
| `MOUNTMORTAR1FINSIDE_POKE_BALL1` | `SPRITE_POKE_BALL` | 33 | 22 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar1FInsideEscapeRope` (`itemball ESCAPE_ROPE`) | `EVENT_MOUNT_MORTAR_1F_INSIDE_ESCAPE_ROPE` |
|
||||
| `MOUNTMORTAR1FINSIDE_POKE_BALL2` | `SPRITE_POKE_BALL` | 16 | 10 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar1FInsideMaxRevive` (`itemball MAX_REVIVE`) | `EVENT_MOUNT_MORTAR_1F_INSIDE_MAX_REVIVE` |
|
||||
| `MOUNTMORTAR1FINSIDE_POKE_BALL3` | `SPRITE_POKE_BALL` | 12 | 21 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar1FInsideHyperPotion` (`itemball HYPER_POTION`) | `EVENT_MOUNT_MORTAR_1F_INSIDE_HYPER_POTION` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `MountMortar1FBoulder` - one line: `jumpstd StrengthBoulderScript`.
|
||||
`engine/events/std_scripts.asm:196` `StrengthBoulderScript: farsjump AskStrengthScript`
|
||||
(`03:4d4e`). See section 3 for the gate.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
`data/wild/johto_grass.asm:714` `def_grass_wildmons MOUNT_MORTAR_1F_INSIDE`,
|
||||
rate `6 percent` morn/day/nite, all three lists identical:
|
||||
|
||||
| slot | lvl | species |
|
||||
|---|---|---|
|
||||
| 1 | 13 | GEODUDE |
|
||||
| 2 | 13 | MACHOP |
|
||||
| 3 | 15 | GEODUDE |
|
||||
| 4 | 14 | RATTATA |
|
||||
| 5 | 15 | MACHOP |
|
||||
| 6 | 14 | ZUBAT |
|
||||
| 7 | 14 | ZUBAT |
|
||||
|
||||
**There is no `def_water_wildmons MOUNT_MORTAR_1F_INSIDE`** in
|
||||
`data/wild/johto_water.asm` - only 1F Outside, 2F Inside and B1F have water
|
||||
tables. Fishing group is still `FISHGROUP_LAKE`.
|
||||
|
||||
---
|
||||
|
||||
### MAP_MOUNT_MORTAR_2F_INSIDE
|
||||
|
||||
- Script: `maps/MountMortar2FInside.asm`
|
||||
- Blocks: `maps/MountMortar2FInside.blk`
|
||||
- Header: `data/maps/maps.asm:129` -> `TILESET_DARK_CAVE`, `CAVE`,
|
||||
`LANDMARK_MT_MORTAR`, `MUSIC_UNION_CAVE`, phone `TRUE`, `PALETTE_DARK`,
|
||||
`FISHGROUP_LAKE`
|
||||
- Dimensions: `constants/map_constants.asm:116` `map_const MOUNT_MORTAR_2F_INSIDE, 20, 18`
|
||||
(x 0..39, y 0..35), map id 51, group 3
|
||||
- Attributes: `data/maps/attributes.asm:448`, border block `$09`, **no connections**
|
||||
- Map events block: `MountMortar2FInside_MapEvents` = `46:5e87`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 17 | 33 | `MOUNT_MORTAR_1F_OUTSIDE` | 4 |
|
||||
| 2 | 3 | 5 | `MOUNT_MORTAR_1F_INSIDE` | 6 |
|
||||
|
||||
**Coord events** - empty.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 19 | 6 | `BGEVENT_ITEM` | `MountMortar2FInsideHiddenFullRestore` -> `hiddenitem FULL_RESTORE, EVENT_MOUNT_MORTAR_2F_INSIDE_HIDDEN_FULL_RESTORE` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `MOUNTMORTAR2FINSIDE_POKE_BALL1` | `SPRITE_POKE_BALL` | 31 | 23 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar2FInsideMaxPotion` (`itemball MAX_POTION`) | `EVENT_MOUNT_MORTAR_2F_INSIDE_MAX_POTION` |
|
||||
| `MOUNTMORTAR2FINSIDE_POKE_BALL2` | `SPRITE_POKE_BALL` | 2 | 24 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar2FInsideRareCandy` (`itemball RARE_CANDY`) | `EVENT_MOUNT_MORTAR_2F_INSIDE_RARE_CANDY` |
|
||||
| `MOUNTMORTAR2FINSIDE_POKE_BALL3` | `SPRITE_POKE_BALL` | 19 | 17 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar2FInsideTMDefenseCurl` (`itemball TM_DEFENSE_CURL`) | `EVENT_MOUNT_MORTAR_2F_INSIDE_TM_DEFENSE_CURL` |
|
||||
| `MOUNTMORTAR2FINSIDE_POKE_BALL4` | `SPRITE_POKE_BALL` | 14 | 5 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar2FInsideDragonScale` (`itemball DRAGON_SCALE`) | `EVENT_MOUNT_MORTAR_2F_INSIDE_DRAGON_SCALE` |
|
||||
| `MOUNTMORTAR2FINSIDE_POKE_BALL5` | `SPRITE_POKE_BALL` | 8 | 9 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar2FInsideElixer` (`itemball ELIXER`) | `EVENT_MOUNT_MORTAR_2F_INSIDE_ELIXER` |
|
||||
| `MOUNTMORTAR2FINSIDE_POKE_BALL6` | `SPRITE_POKE_BALL` | 28 | 5 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortar2FInsideEscapeRope` (`itemball ESCAPE_ROPE`) | `EVENT_MOUNT_MORTAR_2F_INSIDE_ESCAPE_ROPE` |
|
||||
|
||||
`TM_DEFENSE_CURL` is **TM40** by the `add_tm` ordering in
|
||||
`constants/item_constants.asm:261`. The walkthrough calls it "TM40 Aerial Ace",
|
||||
which is the HGSS remake's TM40 - see section 6.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
`data/wild/johto_grass.asm:742` `def_grass_wildmons MOUNT_MORTAR_2F_INSIDE`,
|
||||
rate `6 percent` morn/day/nite, all three lists identical - this is the floor
|
||||
the walkthrough's "Raticate / Machoke / Graveler" list is drawn from:
|
||||
|
||||
| slot | lvl | species |
|
||||
|---|---|---|
|
||||
| 1 | 31 | GRAVELER |
|
||||
| 2 | 32 | MACHOKE |
|
||||
| 3 | 31 | GEODUDE |
|
||||
| 4 | 30 | RATICATE |
|
||||
| 5 | 28 | MACHOP |
|
||||
| 6 | 30 | GOLBAT |
|
||||
| 7 | 30 | GOLBAT |
|
||||
|
||||
`data/wild/johto_water.asm:65` `def_water_wildmons MOUNT_MORTAR_2F_INSIDE`,
|
||||
rate `2 percent`: 20 GOLDEEN / 25 GOLDEEN / 25 SEAKING.
|
||||
|
||||
---
|
||||
|
||||
### MAP_MOUNT_MORTAR_B1F
|
||||
|
||||
- Script: `maps/MountMortarB1F.asm`
|
||||
- Blocks: `maps/MountMortarB1F.blk`
|
||||
- Header: `data/maps/maps.asm:130` -> `TILESET_DARK_CAVE`, `CAVE`,
|
||||
`LANDMARK_MT_MORTAR`, `MUSIC_UNION_CAVE`, phone `TRUE`, `PALETTE_DARK`,
|
||||
`FISHGROUP_LAKE`
|
||||
- Dimensions: `constants/map_constants.asm:117` `map_const MOUNT_MORTAR_B1F, 20, 18`
|
||||
(x 0..39, y 0..35), map id 52, group 3
|
||||
- Attributes: `data/maps/attributes.asm:449`, border block `$09`, **no connections**
|
||||
- Map events block: `MountMortarB1F_MapEvents` = `46:60f0`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 3 | `MOUNT_MORTAR_1F_INSIDE` | 5 |
|
||||
| 2 | 19 | 29 | `MOUNT_MORTAR_1F_OUTSIDE` | 7 |
|
||||
|
||||
**Coord events** - empty. Kiyo is a talk-to trigger, not a trip-wire.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 4 | 6 | `BGEVENT_ITEM` | `MountMortarB1FHiddenMaxRevive` -> `hiddenitem MAX_REVIVE, EVENT_MOUNT_MORTAR_B1F_HIDDEN_MAX_REVIVE` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `MOUNTMORTARB1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 31 | 17 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortarB1FHyperPotion` (`itemball HYPER_POTION`) | `EVENT_MOUNT_MORTAR_B1F_HYPER_POTION` |
|
||||
| `MOUNTMORTARB1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 4 | 16 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `MountMortarB1FFullHeal` (`itemball FULL_HEAL`) | `EVENT_MOUNT_MORTAR_B1F_FULL_HEAL` |
|
||||
| `MOUNTMORTARB1F_BOULDER` | `SPRITE_BOULDER` | 9 | 10 | `SPRITEMOVEDATA_STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | `MountMortarB1FBoulder` (`jumpstd StrengthBoulderScript`) | `-1` |
|
||||
| `MOUNTMORTARB1F_KIYO` | `SPRITE_BLACK_BELT` | 13 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, palette `PAL_NPC_BROWN` | `MountMortarB1FKiyoScript` | `-1` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
`MountMortarB1FKiyoScript` (`46:5eec`) - the whole point of this section. Note
|
||||
it is `OBJECTTYPE_SCRIPT`, **not** `OBJECTTYPE_TRAINER`: Kiyo has no `trainer`
|
||||
header, no sight range, and never walks up to you. He must be talked to.
|
||||
|
||||
```
|
||||
faceplayer
|
||||
opentext
|
||||
checkevent EVENT_GOT_TYROGUE_FROM_KIYO ; iftrue -> .GotTyrogue (46:5f27)
|
||||
checkevent EVENT_BEAT_BLACKBELT_KIYO ; iftrue -> .BeatKiyo (46:5f0d)
|
||||
writetext MountMortarB1FKiyoIntroText
|
||||
waitbutton
|
||||
closetext
|
||||
winlosstext MountMortarB1FKiyoWinText, 0
|
||||
loadtrainer BLACKBELT_T, KIYO
|
||||
startbattle
|
||||
reloadmapafterbattle
|
||||
setevent EVENT_BEAT_BLACKBELT_KIYO
|
||||
opentext
|
||||
.BeatKiyo:
|
||||
writetext MountMortarB1FTyrogueRewardText
|
||||
promptbutton
|
||||
waitsfx
|
||||
readvar VAR_PARTYCOUNT
|
||||
ifequal PARTY_LENGTH, .NoRoom ; 46:5f2d
|
||||
writetext MountMortarB1FReceiveMonText
|
||||
playsound SFX_CAUGHT_MON
|
||||
waitsfx
|
||||
givepoke TYROGUE, 10
|
||||
setevent EVENT_GOT_TYROGUE_FROM_KIYO
|
||||
.GotTyrogue:
|
||||
writetext MountMortarB1FKiyoGotTyrogueText
|
||||
waitbutton
|
||||
closetext
|
||||
end
|
||||
```
|
||||
|
||||
Bot-relevant control flow:
|
||||
|
||||
- The battle is unconditional on first talk (no `checkevent` guard before
|
||||
`loadtrainer`), and the win-flag write is **after** `startbattle`, so a
|
||||
blackout means the whole script re-runs from the top.
|
||||
- `readvar VAR_PARTYCOUNT` / `ifequal PARTY_LENGTH` is the "you have no room"
|
||||
branch. `PARTY_LENGTH` is 6. **If the party is full, `EVENT_BEAT_BLACKBELT_KIYO`
|
||||
is already set but `EVENT_GOT_TYROGUE_FROM_KIYO` is not**, so re-talking
|
||||
re-enters at `.BeatKiyo` and offers the Tyrogue again. This is why the
|
||||
walkthrough tells you to free a slot in advance - but it is recoverable, not
|
||||
missable.
|
||||
- `givepoke TYROGUE, 10` - level 10, no held item, no nickname prompt in this
|
||||
form of the opcode.
|
||||
|
||||
`MountMortarB1FBoulder` - `jumpstd StrengthBoulderScript`, same as 1F Inside.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BEAT_BLACKBELT_KIYO` | `constants/event_flags.asm:684` | set by `MountMortarB1FKiyoScript` after `startbattle` | Kiyo will not fight again |
|
||||
| `EVENT_GOT_TYROGUE_FROM_KIYO` | `constants/event_flags.asm:106` | set by `MountMortarB1FKiyoScript` after `givepoke` | the Tyrogue is banked; the script becomes pure flavour |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `KIYO` (`constants/trainer_constants.asm:500`) | `BLACKBELT_T` | 6th entry of `BlackbeltGroup` | `data/trainers/parties.asm:2455` `; BLACKBELT_T (6)` `db "KIYO@", TRAINERTYPE_NORMAL` / `db 34, HITMONLEE` / `db 34, HITMONCHAN` | `MountMortarB1FKiyoScript` | none - no `trainer` header, so no phone number, no rematch |
|
||||
|
||||
`TRAINERTYPE_NORMAL` means **no custom moves and no held items**: both mons use
|
||||
their level-34 learnset defaults. The walkthrough's "816G" prize and the
|
||||
1012/1020 EXP figures were not verified against `data/trainers/attributes.asm` -
|
||||
see section 6.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
`data/wild/johto_grass.asm:770` `def_grass_wildmons MOUNT_MORTAR_B1F`, rate
|
||||
`6 percent` morn/day/nite, all three lists identical:
|
||||
|
||||
| slot | lvl | species |
|
||||
|---|---|---|
|
||||
| 1 | 15 | ZUBAT |
|
||||
| 2 | 17 | ZUBAT |
|
||||
| 3 | 16 | RATTATA |
|
||||
| 4 | 16 | MACHOP |
|
||||
| 5 | 16 | GEODUDE |
|
||||
| 6 | 16 | RATICATE |
|
||||
| 7 | 16 | RATICATE |
|
||||
|
||||
`data/wild/johto_water.asm:72` `def_water_wildmons MOUNT_MORTAR_B1F`, rate
|
||||
`2 percent`: 20 GOLDEEN / 15 GOLDEEN / 20 SEAKING.
|
||||
|
||||
---
|
||||
|
||||
### MAP_DARK_CAVE_BLACKTHORN_ENTRANCE
|
||||
|
||||
- Script: `maps/DarkCaveBlackthornEntrance.asm`
|
||||
- Blocks: `maps/DarkCaveBlackthornEntrance.blk`
|
||||
- Header: `data/maps/maps.asm:149` -> `TILESET_DARK_CAVE`, `CAVE`,
|
||||
`LANDMARK_DARK_CAVE`, `MUSIC_DARK_CAVE`, phone `TRUE`, `PALETTE_DARK`,
|
||||
`FISHGROUP_LAKE`
|
||||
- Dimensions: `constants/map_constants.asm:136` `map_const DARK_CAVE_BLACKTHORN_ENTRANCE, 15, 18`
|
||||
(x 0..29, y 0..35), map id 71, group 3
|
||||
- Attributes: `data/maps/attributes.asm:468`, border block `$09`, **no connections**
|
||||
- Map events block: `DarkCaveBlackthornEntrance_MapEvents` = `47:4489`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 23 | 3 | `ROUTE_45` | 1 |
|
||||
| 2 | 3 | 25 | `DARK_CAVE_VIOLET_ENTRANCE` | 2 |
|
||||
|
||||
**Coord events** - empty.
|
||||
|
||||
**BG events** - `def_bg_events` is **empty**. No signs, no hidden items.
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `DARKCAVEBLACKTHORNENTRANCE_PHARMACIST` | `SPRITE_PHARMACIST` | 7 | 3 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `DarkCaveBlackthornEntrancePharmacistScript` | `-1` |
|
||||
| `DARKCAVEBLACKTHORNENTRANCE_POKE_BALL1` | `SPRITE_POKE_BALL` | 21 | 24 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `DarkCaveBlackthornEntranceRevive` (`itemball REVIVE`) | `EVENT_DARK_CAVE_BLACKTHORN_ENTRANCE_REVIVE` |
|
||||
| `DARKCAVEBLACKTHORNENTRANCE_POKE_BALL2` | `SPRITE_POKE_BALL` | 7 | 22 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `DarkCaveBlackthornEntranceTMSnore` (`itemball TM_SNORE`) | `EVENT_DARK_CAVE_BLACKTHORN_ENTRANCE_TM_SNORE` |
|
||||
|
||||
`TM_SNORE` is **TM13** by the `add_tm` ordering in
|
||||
`constants/item_constants.asm:233`, which matches the walkthrough's "TM 13".
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
`DarkCaveBlackthornEntrancePharmacistScript` (`47:436c`):
|
||||
|
||||
```
|
||||
faceplayer
|
||||
opentext
|
||||
checkevent EVENT_GOT_BLACKGLASSES_IN_DARK_CAVE ; iftrue -> .GotBlackglasses (47:4381)
|
||||
writetext DarkCaveBlackthornEntrancePharmacistText1
|
||||
promptbutton
|
||||
verbosegiveitem BLACKGLASSES
|
||||
iffalse .PackFull ; 47:4385
|
||||
setevent EVENT_GOT_BLACKGLASSES_IN_DARK_CAVE
|
||||
.GotBlackglasses:
|
||||
writetext DarkCaveBlackthornEntrancePharmacistText2
|
||||
waitbutton
|
||||
.PackFull:
|
||||
closetext
|
||||
end
|
||||
```
|
||||
|
||||
Bot-relevant: `verbosegiveitem` returns 0 when the pack is full, and the
|
||||
`setevent` is **after** the give, so a full item pocket leaves the flag clear
|
||||
and the NPC re-offers. Not missable. The NPC spins
|
||||
(`SPRITEMOVEDATA_SPINRANDOM_SLOW`), so the bot must stand adjacent and press A;
|
||||
`faceplayer` handles the turn.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_GOT_BLACKGLASSES_IN_DARK_CAVE` | `constants/event_flags.asm:123` | `DarkCaveBlackthornEntrancePharmacistScript` | BLACKGLASSES banked; NPC becomes flavour text |
|
||||
| `EVENT_DARK_CAVE_BLACKTHORN_ENTRANCE_REVIVE` | `constants/event_flags.asm:1090` | object visibility mask | set = ball already taken |
|
||||
| `EVENT_DARK_CAVE_BLACKTHORN_ENTRANCE_TM_SNORE` | `constants/event_flags.asm:1091` | object visibility mask | set = TM13 already taken |
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
`data/wild/johto_grass.asm:1545` `def_grass_wildmons DARK_CAVE_BLACKTHORN_ENTRANCE`,
|
||||
rate `4 percent` morn/day/nite, all three lists identical - this is the
|
||||
walkthrough's Wobbuffet spot:
|
||||
|
||||
| slot | lvl | species |
|
||||
|---|---|---|
|
||||
| 1 | 23 | GEODUDE |
|
||||
| 2 | 23 | ZUBAT |
|
||||
| 3 | 25 | GRAVELER |
|
||||
| 4 | 20 | WOBBUFFET |
|
||||
| 5 | 25 | WOBBUFFET |
|
||||
| 6 | 23 | GOLBAT |
|
||||
| 7 | 23 | GOLBAT |
|
||||
|
||||
`data/wild/johto_water.asm:114` `def_water_wildmons DARK_CAVE_BLACKTHORN_ENTRANCE`,
|
||||
rate `2 percent`: 15 MAGIKARP / 10 MAGIKARP / 5 MAGIKARP.
|
||||
|
||||
No `treemon_map` row for this map - **rock smash here yields nothing**, and
|
||||
there are no smashable rock objects on it either.
|
||||
|
||||
---
|
||||
|
||||
### MAP_DARK_CAVE_VIOLET_ENTRANCE
|
||||
|
||||
- Script: `maps/DarkCaveVioletEntrance.asm`
|
||||
- Blocks: `maps/DarkCaveVioletEntrance.blk`
|
||||
- Header: `data/maps/maps.asm:148` -> `TILESET_DARK_CAVE`, `CAVE`,
|
||||
`LANDMARK_DARK_CAVE`, `MUSIC_DARK_CAVE`, phone `TRUE`, `PALETTE_DARK`,
|
||||
`FISHGROUP_LAKE`
|
||||
- Dimensions: `constants/map_constants.asm:135` `map_const DARK_CAVE_VIOLET_ENTRANCE, 20, 18`
|
||||
(x 0..39, y 0..35), map id 70, group 3
|
||||
- Attributes: `data/maps/attributes.asm:467`, border block `$09`, **no connections**
|
||||
- Map events block: `DarkCaveVioletEntrance_MapEvents` = `47:42f5`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 15 | `ROUTE_31` | 3 |
|
||||
| 2 | 17 | 1 | `DARK_CAVE_BLACKTHORN_ENTRANCE` | 2 |
|
||||
| 3 | 35 | 33 | `ROUTE_46` | 3 |
|
||||
|
||||
**Coord events** - empty.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 26 | 3 | `BGEVENT_ITEM` | `DarkCaveVioletEntranceHiddenElixer` -> `hiddenitem ELIXER, EVENT_DARK_CAVE_VIOLET_ENTRANCE_HIDDEN_ELIXER` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `DARKCAVEVIOLETENTRANCE_POKE_BALL1` | `SPRITE_POKE_BALL` | 6 | 8 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `DarkCaveVioletEntrancePotion` (`itemball POTION`) | `EVENT_DARK_CAVE_VIOLET_ENTRANCE_POTION` |
|
||||
| `DARKCAVEVIOLETENTRANCE_ROCK1` | `SPRITE_ROCK` | 16 | 14 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `DarkCaveVioletEntranceRock` (`jumpstd SmashRockScript`) | `-1` |
|
||||
| `DARKCAVEVIOLETENTRANCE_ROCK2` | `SPRITE_ROCK` | 27 | 6 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `DarkCaveVioletEntranceRock` | `-1` |
|
||||
| `DARKCAVEVIOLETENTRANCE_ROCK3` | `SPRITE_ROCK` | 7 | 14 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `DarkCaveVioletEntranceRock` | `-1` |
|
||||
| `DARKCAVEVIOLETENTRANCE_ROCK4` | `SPRITE_ROCK` | 36 | 31 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT` | `DarkCaveVioletEntranceRock` | `-1` |
|
||||
| `DARKCAVEVIOLETENTRANCE_POKE_BALL2` | `SPRITE_POKE_BALL` | 36 | 22 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `DarkCaveVioletEntranceFullHeal` (`itemball FULL_HEAL`) | `EVENT_DARK_CAVE_VIOLET_ENTRANCE_FULL_HEAL` |
|
||||
| `DARKCAVEVIOLETENTRANCE_POKE_BALL3` | `SPRITE_POKE_BALL` | 35 | 9 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `DarkCaveVioletEntranceHyperPotion` (`itemball HYPER_POTION`) | `EVENT_DARK_CAVE_VIOLET_ENTRANCE_HYPER_POTION` |
|
||||
|
||||
Note the four rocks all share one script label; the smash `disappear LAST_TALKED`
|
||||
in `RockSmashScript` acts on `hLastTalked`, so they are independent objects
|
||||
despite the shared pointer. Rocks have **no event flag** (`-1`) - they come back
|
||||
on every map reload.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
`DarkCaveVioletEntranceRock` - `jumpstd SmashRockScript`
|
||||
(`engine/events/std_scripts.asm:199` -> `farsjump AskRockSmashScript`, `03:4f60`).
|
||||
`AskRockSmashScript` calls `HasRockSmash` (`03:4f7f`), which is **inverted**
|
||||
(`wScriptVar = 1` when the party does NOT know ROCK SMASH); on 0 it prompts
|
||||
yes/no and falls into `RockSmashScript` (`03:4f35`):
|
||||
`playsound SFX_STRENGTH` / `earthquake 84` / `applymovementlasttalked MovementData_RockSmash`
|
||||
/ `disappear LAST_TALKED` / `callasm RockMonEncounter` / `randomwildmon` /
|
||||
`startbattle`.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
`data/wild/johto_grass.asm:1517` `def_grass_wildmons DARK_CAVE_VIOLET_ENTRANCE`,
|
||||
rate `4 percent` morn/day/nite, all three lists identical:
|
||||
|
||||
| slot | lvl | species |
|
||||
|---|---|---|
|
||||
| 1 | 3 | GEODUDE |
|
||||
| 2 | 2 | ZUBAT |
|
||||
| 3 | 2 | GEODUDE |
|
||||
| 4 | 4 | GEODUDE |
|
||||
| 5 | 3 | ZUBAT |
|
||||
| 6 | 4 | ZUBAT |
|
||||
| 7 | 4 | DUNSPARCE |
|
||||
|
||||
`data/wild/johto_water.asm:107` `def_water_wildmons DARK_CAVE_VIOLET_ENTRANCE`,
|
||||
rate `2 percent`: 15 MAGIKARP / 10 MAGIKARP / 5 MAGIKARP.
|
||||
|
||||
**Rock smash encounters** - this map IS in `data/wild/treemon_maps.asm:46`
|
||||
`RockMonMaps`: `treemon_map DARK_CAVE_VIOLET_ENTRANCE, TREEMON_SET_ROCK`.
|
||||
`RockMonEncounter` (`2e:63a1`) rolls `RandomRange 10 < 4` (a 40% chance) and then
|
||||
`SelectTreeMon` over `data/wild/treemons.asm:91` `TreeMonSet_Rock`:
|
||||
90% KRABBY lvl 15, 10% SHUCKLE lvl 15. **This is the only Shuckle source in
|
||||
Johto reachable in this section**, and the walkthrough does not mention it.
|
||||
|
||||
---
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Darkness on all six maps | `engine/tilesets/timeofday_pals.asm` `ReplaceTimeOfDayPals` (`23:43e9`) `.NeedsFlash` -> `DARKNESS_PALSET`, because `data/maps/maps.asm` lines 127-130 / 148-149 all say `PALETTE_DARK` | FLASH cast, i.e. `STATUSFLAGS_FLASH_F` set in `wStatusFlags` | `engine/events/overworld.asm` `FlashFunction.CheckUseFlash` (`03:48f1`): `ENGINE_ZEPHYRBADGE` **and** `wTimeOfDayPalset == DARKNESS_PALSET`. The bit is cleared by `home/flag.asm` `ResetFlashIfOutOfCave` (`00:2f1d`) on any `ROUTE`/`TOWN` map, so **re-cast after every exit and every Fly** |
|
||||
| Mt. Mortar middle entrance (`ROUTE_42` warp 4 at (28,9)) | terrain: the tile is across water on Route 42 | SURF | `engine/events/overworld.asm` `SurfFunction` (`03:493b`), `.TrySurf` arm / `TrySurfOW` (`03:4a06`): `ENGINE_FOGBADGE` + a party member knowing SURF |
|
||||
| The lake and the waterfall between 1F Outside and 2F | terrain on `MountMortar1FOutside.blk`; the only route to warp 4 at (17,5) | SURF, then WATERFALL | `WaterfallFunction.TryWaterfall` (`03:4af6`) / `TryWaterfallOW` (`03:4b5f`): `ENGINE_RISINGBADGE`, then `CheckMapCanWaterfall` - **player must be facing UP and the tile ABOVE (`wTileUp`) must be a waterfall tile** |
|
||||
| `MOUNTMORTAR1FINSIDE_BOULDER` at (21,43) | `MountMortar1FBoulder` -> `jumpstd StrengthBoulderScript` -> `AskStrengthScript` (`03:4d4e`) | STRENGTH toggled on | `TryStrengthOW` (`03:4d7b`): party knows STRENGTH **and** `ENGINE_PLAINBADGE` **and** `BIKEFLAGS_STRENGTH_ACTIVE_F` set. `StrengthFunction.TryStrength` (`03:4cf1`) only checks the badge. Note `ResetBikeFlags` clears the active bit on **every map load** - Strength must be re-activated per floor |
|
||||
| `MOUNTMORTARB1F_BOULDER` at (9,10) | `MountMortarB1FBoulder`, same std script | STRENGTH | as above. This is the walkthrough's "move the rock with HM Strength to get out"; the alternative is an Escape Rope |
|
||||
| Kiyo's Tyrogue | `MountMortarB1FKiyoScript` `readvar VAR_PARTYCOUNT` / `ifequal PARTY_LENGTH, .NoRoom` | 5 or fewer party members | free a slot and re-talk; `EVENT_BEAT_BLACKBELT_KIYO` stays set so the battle is not repeated |
|
||||
| Dark Cave water crossings (both entrances) | terrain | SURF | `ENGINE_FOGBADGE` as above |
|
||||
| `DARKCAVEVIOLETENTRANCE_ROCK1..4` | `DarkCaveVioletEntranceRock` -> `jumpstd SmashRockScript` -> `AskRockSmashScript` (`03:4f60`) | a party member knowing ROCK SMASH (TM08) | `HasRockSmash` (`03:4f7f`) - **no badge check at all** for Rock Smash |
|
||||
| Fly between Mahogany and Blackthorn | `engine/events/overworld.asm:545` (the `.TryFly` arm of the Fly jumptable) `ld de, ENGINE_STORMBADGE` | `ENGINE_STORMBADGE` + the destination's `ENGINE_FLYPOINT_*` (`constants/engine_flags.asm:87`, `:89`) | both flypoints are set by visiting the towns in earlier sections |
|
||||
|
||||
Badge constants used above: `ENGINE_ZEPHYRBADGE` (`constants/engine_flags.asm:38`),
|
||||
`ENGINE_PLAINBADGE` (`:40`), `ENGINE_FOGBADGE` (`:41`), `ENGINE_STORMBADGE`
|
||||
(`:43`), `ENGINE_RISINGBADGE` (`:45`). All are checked through
|
||||
`engine/events/overworld.asm` `CheckBadge` (line 50), which prints
|
||||
"Badge required" and returns carry.
|
||||
|
||||
**Nothing in this section gates anything else.** Both Mt. Mortar and Dark Cave
|
||||
are entirely optional; a bot that skips them loses only items, the Tyrogue and
|
||||
the Blackglasses.
|
||||
|
||||
---
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
Preconditions for the whole section: `ENGINE_ZEPHYRBADGE`, `ENGINE_FOGBADGE`,
|
||||
`ENGINE_PLAINBADGE`, `ENGINE_RISINGBADGE`, `ENGINE_STORMBADGE`; party members
|
||||
knowing FLASH, SURF, STRENGTH, WATERFALL (and ROCK SMASH for step 26); party
|
||||
count <= 5 before step 20.
|
||||
|
||||
| # | Map | Target | Intent | Precondition | Postcondition |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `MAHOGANY_TOWN` | spawn (15,14) | Fly in, heal, buy MAX_REPEL / ESCAPE_ROPE | `ENGINE_FLYPOINT_MAHOGANY` | party healthy, bag stocked |
|
||||
| 2 | `MAHOGANY_TOWN` | west map connection | walk west | - | on `ROUTE_42` |
|
||||
| 3 | `ROUTE_42` | warp 3 at (10,5) | step onto warp (west entrance) | - | on `MOUNT_MORTAR_1F_OUTSIDE` at (3,33) |
|
||||
| 4 | `MOUNT_MORTAR_1F_OUTSIDE` | - | use FLASH from the PACK/party menu | `ENGINE_ZEPHYRBADGE`, map palset is `DARKNESS_PALSET` | `STATUSFLAGS_FLASH_F` set |
|
||||
| 5 | `MOUNT_MORTAR_1F_OUTSIDE` | - | use MAX_REPEL | bag has one | encounters suppressed |
|
||||
| 6 | `MOUNT_MORTAR_1F_OUTSIDE` | object at (13,15) | walk onto / A-press the Poke Ball | `EVENT_MOUNT_MORTAR_1F_OUTSIDE_ETHER` clear | ETHER in bag, flag set |
|
||||
| 7 | `MOUNT_MORTAR_1F_OUTSIDE` | object at (31,18) | pick up Poke Ball | `EVENT_MOUNT_MORTAR_1F_OUTSIDE_REVIVE` clear | GUARD_SPEC in bag, flag set |
|
||||
| 8 | `MOUNT_MORTAR_1F_OUTSIDE` | bg event (25,22) | face the tile, press A | `EVENT_MOUNT_MORTAR_1F_OUTSIDE_HIDDEN_HYPER_POTION` clear | HYPER_POTION in bag, flag set |
|
||||
| 9 | `MOUNT_MORTAR_1F_OUTSIDE` | warp 5 at (11,21) | step on warp | - | on `MOUNT_MORTAR_1F_INSIDE` at (11,47) |
|
||||
| 10 | `MOUNT_MORTAR_1F_INSIDE` | - | re-cast FLASH only if it was lost (it is not - cave to cave keeps the bit) | - | - |
|
||||
| 11 | `MOUNT_MORTAR_1F_INSIDE` | object at (33,22) | pick up Poke Ball | `EVENT_MOUNT_MORTAR_1F_INSIDE_ESCAPE_ROPE` clear | ESCAPE_ROPE in bag |
|
||||
| 12 | `MOUNT_MORTAR_1F_INSIDE` | object at (12,21) | pick up Poke Ball | `EVENT_MOUNT_MORTAR_1F_INSIDE_HYPER_POTION` clear | HYPER_POTION in bag |
|
||||
| 13 | `MOUNT_MORTAR_1F_INSIDE` | boulder at (21,43) | activate STRENGTH from the party menu, then push | `ENGINE_PLAINBADGE`, party knows STRENGTH | `BIKEFLAGS_STRENGTH_ACTIVE_F` set, boulder movable |
|
||||
| 14 | `MOUNT_MORTAR_1F_INSIDE` | warp 1 at (11,47) | back out | - | on `MOUNT_MORTAR_1F_OUTSIDE` at (11,21) |
|
||||
| 15 | `MOUNT_MORTAR_1F_OUTSIDE` | the lake | walk into water / A-press facing water -> SURF | `ENGINE_FOGBADGE` | `wPlayerState = PLAYER_SURF` |
|
||||
| 16 | `MOUNT_MORTAR_1F_OUTSIDE` | the waterfall tile | face **UP** at the waterfall, press A -> WATERFALL | `ENGINE_RISINGBADGE`, `CheckMapCanWaterfall` passes | climbed |
|
||||
| 17 | `MOUNT_MORTAR_1F_OUTSIDE` | warp 4 at (17,5) | step on warp | - | on `MOUNT_MORTAR_2F_INSIDE` at (17,33) |
|
||||
| 18 | `MOUNT_MORTAR_2F_INSIDE` | objects at (31,23), (2,24), (19,17), (14,5), (8,9), (28,5) | pick up all six Poke Balls | matching `EVENT_MOUNT_MORTAR_2F_INSIDE_*` clear | MAX_POTION, RARE_CANDY, TM_DEFENSE_CURL (TM40), DRAGON_SCALE, ELIXER, ESCAPE_ROPE |
|
||||
| 19 | `MOUNT_MORTAR_2F_INSIDE` | bg event (19,6) | face, press A | `EVENT_MOUNT_MORTAR_2F_INSIDE_HIDDEN_FULL_RESTORE` clear | FULL_RESTORE in bag |
|
||||
| 20 | `MOUNT_MORTAR_2F_INSIDE` | warp 2 at (3,5) | step on warp | - | on `MOUNT_MORTAR_1F_INSIDE` at (9,9) |
|
||||
| 21 | `MOUNT_MORTAR_1F_INSIDE` | object at (16,10) | pick up Poke Ball | `EVENT_MOUNT_MORTAR_1F_INSIDE_MAX_REVIVE` clear | MAX_REVIVE in bag |
|
||||
| 22 | `MOUNT_MORTAR_1F_INSIDE` | bg event (31,9) | face, press A | `EVENT_MOUNT_MORTAR_1F_INSIDE_HIDDEN_MAX_REPEL` clear | MAX_REPEL in bag |
|
||||
| 23 | `MOUNT_MORTAR_1F_INSIDE` | warp 5 at (3,19) | step on warp | - | on `MOUNT_MORTAR_B1F` at (3,3) |
|
||||
| 24 | `MOUNT_MORTAR_B1F` | bg (4,6), objects (4,16) and (31,17) | pick up hidden MAX_REVIVE, FULL_HEAL, HYPER_POTION | matching `EVENT_MOUNT_MORTAR_B1F_*` clear | three items |
|
||||
| 25 | `MOUNT_MORTAR_B1F` | `MOUNTMORTARB1F_KIYO` at (13,4) | stand adjacent, press A | party count <= 5, `EVENT_GOT_TYROGUE_FROM_KIYO` clear | battle `BLACKBELT_T`/`KIYO` (L34 HITMONLEE, L34 HITMONCHAN) -> `EVENT_BEAT_BLACKBELT_KIYO` |
|
||||
| 26 | `MOUNT_MORTAR_B1F` | Kiyo (same object, script continues) | press A through the reward text | party count <= 5 | `givepoke TYROGUE, 10`, `EVENT_GOT_TYROGUE_FROM_KIYO` set |
|
||||
| 27 | `MOUNT_MORTAR_B1F` | - | use ESCAPE_ROPE (or STRENGTH the boulder at (9,10) then warp 2 at (19,29)) | ESCAPE_ROPE in bag | out of the cave |
|
||||
| 28 | anywhere outdoors | `SPAWN_BLACKTHORN` | Fly to Blackthorn, heal | `ENGINE_STORMBADGE`, `ENGINE_FLYPOINT_BLACKTHORN` | at Blackthorn spawn (21,30); **`STATUSFLAGS_FLASH_F` is now cleared** |
|
||||
| 29 | `BLACKTHORN_CITY` | south map connection | walk south | - | on `ROUTE_45` |
|
||||
| 30 | `ROUTE_45` | warp 1 at (2,5) | step on warp | - | on `DARK_CAVE_BLACKTHORN_ENTRANCE` at (23,3) |
|
||||
| 31 | `DARK_CAVE_BLACKTHORN_ENTRANCE` | - | use FLASH again | `ENGINE_ZEPHYRBADGE` | `STATUSFLAGS_FLASH_F` set |
|
||||
| 32 | `DARK_CAVE_BLACKTHORN_ENTRANCE` | water in the middle of the map | SURF | `ENGINE_FOGBADGE` | crossing possible |
|
||||
| 33 | `DARK_CAVE_BLACKTHORN_ENTRANCE` | `DARKCAVEBLACKTHORNENTRANCE_PHARMACIST` at (7,3) | stand adjacent, press A | `EVENT_GOT_BLACKGLASSES_IN_DARK_CAVE` clear, item pocket has room | BLACKGLASSES, flag set |
|
||||
| 34 | `DARK_CAVE_BLACKTHORN_ENTRANCE` | objects at (7,22) and (21,24) | pick up both Poke Balls | matching events clear | TM_SNORE (TM13), REVIVE |
|
||||
| 35 | `DARK_CAVE_BLACKTHORN_ENTRANCE` | warp 2 at (3,25) | step on warp | - | on `DARK_CAVE_VIOLET_ENTRANCE` at (17,1) |
|
||||
| 36 | `DARK_CAVE_VIOLET_ENTRANCE` | water below (17,1) | SURF down | `ENGINE_FOGBADGE` | - |
|
||||
| 37 | `DARK_CAVE_VIOLET_ENTRANCE` | rock at (16,14) or (7,14) | face rock, press A, answer yes | party knows ROCK_SMASH | rock gone; 40% chance of a KRABBY/SHUCKLE L15 battle |
|
||||
| 38 | `DARK_CAVE_VIOLET_ENTRANCE` | object at (6,8) | pick up Poke Ball | `EVENT_DARK_CAVE_VIOLET_ENTRANCE_POTION` clear | POTION in bag |
|
||||
| 39 | `DARK_CAVE_VIOLET_ENTRANCE` | warp 1 at (3,15) | step on warp | - | on `ROUTE_31` at (34,5); **FLASH bit cleared** |
|
||||
| 40 | `ROUTE_31` | - | Fly back to Blackthorn, heal | - | section complete |
|
||||
|
||||
Optional extras this route skips, with their coordinates if a completionist bot
|
||||
wants them: `DARK_CAVE_VIOLET_ENTRANCE` objects at (36,22) FULL_HEAL and (35,9)
|
||||
HYPER_POTION plus the hidden ELIXER bg at (26,3) - all three sit on the
|
||||
north-east lobe of the map that the earlier Route 31 section already reaches.
|
||||
|
||||
---
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Map headers / dimensions for all six maps | `tools/rom_manifest_gold.json` (e.g. `MOUNT_MORTAR_1F_OUTSIDE` 20x18 group 3 map 49, `DARK_CAVE_BLACKTHORN_ENTRANCE` 15x18 group 3 map 71), read by `src/import/RomExtractorGen2.lua` | implemented (extracted from ROM, no hand-ported map file needed) |
|
||||
| Warp / coord / bg / object event tables | `src/import/RomExtractorGen2.lua` (`warps`, `coordEvents`, `bgEvents`, `objects` around lines 787-862) -> `src/world/gen2/Map.lua` | implemented |
|
||||
| `PALETTE_DARK` -> `DARKNESS_PALSET` and the FLASH override | `src/world/gen2/Palettes.lua`, asserted by `tests/gen2_palettes_test.lua:118-129` (`dark cave` / `dark cave with flash`) | implemented |
|
||||
| `ResetFlashIfOutOfCave` (FLASH dies on ROUTE/TOWN) | `src/world/gen2/World.lua:5622-5625` | implemented |
|
||||
| FLASH / SURF / WATERFALL / STRENGTH badge gates | `src/world/gen2/FieldMoves.lua:105-110` (`FLASH=ZEPHYR`, `SURF=FOG`, `STRENGTH=PLAIN`, `WATERFALL=RISING`), `FieldMoves.flashFromMenu` / `surfFromMenu` / `waterfallFromMenu` / `strengthFromMenu` | implemented |
|
||||
| `CheckMapCanWaterfall` (facing UP, `wTileUp`) and the climb loop | `src/world/gen2/FieldMoves.lua:253-266`, `src/world/gen2/World.lua:4030-4046` `runWaterfall` / `waterfallStep`, `World:tryWaterfallOW` (4217) | implemented |
|
||||
| Strength boulder push (`StrengthBoulderScript` -> `AskStrengthScript`) | `src/world/gen2/World.lua:5268-5274` (the boulder arm of `World:interact`), `World:tryStrengthOW`, driver `tests/drivers/gold_icepath_boulder.lua` | implemented |
|
||||
| `BIKEFLAGS_STRENGTH_ACTIVE_F` cleared on map load | `src/world/gen2/World.lua:5611-5613` | implemented |
|
||||
| Hidden items (`BGEVENT_ITEM` / `hiddenitem`) | `src/world/gen2/HiddenItems.lua`, wired at `src/world/gen2/World.lua:5290-5296` | implemented |
|
||||
| `verbosegiveitem BLACKGLASSES` (the Dark Cave pharmacist) | `src/script/gen2/Vm.lua:490-510`, hook `World.lua:831` `giveItem` | implemented |
|
||||
| `givepoke TYROGUE, 10` (Kiyo's reward) | `src/script/gen2/Vm.lua:439-445`, hook `World.lua:812` `givePoke` | implemented |
|
||||
| `readvar VAR_PARTYCOUNT` (Kiyo's "no room" branch) | `src/world/gen2/World.lua:112` `VAR_PARTYCOUNT = 0x01`, read at `World.lua:1225` | implemented |
|
||||
| `loadtrainer` / `startbattle` / `winlosstext` / `reloadmapafterbattle` | `src/script/gen2/Vm.lua:806`, `:817`, `:886`, `:918`; `src/world/gen2/Trainers.lua`; driver `tests/drivers/gold_trainer_smoke.lua` | implemented |
|
||||
| Tyrogue's three-way stat evolution (what the reward mon becomes) | `src/core/gen2/Evolution.lua:71`, asserted by `tests/gen2_evolution_test.lua:347-357` | implemented |
|
||||
| Repel / Max Repel step counter | `src/world/gen2/World.lua:3382` `World:useRepel`, `src/ui/gen2/PackMenu.lua:199-206` | implemented |
|
||||
| **Picking up a Poke Ball (`OBJECTTYPE_ITEMBALL`)** | extracted into `obj.itemball` by `src/import/RomExtractorGen2.lua:2968-2969`, but `World:interact` (`src/world/gen2/World.lua:5256-5310`) has arms for trainer, boulder, `scriptKey`, `BGEVENT_READ` and `BGEVENT_ITEM` **and no itemball arm**; `CallAsm.lua:551` stubs `TryReceiveItem` out | **missing** - all 15 Poke Balls in this section are unobtainable in the port today |
|
||||
| **ROCK SMASH as a field move** | `src/script/gen2/CallAsm.lua:346-350` implements `HasRockSmash`, but line 526 stubs `RockMonEncounter` with the note "ROCK SMASH has no field-move path yet; RockMonMaps is unported"; `World:interact`'s tile-event chain (`World.lua:5301-5308`) lists cut / whirlpool / waterfall / headbutt / surf and **not** rock smash, and nothing in `src/` references `SPRITEMOVEDATA_SMASHABLE_ROCK` | **missing** - the four Dark Cave Violet rocks cannot be broken, and the Krabby/Shuckle table is unreachable |
|
||||
| **ESCAPE_ROPE / DIG as a field item** | `src/world/gen2/World.lua:3293-3302` `World:useFieldItem` handles ITEMFINDER, BICYCLE, SACRED_ASH, repels, trophy boxes and rods only. (Gen 1's path exists at `src/ui/BagMenu.lua:231-240`, but that is the Red/Blue engine, not the Gen 2 one.) | **missing** - the walkthrough's "use one of the Escape Ropes to get out" has no port path; the Strength-boulder exit at B1F (9,10) does work |
|
||||
| Fly to Mahogany / Blackthorn | `src/world/gen2/FieldMoves.lua:394-440` `hasVisitedSpawn` / `flyPoints`, `flyFromMenu` (504) | implemented |
|
||||
| Wild encounter tables (grass + water + fishing) for these maps | extracted; `src/world/gen2/FieldMoves.lua:162-177` `canEncounterWildMon` / `encounterTable` | implemented (not separately verified against these six maps) |
|
||||
|
||||
No driver in `tests/drivers/gold_*.lua` visits Mt. Mortar or Dark Cave.
|
||||
|
||||
---
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
1. **The walkthrough's Mt. Mortar item list is the HGSS list, not the GS list.**
|
||||
It names Carbos, Escape Rope x2, Full Restore, Hyper Potion x2, Max Ether,
|
||||
Max Potion, Max Revive, PP Up, Rare Candy, Dragon Scale and "TM40 Aerial
|
||||
Ace". What is actually in `maps/MountMortar*.asm` is: ETHER, GUARD_SPEC,
|
||||
hidden HYPER_POTION (1F Outside); ESCAPE_ROPE, MAX_REVIVE, HYPER_POTION,
|
||||
hidden MAX_REPEL (1F Inside); MAX_POTION, RARE_CANDY, TM_DEFENSE_CURL,
|
||||
DRAGON_SCALE, ELIXER, ESCAPE_ROPE, hidden FULL_RESTORE (2F Inside);
|
||||
HYPER_POTION, FULL_HEAL, hidden MAX_REVIVE (B1F). **No CARBOS, no PP_UP, no
|
||||
MAX_ETHER, no IRON anywhere in Mt. Mortar.** TM40 is the right *number* but
|
||||
the move is DEFENSE_CURL (`constants/item_constants.asm:261`); Aerial Ace is
|
||||
a Gen 3 move that does not exist in this disassembly.
|
||||
2. **"grab the free Iron on the cliff" (1F Inside, after coming down from 2F).**
|
||||
There is no IRON object on any Mt. Mortar map. The nearest items to warp 6 at
|
||||
(9,9) are the MAX_REVIVE ball at (16,10) and the hidden MAX_REPEL bg event at
|
||||
(31,9). Treat "Iron" as an HGSS-only pickup.
|
||||
3. **"surf clockwise around and reach a Hyper Potion and a Max Ether."** There is
|
||||
no MAX_ETHER in Mt. Mortar. If the intended floor is B1F (the ladder from the
|
||||
central entrance) the pair is HYPER_POTION (31,17) + FULL_HEAL (4,16); if 2F,
|
||||
the closest analogue is ELIXER (8,9). Which one the author meant could not be
|
||||
determined from the asm.
|
||||
4. **Kiyo's prize money and EXP yields.** The walkthrough gives 816G and
|
||||
1012/1020 EXP. `data/trainers/parties.asm` only carries the class, name, type
|
||||
byte and party; the money formula lives in the battle engine's
|
||||
`data/trainers/attributes.asm` base-money table times level, which was not
|
||||
opened for this document. The party itself is verified: L34 HITMONLEE, L34
|
||||
HITMONCHAN, `TRAINERTYPE_NORMAL` (default moves, no held items).
|
||||
5. **"a giant room where you can zig zag around ... and get two items, to include
|
||||
a Hyper Potion and Escape Rope."** `MOUNT_MORTAR_1F_INSIDE` actually holds
|
||||
*three* balls (ESCAPE_ROPE (33,22), MAX_REVIVE (16,10), HYPER_POTION (12,21))
|
||||
plus a hidden MAX_REPEL - but MAX_REVIVE and the hidden item sit on the
|
||||
section of the map only reachable from 2F, which is presumably why the
|
||||
walkthrough counts two.
|
||||
6. **"The west entrance and east entrance lead to each other."** All three Route
|
||||
42 doors land on the same map, `MOUNT_MORTAR_1F_OUTSIDE`; the west/east link
|
||||
runs through `MOUNT_MORTAR_1F_INSIDE` via warp pairs 5/6 (at y=21) and 8/9
|
||||
(at y=13). The blk files were not decoded, so the exact walkable path between
|
||||
any two of these warps is not verified here - only the warp graph is.
|
||||
7. **Dark Cave Pokemon list.** The walkthrough lists Zubat, Golbat, Geodude,
|
||||
Graveler, Magikarp and Wobbuffet - that is the `DARK_CAVE_BLACKTHORN_ENTRANCE`
|
||||
table exactly. It omits DUNSPARCE, which is slot 7 of
|
||||
`DARK_CAVE_VIOLET_ENTRANCE` and is on the map the route exits through.
|
||||
8. **Dark Cave item list.** The walkthrough lists Blackglasses, Full Heal, Hyper
|
||||
Potion, Revive and TM13. The Full Heal (36,22) and Hyper Potion (35,9) are on
|
||||
`DARK_CAVE_VIOLET_ENTRANCE`'s north-east lobe, which this route does not pass
|
||||
through; the hidden ELIXER at (26,3) is not mentioned at all. The POTION at
|
||||
(6,8) is named in the prose but missing from the list.
|
||||
9. **The Shuckle/Krabby rock-smash table** (`data/wild/treemon_maps.asm:46`,
|
||||
`data/wild/treemons.asm:91`) is real and reachable at
|
||||
`DARK_CAVE_VIOLET_ENTRANCE`, but the walkthrough never mentions it. Worth
|
||||
flagging to a bot author as a cheap Shuckle source.
|
||||
10. **`MOUNT_MORTAR_1F_OUTSIDE` object 2 flag naming.** The item is
|
||||
`GUARD_SPEC` but the event flag is `EVENT_MOUNT_MORTAR_1F_OUTSIDE_REVIVE`
|
||||
(`constants/event_flags.asm:1053`). Verbatim from the disassembly; not a
|
||||
transcription error here, but it will look like one to anyone grepping by
|
||||
item name.
|
||||
11. **Mahogany Town and Blackthorn City interiors** (Poke Center, Mart, gym) were
|
||||
not opened for this document - only their `data/maps/maps.asm` header rows
|
||||
and `data/maps/spawn_points.asm` entries. Those maps belong to neighbouring
|
||||
sections.
|
||||
@@ -0,0 +1,599 @@
|
||||
# Section 17 - Victory Road
|
||||
|
||||
Source: `../section-17-victory-road.txt` (the FAQ numbers this chapter "23 > Victory Road")
|
||||
Maps covered: `MAP_VICTORY_ROAD`, `MAP_VICTORY_ROAD_GATE` (entry gate, badge check)
|
||||
Badges / key milestones in this section: no badge. Milestones are the fifth and
|
||||
final `RIVAL1` battle (`EVENT_RIVAL_VICTORY_ROAD`), TM26 Earthquake, and the
|
||||
north exit onto Route 23 / Indigo Plateau.
|
||||
|
||||
Important structural fact the walkthrough hides: **Gold/Silver Victory Road is a
|
||||
single 10x36-block map**, not three floors. What the FAQ calls "first / second /
|
||||
third floor" are three vertically stacked regions of the same map joined by
|
||||
`warp_event` rows whose destination map is `VICTORY_ROAD` itself. Every "ladder"
|
||||
and the "hole" are self-warps inside `maps/VictoryRoad.asm`.
|
||||
|
||||
## 1. Route order
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 0 | `MAP_VICTORY_ROAD_GATE` | `maps/VictoryRoadGate.asm` | gate warps 3/4 at (9,17)/(10,17), arriving from Route 26 (`maps/Route26.asm:397` warps to `VICTORY_ROAD_GATE, 3`) | gate warps 5/6 at (9,0)/(10,0) -> `VICTORY_ROAD` warp 1 | 8-badge check at coord event (10,11); belongs to the previous section but is the only door in |
|
||||
| 1 | `MAP_VICTORY_ROAD` (entrance region, y 48..71) | `maps/VictoryRoad.asm` | warp 1 at (9,67) | warp 2, the ladder at (1,49) | "Head up the stairway and up the cliff"; Full Heal (15,48) and Max Revive (12,48) sit here, plus the hidden Full Heal at (3,65) |
|
||||
| 2 | `MAP_VICTORY_ROAD` (mid region, y 32..43) | same | warp 3 at (1,35) | warp 4, the ladder at (13,31) | "second floor" - X Special at (7,38), then "up along the path ... another ladder" |
|
||||
| 3 | `MAP_VICTORY_ROAD` (top region, y 5..21) | same | warp 5 at (13,17) | warp 7, the ladder at (17,19) | "Save when you get to the third floor. Go up the cliff to your left, go down the ladder" |
|
||||
| 4 | `MAP_VICTORY_ROAD` (Full Restore shelf, y 28..34) | same | warp 6 at (17,33) | one-way `HOP_DOWN` ledge row y=34, x=16..19, landing on y=36 | "grab the Full Restore, and jump down" |
|
||||
| 5 | `MAP_VICTORY_ROAD` (mid region again) | same | ledge landing (x,36) | warp 4 at (13,31) | "Head back up the ladder to the third floor" |
|
||||
| 6 | `MAP_VICTORY_ROAD` (top region) | same | warp 5 at (13,17) | coord events at (12,8) / (13,8) | rival ambush, `VictoryRoadRivalLeft` / `VictoryRoadRivalRight` |
|
||||
| 7 | `MAP_VICTORY_ROAD` (TM pocket, y 26..30) | same | warp 8, the `COLL_PIT` at (0,11) | warp 8's landing is warp 9 at (0,27); leave by `HOP_RIGHT` at (8,28)->(10,28) or `HOP_DOWN` at (2,30)/(3,30)->y=32 | "go left instead of out the door, and fall down the hole to get TM26 Earthquake" |
|
||||
| 8 | `MAP_VICTORY_ROAD` exit | same | corridor x=12..13 at y=7..8, x=11..14 at y=6 | warp 10 at (13,5) -> `ROUTE_23` warp 3 | leave the dungeon |
|
||||
| 9 | `MAP_ROUTE_23` | `maps/Route23.asm` | warp 3 at (9,13) | - | **next section** (Indigo Plateau / Pokemon League). `Route23FlypointCallback` sets `ENGINE_FLYPOINT_INDIGO_PLATEAU` on arrival. Stop here. |
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_VICTORY_ROAD
|
||||
|
||||
- Script: `maps/VictoryRoad.asm` (included from `data/maps/scripts.asm:24`)
|
||||
- Blocks: `maps/VictoryRoad.blk` (360 bytes; `data/maps/blocks.asm:962`)
|
||||
- Header: `data/maps/maps.asm:160`
|
||||
`map VictoryRoad, TILESET_CAVE, CAVE, LANDMARK_VICTORY_ROAD, MUSIC_VICTORY_ROAD, TRUE, PALETTE_NITE, FISHGROUP_SHORE`
|
||||
(per the `map` macro at the head of `data/maps/maps.asm`: tileset, environment,
|
||||
location, music, phone-service flag TRUE = phone calls suppressed, time-of-day
|
||||
palette, fishing group.)
|
||||
- Attributes: `data/maps/attributes.asm:494` `map_attributes VictoryRoad, VICTORY_ROAD, $1d` - border block `$1d`, and **no `connection` rows follow**, so the map has no overworld connections.
|
||||
- Dimensions: `constants/map_constants.asm:147` `map_const VICTORY_ROAD, 10, 36` -> 10x36 blocks = **20x72 walk cells**. Group `DUNGEONS` (group 3), map id 82.
|
||||
- Scene variable: `data/maps/scenes.asm:60` `scene_var VICTORY_ROAD, wVictoryRoadSceneID` (sym `01:d6eb`)
|
||||
- Callbacks: `def_callbacks` is empty - no `MAPCALLBACK_*` for this map.
|
||||
- Connections: none.
|
||||
|
||||
**Warps** (`def_warp_events`, transcribed from `maps/VictoryRoad.asm:242-252`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 9 | 67 | `VICTORY_ROAD_GATE` | 5 |
|
||||
| 2 | 1 | 49 | `VICTORY_ROAD` | 3 |
|
||||
| 3 | 1 | 35 | `VICTORY_ROAD` | 2 |
|
||||
| 4 | 13 | 31 | `VICTORY_ROAD` | 5 |
|
||||
| 5 | 13 | 17 | `VICTORY_ROAD` | 4 |
|
||||
| 6 | 17 | 33 | `VICTORY_ROAD` | 7 |
|
||||
| 7 | 17 | 19 | `VICTORY_ROAD` | 6 |
|
||||
| 8 | 0 | 11 | `VICTORY_ROAD` | 9 |
|
||||
| 9 | 0 | 27 | `VICTORY_ROAD` | 8 |
|
||||
| 10 | 13 | 5 | `ROUTE_23` | 3 |
|
||||
|
||||
Self-warp pairs, as a bot should read them: 2<->3, 4<->5, 6<->7, 8->9.
|
||||
Warp 8's tile is `COLL_PIT` and warp 9's tile is plain `COLL_FLOOR`, so **warp 9
|
||||
never fires**: the hole at (0,11) is one-way down to (0,27). (Tile kinds verified
|
||||
below.)
|
||||
|
||||
**Coord events** (`def_coord_events`, `maps/VictoryRoad.asm:254-256`)
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_VICTORYROAD_RIVAL_BATTLE` (0) | 12 | 8 | `VictoryRoadRivalLeft` (sym `42:59ba`) | rival is teleported to (18,11), walks LEFTx6 + UPx2 to (12,9), battle, then leaves DOWNx2 + RIGHTx6 |
|
||||
| `SCENE_VICTORYROAD_RIVAL_BATTLE` (0) | 13 | 8 | `VictoryRoadRivalRight` (sym `42:59dd`) | rival walks from (18,13) UPx2 + LEFTx5 + UPx2 to (13,9), battle, then leaves DOWNx2 + RIGHTx5 + DOWNx2 |
|
||||
|
||||
These two tiles are the entire width of the corridor that leads to the exit
|
||||
(y=8 is walkable only at x=6,7 and x=12,13), so the ambush is unavoidable.
|
||||
|
||||
**BG events** (`def_bg_events`, `maps/VictoryRoad.asm:258-260`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 3 | 29 | `BGEVENT_ITEM` | `VictoryRoadHiddenMaxPotion` = `hiddenitem MAX_POTION, EVENT_VICTORY_ROAD_HIDDEN_MAX_POTION` |
|
||||
| 3 | 65 | `BGEVENT_ITEM` | `VictoryRoadHiddenFullHeal` = `hiddenitem FULL_HEAL, EVENT_VICTORY_ROAD_HIDDEN_FULL_HEAL` |
|
||||
|
||||
Both target tiles are `COLL_WALL`, i.e. the player stands next to them and
|
||||
presses A into the wall. (3,29) is faced from (2,29), (4,29) or (3,28);
|
||||
(3,65) is faced from (2,65), (4,65), (3,64) or (3,66).
|
||||
|
||||
**Object events** (`def_object_events`, `maps/VictoryRoad.asm:262-268`)
|
||||
|
||||
Column order follows the `object_event` macro in `macros/scripts/maps.asm:113`:
|
||||
x, y, sprite, movement, radius x, radius y, hour1, hour2, palette, type, sight,
|
||||
script, event flag.
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `VICTORYROAD_RIVAL` | `SPRITE_RIVAL` | 18 | 13 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` (shared ROM0 stub, sym `00:2812`) | `EVENT_RIVAL_VICTORY_ROAD` |
|
||||
| `VICTORYROAD_POKE_BALL1` | `SPRITE_POKE_BALL` | 3 | 28 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `VictoryRoadTMEarthquake` = `itemball TM_EARTHQUAKE` | `EVENT_VICTORY_ROAD_TM_EARTHQUAKE` |
|
||||
| `VICTORYROAD_POKE_BALL2` | `SPRITE_POKE_BALL` | 12 | 48 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `VictoryRoadMaxRevive` = `itemball MAX_REVIVE` | `EVENT_VICTORY_ROAD_MAX_REVIVE` |
|
||||
| `VICTORYROAD_POKE_BALL3` | `SPRITE_POKE_BALL` | 18 | 29 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `VictoryRoadFullRestore` = `itemball FULL_RESTORE` | `EVENT_VICTORY_ROAD_FULL_RESTORE` |
|
||||
| `VICTORYROAD_POKE_BALL4` | `SPRITE_POKE_BALL` | 15 | 48 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `VictoryRoadFullHeal` = `itemball FULL_HEAL` | `EVENT_VICTORY_ROAD_FULL_HEAL` |
|
||||
| `VICTORYROAD_POKE_BALL5` | `SPRITE_POKE_BALL` | 7 | 38 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `VictoryRoadXSpecial` = `itemball X_SPECIAL` | `EVENT_VICTORY_ROAD_X_SPECIAL` |
|
||||
|
||||
All rows use radius 0,0, hours -1,-1 (always present), palette 0, sight 0.
|
||||
|
||||
Object-flag polarity (this bites bot authors): `CheckObjectFlag` in
|
||||
`engine/overworld/map_objects_2.asm:32` masks an object when its event flag is
|
||||
**set**, and shows it when the flag is clear or is `-1`. `Script_appear` clears
|
||||
the flag, `Script_disappear` sets it (`engine/overworld/scripting.asm:879-897`).
|
||||
`InitializeEventsScript` in `engine/events/std_scripts.asm:521` sets
|
||||
`EVENT_RIVAL_VICTORY_ROAD` at new game, so the rival object starts hidden and is
|
||||
only revealed by the `appear` inside the ambush script.
|
||||
|
||||
**Derived collision map** (not verbatim asm - built by decoding
|
||||
`maps/VictoryRoad.blk` through `data/tilesets/cave_collision.asm` and
|
||||
`data/collision/collision_permissions.asm`, with the quadrant index
|
||||
`(x & 1) + 2 * (y & 1)` taken from `GetCoordTileCollision` in
|
||||
`home/map.asm:2065`). It is included because Victory Road is the one map in this
|
||||
section where "which tile can I stand on" is the whole problem.
|
||||
|
||||
Verification: all ten `warp_event` tiles land on the expected collision
|
||||
(`WARP_CARPET_DOWN`, six `LADDER`, `PIT`, `FLOOR`, `CAVE`) and all six
|
||||
`object_event` tiles land on walkable tiles, so the decode is aligned.
|
||||
|
||||
Legend: `.` floor, `#` wall (impassable), `^` `COLL_UP_WALL` (walkable, but you
|
||||
cannot step *down* into it and cannot step *up* off it), `L` ladder, `P` pit,
|
||||
`C` cave exit, `W` warp carpet (needs a DOWN press), `<` `HOP_LEFT`,
|
||||
`>` `HOP_RIGHT`, `v` `HOP_DOWN` (all three are walkable land; pressing the
|
||||
matching direction while standing on one jumps two tiles - see `.TryJump` and
|
||||
`.ledge_table` in `engine/overworld/player_movement.asm:354-391`, which reads the
|
||||
tile the player is **standing on**, not the tile ahead).
|
||||
|
||||
```
|
||||
01234567890123456789
|
||||
0 ....................
|
||||
1 ....................
|
||||
2 ....................
|
||||
3 ....................
|
||||
4 ....................
|
||||
5 #############C######
|
||||
6 ###########....#####
|
||||
7 ############..######
|
||||
8 ######..####..######
|
||||
9 #####....##....#####
|
||||
10 ......##...........#
|
||||
11 P....####...........
|
||||
12 ...######^^^^^^^^#..
|
||||
13 #########........#..
|
||||
14 #^^^^^^##........#..
|
||||
15 #......##..#####.#..
|
||||
16 #......##..#........
|
||||
17 ###.#..##..#.L..####
|
||||
18 #...#......#####..##
|
||||
19 #...##########...L##
|
||||
20 ##......>#.......###
|
||||
21 ###.....>#.....#####
|
||||
22 ####################
|
||||
23 ####################
|
||||
24 ####################
|
||||
25 ####################
|
||||
26 #...################
|
||||
27 .......#############
|
||||
28 ........>#...#######
|
||||
29 ...#....>#....####.#
|
||||
30 ##vv..#####...####..
|
||||
31 ###########..L###...
|
||||
32 ##..########..##....
|
||||
33 ....########..##.L..
|
||||
34 ..#^^^^^^^^#...#vvvv
|
||||
35 .L#........#...#####
|
||||
36 ..#........#........
|
||||
37 ..###.###..##.......
|
||||
38 ......#<#...^^^^^#..
|
||||
39 #..#..#<#........#..
|
||||
40 ##....#<#........#..
|
||||
41 ####..#<#####.####..
|
||||
42 #######.............
|
||||
43 ##########........##
|
||||
44 ####################
|
||||
45 ####################
|
||||
46 ####################
|
||||
47 ####################
|
||||
48 ....########.##..###
|
||||
49 .L..########.###..##
|
||||
50 #.....####........##
|
||||
51 #......#####..#....#
|
||||
52 #...#^^^^^^^^^^^^#..
|
||||
53 #...#............#..
|
||||
54 ##..#............#..
|
||||
55 ##..###.###..##.##..
|
||||
56 ##........#..##.....
|
||||
57 ##........#..##....#
|
||||
58 ##.##^^####..#######
|
||||
59 ##..#..####..#######
|
||||
60 ##vv#...^^...#######
|
||||
61 #####........#######
|
||||
62 ##..#........######^
|
||||
63 ##..###.###########.
|
||||
64 #....#..##.....####.
|
||||
65 #..#....#......####.
|
||||
66 ##............###^..
|
||||
67 ######...W##.####...
|
||||
68 #######^^#^^^^^^....
|
||||
69 #######.............
|
||||
70 ^^^^^^..............
|
||||
71 ....................
|
||||
```
|
||||
|
||||
One-way transitions a bot must respect (all derived from the grid above):
|
||||
|
||||
- (0,11) `PIT` -> (0,27). No route back up from (0,27); the return is a ledge.
|
||||
- (8,28) or (8,29) `HOP_RIGHT` + RIGHT -> (10,28) / (10,29), which reaches the (13,31) ladder. This is the short way out of the TM pocket.
|
||||
- (2,30) or (3,30) `HOP_DOWN` + DOWN -> (2,32) / (3,32), which drops into the pocket that holds the (1,35) ladder. This is the long way out of the TM pocket.
|
||||
- (16..19, 34) `HOP_DOWN` + DOWN -> (16..19, 36). This is the only exit from the Full Restore shelf, and it is why the FAQ says "jump down".
|
||||
- (8,20) or (8,21) `HOP_RIGHT` + RIGHT -> (10,20) / (10,21) in the top region.
|
||||
- Rows of `^` (`COLL_UP_WALL`) are walkable dead strips: you may enter them from the side or from below and leave sideways or downward, never upward, and you can never step down onto one.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `VictoryRoadRivalLeft` (`maps/VictoryRoad.asm:22`, sym `42:59ba`)
|
||||
`moveobject VICTORYROAD_RIVAL, 18, 11` / `turnobject PLAYER, DOWN` /
|
||||
`showemote EMOTE_SHOCK, PLAYER, 15` / `special FadeOutMusic` / `pause 15` /
|
||||
`appear VICTORYROAD_RIVAL` / `applymovement` `VictoryRoadRivalBattleApproachMovement1`
|
||||
(LEFT x6, UP x2) / `scall VictoryRoadRivalNext` /
|
||||
`applymovement VictoryRoadRivalBattleExitMovement1` (DOWN x2, RIGHT x6) /
|
||||
`disappear VICTORYROAD_RIVAL` / `setscene SCENE_VICTORYROAD_NOOP` /
|
||||
`playmapmusic` / `end`.
|
||||
- `VictoryRoadRivalRight` (`maps/VictoryRoad.asm:37`, sym `42:59dd`) - identical
|
||||
except there is no `moveobject` (he starts at his object_event position 18,13),
|
||||
the approach is `VictoryRoadRivalBattleApproachMovement2` (UP x2, LEFT x5,
|
||||
UP x2) and the exit is `VictoryRoadRivalBattleExitMovement2` (DOWN x2,
|
||||
RIGHT x5, DOWN x2).
|
||||
- `VictoryRoadRivalNext` (`maps/VictoryRoad.asm:51`, sym `42:59fc`) - the shared
|
||||
body. `turnobject PLAYER, DOWN`, `playmusic MUSIC_RIVAL_ENCOUNTER`, text
|
||||
`VictoryRoadRivalBeforeText`, then `setevent EVENT_RIVAL_VICTORY_ROAD`
|
||||
(**before** the battle, so a reset or blackout mid-battle still leaves the
|
||||
object hidden), then a three-way starter branch:
|
||||
- `checkevent EVENT_GOT_TOTODILE_FROM_ELM` -> `.GotTotodile` -> `loadtrainer RIVAL1, RIVAL1_5_CHIKORITA`
|
||||
- `checkevent EVENT_GOT_CHIKORITA_FROM_ELM` -> `.GotChikorita` -> `loadtrainer RIVAL1, RIVAL1_5_CYNDAQUIL`
|
||||
- fall-through (player picked Cyndaquil) -> `loadtrainer RIVAL1, RIVAL1_5_TOTODILE`
|
||||
|
||||
Each arm runs `winlosstext VictoryRoadRivalDefeatText, VictoryRoadRivalVictoryText`,
|
||||
`setlasttalked VICTORYROAD_RIVAL`, `startbattle`, `dontrestartmapmusic`,
|
||||
`reloadmapafterbattle`, then `.AfterBattle`: `playmusic MUSIC_RIVAL_AFTER` and
|
||||
`VictoryRoadRivalAfterText`.
|
||||
- `VictoryRoadTMEarthquake` / `MaxRevive` / `FullRestore` / `FullHeal` /
|
||||
`XSpecial` (`maps/VictoryRoad.asm:97-110`) - each is a bare
|
||||
`itemball <ITEM>` (two bytes: item, quantity 1; `macros/scripts/maps.asm:155`),
|
||||
not bytecode. Do not try to disassemble them.
|
||||
- `VictoryRoadHiddenMaxPotion` / `VictoryRoadHiddenFullHeal`
|
||||
(`maps/VictoryRoad.asm:112-116`) - `hiddenitem item, flag`, three bytes laid
|
||||
down as `dwb flag, item` (`macros/scripts/maps.asm:165`).
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_RIVAL_VICTORY_ROAD` | `constants/event_flags.asm:1124` | set by `InitializeEventsScript` (`engine/events/std_scripts.asm:521`) and again by `VictoryRoadRivalNext`; cleared by the `appear` in the ambush scripts | set = rival object masked. Postcondition of the ambush. |
|
||||
| `EVENT_VICTORY_ROAD_TM_EARTHQUAKE` | `constants/event_flags.asm:1092` | itemball object flag | set once TM26 is taken |
|
||||
| `EVENT_VICTORY_ROAD_MAX_REVIVE` | `constants/event_flags.asm:1093` | itemball object flag | set once Max Revive is taken |
|
||||
| `EVENT_VICTORY_ROAD_FULL_RESTORE` | `constants/event_flags.asm:1094` | itemball object flag | set once Full Restore is taken |
|
||||
| `EVENT_VICTORY_ROAD_FULL_HEAL` | `constants/event_flags.asm:1095` | itemball object flag | set once Full Heal is taken |
|
||||
| `EVENT_VICTORY_ROAD_X_SPECIAL` | `constants/event_flags.asm:1096` | itemball object flag | set once X Special is taken |
|
||||
| `EVENT_VICTORY_ROAD_HIDDEN_MAX_POTION` | `constants/event_flags.asm:168` | `hiddenitem` operand at bg (3,29) | set once dug up |
|
||||
| `EVENT_VICTORY_ROAD_HIDDEN_FULL_HEAL` | `constants/event_flags.asm:169` | `hiddenitem` operand at bg (3,65) | set once dug up |
|
||||
| `EVENT_GOT_TOTODILE_FROM_ELM` (`:37`), `EVENT_GOT_CHIKORITA_FROM_ELM` (`:38`) | `constants/event_flags.asm` | read by `VictoryRoadRivalNext` | selects which of the three rival parties loads. `EVENT_GOT_CYNDAQUIL_FROM_ELM` (`:36`) is never read here; Cyndaquil is the fall-through case |
|
||||
| `SCENE_VICTORYROAD_RIVAL_BATTLE` = 0 | defined inline by the `scene_script` macro (`macros/scripts/maps.asm:25`, `const_def` starting at 0) via `maps/VictoryRoad.asm:11` | compared by both coord events | the scene the map starts a new game on |
|
||||
| `SCENE_VICTORYROAD_NOOP` = 1 | same, `maps/VictoryRoad.asm:12` | written by `setscene` at the end of both ambush scripts | rival ambush disarmed |
|
||||
|
||||
Both scene scripts (`VictoryRoadNoop1Scene`, `VictoryRoadNoop2Scene`) are a bare
|
||||
`end`; the scene id only gates the coord events.
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `TM_EARTHQUAKE` (TM26 - counted off the `add_tm` list in `constants/item_constants.asm:218-246`) | Poke Ball at (3,28), only reachable through the pit at (0,11) | `VictoryRoadTMEarthquake` | `EVENT_VICTORY_ROAD_TM_EARTHQUAKE` |
|
||||
| `MAX_REVIVE` | Poke Ball at (12,48) | `VictoryRoadMaxRevive` | `EVENT_VICTORY_ROAD_MAX_REVIVE` |
|
||||
| `FULL_RESTORE` | Poke Ball at (18,29), reached by ladder warp 7 -> (17,33) then north | `VictoryRoadFullRestore` | `EVENT_VICTORY_ROAD_FULL_RESTORE` |
|
||||
| `FULL_HEAL` | Poke Ball at (15,48) | `VictoryRoadFullHeal` | `EVENT_VICTORY_ROAD_FULL_HEAL` |
|
||||
| `X_SPECIAL` | Poke Ball at (7,38), standing on the `HOP_LEFT` column; walk north from (7,42) | `VictoryRoadXSpecial` | `EVENT_VICTORY_ROAD_X_SPECIAL` |
|
||||
| `MAX_POTION` | hidden, face the wall at (3,29) | `bg_event 3, 29, BGEVENT_ITEM` | `EVENT_VICTORY_ROAD_HIDDEN_MAX_POTION` |
|
||||
| `FULL_HEAL` (second one) | hidden, face the wall at (3,65) | `bg_event 3, 65, BGEVENT_ITEM` | `EVENT_VICTORY_ROAD_HIDDEN_FULL_HEAL` |
|
||||
|
||||
**Trainers**
|
||||
|
||||
There are no `OBJECTTYPE_TRAINER` objects on this map. The single battle is a
|
||||
scripted `loadtrainer` with no `EVENT_BEAT_*` flag of its own.
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `RIVAL1_5_CHIKORITA` | `RIVAL1` (trainer class 9, `constants/trainer_constants.asm:51`) | 13th member of the class | `Rival1Group` "RIVAL1 (13)" (`data/trainers/parties.asm:164`, sym `0e:5a92` for the group) | `VictoryRoadRivalNext.GotTotodile` (sym `42:5a27`) | none |
|
||||
| `RIVAL1_5_CYNDAQUIL` | `RIVAL1` | 14th | `Rival1Group` "RIVAL1 (14)" (`data/trainers/parties.asm:174`) | `VictoryRoadRivalNext.GotChikorita` (sym `42:5a37`) | none |
|
||||
| `RIVAL1_5_TOTODILE` | `RIVAL1` | 15th | `Rival1Group` "RIVAL1 (15)" (`data/trainers/parties.asm:184`) | fall-through arm of `VictoryRoadRivalNext` | none |
|
||||
|
||||
Parties, verbatim (`TRAINERTYPE_MOVES`, name string `"?@"`):
|
||||
|
||||
RIVAL1 (13) - loaded when the player chose Totodile:
|
||||
|
||||
```
|
||||
db 34, SNEASEL, QUICK_ATTACK, SCREECH, FAINT_ATTACK, FURY_CUTTER
|
||||
db 36, GOLBAT, LEECH_LIFE, BITE, CONFUSE_RAY, WING_ATTACK
|
||||
db 35, MAGNETON, THUNDERSHOCK, SONICBOOM, THUNDER_WAVE, SWIFT
|
||||
db 35, HAUNTER, MEAN_LOOK, CURSE, SHADOW_BALL, CONFUSE_RAY
|
||||
db 35, KADABRA, DISABLE, PSYBEAM, RECOVER, FUTURE_SIGHT
|
||||
db 38, MEGANIUM, REFLECT, RAZOR_LEAF, POISONPOWDER, BODY_SLAM
|
||||
```
|
||||
|
||||
RIVAL1 (14) - loaded when the player chose Chikorita:
|
||||
|
||||
```
|
||||
db 34, SNEASEL, QUICK_ATTACK, SCREECH, FAINT_ATTACK, FURY_CUTTER
|
||||
db 36, GOLBAT, LEECH_LIFE, BITE, CONFUSE_RAY, WING_ATTACK
|
||||
db 35, MAGNETON, THUNDERSHOCK, SONICBOOM, THUNDER_WAVE, SWIFT
|
||||
db 35, HAUNTER, MEAN_LOOK, CURSE, SHADOW_BALL, CONFUSE_RAY
|
||||
db 35, KADABRA, DISABLE, PSYBEAM, RECOVER, FUTURE_SIGHT
|
||||
db 38, TYPHLOSION, SMOKESCREEN, EMBER, QUICK_ATTACK, FLAME_WHEEL
|
||||
```
|
||||
|
||||
RIVAL1 (15) - loaded when the player chose Cyndaquil:
|
||||
|
||||
```
|
||||
db 34, SNEASEL, QUICK_ATTACK, SCREECH, FAINT_ATTACK, FURY_CUTTER
|
||||
db 36, GOLBAT, LEECH_LIFE, BITE, CONFUSE_RAY, WING_ATTACK
|
||||
db 34, MAGNETON, THUNDERSHOCK, SONICBOOM, THUNDER_WAVE, SWIFT
|
||||
db 35, HAUNTER, MEAN_LOOK, CURSE, SHADOW_BALL, CONFUSE_RAY
|
||||
db 35, KADABRA, DISABLE, PSYBEAM, RECOVER, FUTURE_SIGHT
|
||||
db 38, FERALIGATR, RAGE, WATER_GUN, SCARY_FACE, SLASH
|
||||
```
|
||||
|
||||
Note the send-out order: Sneasel, Golbat, Magneton, Haunter, Kadabra, starter.
|
||||
The FAQ lists the starter fourth; the asm puts it last.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
`data/wild/kanto_grass.asm:144` `def_grass_wildmons VICTORY_ROAD` (Victory Road
|
||||
lives in the Kanto grass table even though it is Johto-side).
|
||||
|
||||
Encounter rates: `db 6 percent, 6 percent, 6 percent` (morn / day / nite - all
|
||||
equal). Slot weights come from `GrassMonProbTable`
|
||||
(`data/wild/probabilities.asm:6`): 30 / 30 / 20 / 10 / 5 / 4 / 1 percent.
|
||||
|
||||
Gold (`IF DEF(_GOLD)`), identical for morn, day and nite:
|
||||
|
||||
| slot | % | level | species |
|
||||
|---|---|---|---|
|
||||
| 1 | 30 | 32 | `GRAVELER` |
|
||||
| 2 | 30 | 32 | `GOLBAT` |
|
||||
| 3 | 20 | 33 | `URSARING` |
|
||||
| 4 | 10 | 34 | `ONIX` |
|
||||
| 5 | 5 | 36 | `ONIX` |
|
||||
| 6 | 4 | 35 | `RHYHORN` |
|
||||
| 7 | 1 | 35 | `RHYHORN` |
|
||||
|
||||
Silver (`ELIF DEF(_SILVER)`) is the same table with slot 3 replaced by
|
||||
`db 33, DONPHAN`.
|
||||
|
||||
No water, fishing, headbutt or rock-smash data: there is no `VICTORY_ROAD` row in
|
||||
`data/wild/kanto_water.asm`, `data/wild/johto_water.asm`, `data/wild/fish.asm` or
|
||||
`data/wild/treemons.asm`, and the decoded collision map contains no water tiles.
|
||||
The `FISHGROUP_SHORE` in the header is therefore unreachable. No roamer either
|
||||
(`VICTORY_ROAD` is absent from `data/wild/roammon_maps.asm`).
|
||||
|
||||
### MAP_VICTORY_ROAD_GATE
|
||||
|
||||
Included here because it is the only entrance and it carries the badge gate. The
|
||||
NPC beats themselves belong to the previous / later sections.
|
||||
|
||||
- Script: `maps/VictoryRoadGate.asm` (`data/maps/scripts.asm:452`)
|
||||
- Blocks: `maps/VictoryRoadGate.blk` (`data/maps/blocks.asm:943`)
|
||||
- Header: `data/maps/maps.asm:467`
|
||||
`map VictoryRoadGate, TILESET_GATE, GATE, LANDMARK_ROUTE_26, MUSIC_INDIGO_PLATEAU, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Attributes: `data/maps/attributes.asm:666`, border block `$00`, no connections.
|
||||
- Dimensions: `constants/map_constants.asm:433` `map_const VICTORY_ROAD_GATE, 10, 9` -> 20x18 cells. Group `VIRIDIAN` (group 23), map id 13.
|
||||
- Scene variable: `data/maps/scenes.asm:16` `scene_var VICTORY_ROAD_GATE, wVictoryRoadGateSceneID` (sym `01:d6bf`)
|
||||
|
||||
**Warps** (`maps/VictoryRoadGate.asm:101-109`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 17 | 7 | `ROUTE_22` | 1 |
|
||||
| 2 | 18 | 7 | `ROUTE_22` | 1 |
|
||||
| 3 | 9 | 17 | `ROUTE_26` | 1 |
|
||||
| 4 | 10 | 17 | `ROUTE_26` | 1 |
|
||||
| 5 | 9 | 0 | `VICTORY_ROAD` | 1 |
|
||||
| 6 | 10 | 0 | `VICTORY_ROAD` | 1 |
|
||||
| 7 | 1 | 7 | `ROUTE_28` | 2 |
|
||||
| 8 | 2 | 7 | `ROUTE_28` | 2 |
|
||||
|
||||
Cross-checked from the other side: `maps/Route22.asm:20` warps to
|
||||
`VICTORY_ROAD_GATE, 1`, `maps/Route26.asm:397` warps to `VICTORY_ROAD_GATE, 3`,
|
||||
`maps/Route28.asm:21` warps to `VICTORY_ROAD_GATE, 7`.
|
||||
|
||||
**Coord events**
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_VICTORYROADGATE_BADGE_CHECK` (0) | 10 | 11 | `VictoryRoadGateBadgeCheckScript` (sym `5f:4fdc`) | turns the player LEFT and falls into `_VictoryRoadGateBadgeCheckScript` |
|
||||
|
||||
**BG events**: none.
|
||||
|
||||
**Object events** (`maps/VictoryRoadGate.asm:116-119`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `VICTORYROADGATE_OFFICER` | `SPRITE_OFFICER` | 8 | 11 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `VictoryRoadGateOfficerScript` | `-1` (always present) |
|
||||
| `VICTORYROADGATE_BLACK_BELT1` | `SPRITE_BLACK_BELT` | 7 | 5 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `VictoryRoadGateLeftBlackBeltScript` | `EVENT_OPENED_MT_SILVER` |
|
||||
| `VICTORYROADGATE_BLACK_BELT2` | `SPRITE_BLACK_BELT` | 12 | 5 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `VictoryRoadGateRightBlackBeltScript` | `EVENT_FOUGHT_SNORLAX` |
|
||||
|
||||
**Derived collision map** (same method, `data/tilesets/gate_collision.asm`;
|
||||
`c` = `COLL_COUNTER`, a wall):
|
||||
|
||||
```
|
||||
01234567890123456789
|
||||
0 #########DD#########
|
||||
1 ########....########
|
||||
2 ########....########
|
||||
3 ########....########
|
||||
4 ########....########
|
||||
5 ....................
|
||||
6 ....####....####....
|
||||
7 .WW.####....####.WW.
|
||||
8 ########....########
|
||||
9 ########....########
|
||||
10 ########cc..########
|
||||
11 ########.c.c########
|
||||
12 ########.c..########
|
||||
13 ########cc..########
|
||||
14 ########....########
|
||||
15 ########....########
|
||||
16 ########....########
|
||||
17 ########.WW.########
|
||||
```
|
||||
|
||||
Row 11 is walkable only at x=8 (where the officer stands) and x=10, so
|
||||
**(10,11) is a mandatory chokepoint**: the badge check cannot be walked around.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `_VictoryRoadGateBadgeCheckScript` (`maps/VictoryRoadGate.asm:25`, sym `5f:4fe3`):
|
||||
`writetext VictoryRoadGateOfficerText`, `promptbutton`, `readvar VAR_BADGES`,
|
||||
`ifgreater NUM_JOHTO_BADGES - 1, .AllEightBadges`. On failure it prints
|
||||
`VictoryRoadGateNotEnoughBadgesText` and `applymovement PLAYER,
|
||||
VictoryRoadGateStepDownMovement` (one `step DOWN`, pushing the player back
|
||||
south). On success it prints `VictoryRoadGateEightBadgesText` and
|
||||
`setscene SCENE_VICTORYROADGATE_NOOP`, permanently disarming the coord event.
|
||||
- `VictoryRoadGateOfficerScript` (`maps/VictoryRoadGate.asm:23`) - `faceplayer`
|
||||
then the same body, so talking to the officer also opens the gate.
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Entry to Victory Road | `maps/VictoryRoadGate.asm:19` `VictoryRoadGateBadgeCheckScript` / `_VictoryRoadGateBadgeCheckScript` | `readvar VAR_BADGES` then `ifgreater NUM_JOHTO_BADGES - 1` (that is, badge count must be >= 8). `NUM_JOHTO_BADGES` = 8 (`constants/ram_constants.asm:260`). `VAR_BADGES` resolves to `.CountBadges` in `engine/overworld/variables.asm:80`, which is `CountSetBits` over **two** bytes (`wBadges`, b = 2), i.e. Johto **and** Kanto badges together | own all eight Johto badges. The script then sets `SCENE_VICTORYROADGATE_NOOP`, so the check runs at most once |
|
||||
| Physical block west to Route 28 | `maps/VictoryRoadGate.asm:118` object `VICTORYROADGATE_BLACK_BELT1` at (7,5), masked when `EVENT_OPENED_MT_SILVER` is set | row 5 is the only east-west corridor in the gate and he stands in it | out of scope here (Mt. Silver, later section) |
|
||||
| Physical block east to Route 22 | `maps/VictoryRoadGate.asm:119` object `VICTORYROADGATE_BLACK_BELT2` at (12,5), masked when `EVENT_FOUGHT_SNORLAX` is set | same corridor | out of scope here (Kanto, later section) |
|
||||
| Rival ambush before the exit | `maps/VictoryRoad.asm:255-256` coord events at (12,8) and (13,8), gated on `SCENE_VICTORYROAD_RIVAL_BATTLE` | those two tiles are the whole corridor to the exit at (13,5) | win the battle; the script then runs `setscene SCENE_VICTORYROAD_NOOP` |
|
||||
| TM26 Earthquake | geometry only, no flag: (3,28) is inside the y=26..30 pocket whose only entrance is the `COLL_PIT` at (0,11) | reach the top region first (so: beat the ladders, not the rival) | none |
|
||||
| Full Restore | geometry only: (18,29) is on a shelf whose only entrance is warp 7 (17,19) -> (17,33) | reach the top region | none |
|
||||
|
||||
Field moves: **none required**. There is no `COLL_CUT_TREE`, no water, no
|
||||
strength boulder and no whirlpool anywhere in the decoded map, and the header
|
||||
palette is `PALETTE_NITE`, not `PALETTE_DARK`, so **Flash is not needed** either.
|
||||
The FAQ's "no puzzles to solve now" is accurate.
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
Coordinates are map cells `(x, y)` on the named map. "step" means walk onto the
|
||||
tile; ladders and the pit fire on arrival (`Permissions.isImmediateWarp`
|
||||
equivalent: `COLL_LADDER` = `$72` and `COLL_PIT` = `$60` are warp collisions),
|
||||
while the entrance carpet at (9,67) fires on a DOWN press.
|
||||
|
||||
1. `MAP_VICTORY_ROAD_GATE`: from the Route 26 door at (9,17)/(10,17) walk north along x=10. Precondition: 8 Johto badges. Step (10,11) -> `VictoryRoadGateBadgeCheckScript` fires. Postcondition: `wVictoryRoadGateSceneID` = `SCENE_VICTORYROADGATE_NOOP`. If badges < 8 the script pushes you one step DOWN and you cannot pass.
|
||||
2. `MAP_VICTORY_ROAD_GATE`: step (9,0) or (10,0) -> warp to `MAP_VICTORY_ROAD` (9,67).
|
||||
3. `MAP_VICTORY_ROAD` entrance region. Waypoints that were checked against the
|
||||
collision map: (9,66) -> west along y=66 to (7,66) -> north x=7 through
|
||||
y=65,64 to (7,63) -> (7,62) -> east along y=62 to (11,62)/(12,62) -> north
|
||||
x=11/12 through y=61..55 -> (11,54) -> the long y=53/54 corridor (x=5..16).
|
||||
4. Max Revive and Full Heal: from the y=53/54 corridor go east to (15,54) ->
|
||||
(15,55) -> (15,56) -> east along y=56 to (18,56) -> north along x=18 through
|
||||
y=55,54,53,52 to (18,51) -> west along y=51 to (15,51) -> (15,50) -> (16,50)
|
||||
-> (16,49) -> (16,48), press A facing LEFT
|
||||
into (15,48) -> Full Heal (`EVENT_VICTORY_ROAD_FULL_HEAL`). Then west along
|
||||
y=50 to (12,50) -> (12,49) -> press A facing UP into (12,48) -> Max Revive
|
||||
(`EVENT_VICTORY_ROAD_MAX_REVIVE`).
|
||||
5. Optional hidden Full Heal: face the wall at (3,65) from (2,65), (4,65),
|
||||
(3,64) or (3,66) and press A. Postcondition
|
||||
`EVENT_VICTORY_ROAD_HIDDEN_FULL_HEAL`.
|
||||
6. Back along the y=53/54 corridor to its west end, (3,53) -> (2,52) -> (2,51)
|
||||
-> (2,50) -> (1,49); the ladder fires on arrival -> warp 2 -> (1,35).
|
||||
7. X Special. Verified chain from the ladder landing: (1,35) -> (1,36) -> (1,37)
|
||||
-> (1,38) -> east along y=38 to (5,38) -> (5,37) -> (5,36) -> east along y=36
|
||||
to (9,36)/(10,36) -> (9,37)/(10,37) -> (9,38)/(10,38) -> south through
|
||||
y=39,40 (x=9..16 is open) -> (13,41) -> (13,42) -> west along y=42 to (7,42)
|
||||
-> north along x=7 (the `HOP_LEFT` column is walkable) through y=41,40,39 to
|
||||
(7,38). Press A -> X Special. Postcondition `EVENT_VICTORY_ROAD_X_SPECIAL`.
|
||||
Do **not** press LEFT while on that column (it triggers a two-tile jump into
|
||||
a wall column).
|
||||
8. To the (13,31) ladder: back to (7,42) -> east along y=42 to (18,42) -> north
|
||||
along x=18 through y=41,40,39,38 to (18,37) -> west along y=37 then y=36 to
|
||||
(13,36) -> north (13,35), (13,34), (13,33), (13,32) -> step (13,31) -> warp 4
|
||||
-> arrive (13,17). Note you cannot climb x=13 straight up from y=41: (13,38)
|
||||
is `COLL_UP_WALL` and blocks the step up out of it, and the x=11 column walls
|
||||
off the y=34..36 strip on its west side.
|
||||
9. Optional Full Restore detour. The verified chain from the ladder to (17,19) is:
|
||||
(13,17) -> (13,16) -> east along y=16 to (16,16) -> (16,15) -> (16,14) ->
|
||||
west along y=14 to (9,14)/(10,14) -> south x=9/10 through y=15,16,17 to
|
||||
(9,18)/(10,18) -> west along y=18 to (5,18) -> (5,17) -> (5,16) -> west along
|
||||
y=16 to (3,16) -> (3,17) -> (3,18) -> (3,19) -> (3,20) -> east along y=20 to
|
||||
(8,20) -> **press RIGHT to hop the ledge**, landing (10,20) -> east/north to
|
||||
(16,19) -> (17,19). On the cart that `HOP_RIGHT` is the only way into the
|
||||
(17,19) pocket. Step (17,19) -> warp 7 -> arrive (17,33). Walk north along
|
||||
x=17..19 to (18,29), press A -> Full Restore. Postcondition
|
||||
`EVENT_VICTORY_ROAD_FULL_RESTORE`. Return to y=34 (x=16..19) and press DOWN to
|
||||
hop the ledge, landing on y=36 - which is already the y=36 corridor, so go
|
||||
west to (13,36) and north to (13,31), warp 4, back to (13,17).
|
||||
10. From (13,17): (13,16) -> east along y=16 to (18,16) -> north along x=18
|
||||
through (18,15), (18,14), (18,13) (the rival's masked object tile), (18,12)
|
||||
to (18,11) -> west along y=11 to (13,11) -> north (13,10), (13,9) to (13,8).
|
||||
11. Step (12,8) or (13,8) -> rival ambush. Precondition `wVictoryRoadSceneID` = 0. Battle `RIVAL1` member 13/14/15 depending on the starter events. Postconditions: `EVENT_RIVAL_VICTORY_ROAD` set, `wVictoryRoadSceneID` = `SCENE_VICTORYROAD_NOOP`.
|
||||
12. TM26 detour. The x=6,7 wall pair on y=10 and the x=5..8 wall run on y=11 mean
|
||||
you cannot walk straight west; the verified chain is (9,11) -> (9,10) ->
|
||||
(8,10) -> (8,9) -> west along y=9 to (5,9) -> (5,10) -> west along y=10 to
|
||||
(0,10) -> (0,11), the `COLL_PIT`, which fires on arrival -> land at (0,27).
|
||||
Then (0,28) -> east along y=28 to (2,28), press A facing RIGHT into the ball
|
||||
object at (3,28) -> TM26 Earthquake (`EVENT_VICTORY_ROAD_TM_EARTHQUAKE`).
|
||||
Optionally step to (2,29) and press A facing RIGHT into the wall at (3,29)
|
||||
-> hidden Max Potion (`EVENT_VICTORY_ROAD_HIDDEN_MAX_POTION`).
|
||||
13. Leave the pocket: east along y=28 to (7,28) -> step onto (8,28), the
|
||||
`HOP_RIGHT` tile -> press RIGHT to hop to (10,28) -> (10,29) -> east to
|
||||
(12,29) -> (12,30) -> (12,31) -> step (13,31) -> warp 4 -> (13,17).
|
||||
(Alternative, longer: stand on (2,30) or (3,30) and press DOWN, landing at
|
||||
y=32, which drops you back in the (1,35) ladder pocket and forces a full
|
||||
re-cross of the middle region via steps 7 and 8.)
|
||||
14. Repeat step 10 to return to the exit corridor. The coord events no longer fire.
|
||||
15. Walk (13,7) -> (13,6) -> step (13,5) (`COLL_CAVE`) -> warp 10 -> `MAP_ROUTE_23` (9,13). End of section.
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Map header, dimensions, warps, coord events, bg events, object events for any Gold map | `src/import/RomExtractorGen2.lua` (map event reader at lines 785-865, `extractMaps` from 866) | implemented - data driven off the ROM, so `VICTORY_ROAD` needs no hand port |
|
||||
| `OBJECTTYPE_ITEMBALL` item data extraction | `src/import/RomExtractorGen2.lua:2968` (`obj.itemball = readItemBall(...)`) | implemented (extraction only) |
|
||||
| **Picking up a Poke Ball object** (all five Victory Road items) | `src/world/gen2/World.lua:5257` `World:interact` | **missing** - the A-press path handles trainers, strength boulders, `scriptKey` NPCs, `BGEVENT_READ` signs, `BGEVENT_ITEM` hidden items and the tile-collision field moves. Nothing consumes `obj.itemball`, and an itemball object has no `scriptKey`, so the press falls through |
|
||||
| Hidden items (`BGEVENT_ITEM`, the two here) | `src/world/gen2/HiddenItems.lua`, used at `src/world/gen2/World.lua:5290` | implemented |
|
||||
| Ladder / cave / pit warps | `src/world/gen2/Permissions.lua:162` `isWarpCollision` (covers `$60` PIT and the `$7x` block) and `isImmediateWarp` | implemented - the (0,11) hole and all six ladders work |
|
||||
| Warp carpet needing a direction press (the (9,67) entrance) | `src/world/gen2/Permissions.lua:170` `CARPET_DIR` | implemented |
|
||||
| Walkability from `CollisionPermissionTable` | `src/world/gen2/Permissions.lua:11` (256-entry table), `src/world/gen2/Map.lua:59` | implemented |
|
||||
| **Ledge hops** (`COLL_HOP_LEFT/RIGHT/DOWN`, five of them in this map) | - | **missing** - no `$a0..$a7` handling anywhere under `src/world/gen2/`. The tiles are LAND in the port's permission table, so a bot can walk over them freely. Net effect: the one-way ledges become two-way, and the Full Restore shelf / TM pocket exits behave differently from the cart |
|
||||
| **Directional side walls** (`COLL_UP_WALL`, heavily used here) | `src/world/gen2/FieldMoves.lua:313` `BLOCKED_BY` | partial - the `$b0..$b7` table exists but is only consulted by `FieldMoves.directionBlocked` for surf/field-move refusals. There is no port equivalent of `GetMovementPermissions` (`home/map.asm:1868`), so `UP_WALL` does not restrict ordinary walking |
|
||||
| Coord events gated on the map scene | `src/world/gen2/World.lua:5007` `World:tryCoordScript`, scene storage at `World.lua:740` / `World:scene` at 1161 | implemented |
|
||||
| Scene scripts / `setscene` persistence across save | `src/world/gen2/World.lua:558-584`, `src/core/gen2/Save.lua` | implemented |
|
||||
| Rival ambush opcodes (`moveobject`, `turnobject`, `showemote`, `appear`, `disappear`, `applymovement`, `scall`, `setscene`, `playmapmusic`, `playmusic`, `winlosstext`, `setlasttalked`, `loadtrainer`, `startbattle`, `dontrestartmapmusic`, `reloadmapafterbattle`) | `src/script/gen2/Opcodes.lua` (table), `src/script/gen2/Vm.lua` (`moveobject` 337, `showemote` 961, `loadtrainer` 806, `winlosstext` 918, `reloadmapafterbattle` 886) | implemented |
|
||||
| `special FadeOutMusic` | `src/script/gen2/Specials.lua:1066` | implemented |
|
||||
| `VAR_BADGES` (the gate check) | `src/world/gen2/World.lua:1240` - counts `player.badges` + `player.kantoBadges`, matching `CountSetBits` over two bytes | implemented |
|
||||
| Object masking by event flag | `src/import/RomExtractorGen2.lua` object `eventFlag` field; `src/world/gen2/Npc.lua` | partial - the flag is extracted, but confirm the mask polarity (hidden when **set**) matches `CheckObjectFlag`; not verified in this pass |
|
||||
| Wild encounters for this map | `src/import/RomExtractorGen2.lua:3720` (`JohtoGrassWildMons` / `KantoGrassWildMons`), `src/battle/gen2/Encounter.lua` | implemented |
|
||||
| Battle music landmark split | `src/battle/gen2/BattleMusic.lua:45-56` explicitly names the Victory Road landmark boundary | implemented |
|
||||
| A driver that walks Victory Road | - | **missing** - no `tests/drivers/gold_*.lua` mentions Victory Road |
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
- **Rival Magneton level.** The FAQ says "Level 34 Magneton". The asm has 35 in
|
||||
RIVAL1 (13) and RIVAL1 (14) and 34 only in RIVAL1 (15), the Feraligatr set
|
||||
loaded when the player picked Cyndaquil. The FAQ appears to have been written
|
||||
from one playthrough.
|
||||
- **Rival party order.** The FAQ lists the starter evolution fourth. The asm
|
||||
order is Sneasel, Golbat, Magneton, Haunter, Kadabra, starter (starter last).
|
||||
- **"You get: 2280G"** and the per-mon EXP figures were not verified; base money
|
||||
for the `RIVAL1` class lives in the trainer class attribute table, which was
|
||||
not opened for this pass.
|
||||
- **Wild list.** The FAQ lists only Golbat, Graveler, Rhyhorn and "Donphan
|
||||
(Silver only)". The asm table also carries Onix (two slots, L34 and L36) and,
|
||||
in Gold, Ursaring at L33 in the slot Silver gives to Donphan.
|
||||
- **Item list.** The FAQ's item list omits the hidden Max Potion at (3,29) and
|
||||
does not distinguish the itemball Full Heal at (15,48) from the hidden Full
|
||||
Heal at (3,65).
|
||||
- **"Head up the stairway"** - there is no `COLL_STAIRCASE` in the decoded
|
||||
Victory Road map. What the FAQ calls stairways and cliffs are ordinary floor
|
||||
plus the `COLL_UP_WALL` strips; the only vertical transitions are the six
|
||||
ladders, the pit and the five ledges.
|
||||
- **"Head back up the ladder to get back to the third floor"** (after TM26).
|
||||
There is no ladder inside the TM pocket. The two ways out are the `HOP_RIGHT`
|
||||
at (8,28)/(8,29) into the x=10..13 corridor that reaches the (13,31) ladder,
|
||||
and the `HOP_DOWN` at (2,30)/(3,30) into the (1,35) pocket. The checklist above
|
||||
prefers the first; which one the FAQ author meant is not recoverable from the
|
||||
text.
|
||||
- **X Special tile.** The Poke Ball at (7,38) sits on a `COLL_HOP_LEFT` tile with
|
||||
`COLL_WALL` on both sides and above. Ledge tiles are `LAND_TILE` in
|
||||
`CollisionPermissionTable`, so the column x=7, y=38..41 is walkable from
|
||||
(7,42) upward and the ball is reachable; but this should be confirmed on
|
||||
hardware or in an emulator, because it is the one placement in this map that
|
||||
looks like a mistake rather than a design.
|
||||
- **The gate's black belts as physical blockers.** The claim that
|
||||
`VICTORYROADGATE_BLACK_BELT1` at (7,5) and `BLACK_BELT2` at (12,5) block the
|
||||
only east-west corridor is inferred from the decoded gate collision (row 5 is
|
||||
the sole corridor and rows 4/6 are walls beside it) plus the fact that NPCs are
|
||||
solid. It is not stated in any script, and it belongs to later sections
|
||||
(Route 28 / Route 22) rather than this one.
|
||||
- **Object flag polarity in the port** was not tested; see the Port coverage row.
|
||||
@@ -0,0 +1,883 @@
|
||||
# Section 18 - Pokémon League
|
||||
|
||||
Source: `../section-18-pok-mon-league.txt`
|
||||
Maps covered: `MAP_ROUTE_23`, `MAP_INDIGO_PLATEAU_POKECENTER_1F`, `MAP_WILLS_ROOM`,
|
||||
`MAP_KOGAS_ROOM`, `MAP_BRUNOS_ROOM`, `MAP_KARENS_ROOM`, `MAP_LANCES_ROOM`,
|
||||
`MAP_HALL_OF_FAME`
|
||||
Badges / key milestones in this section: no badge. `ENGINE_FLYPOINT_INDIGO_PLATEAU`
|
||||
(fly point), `EVENT_BEAT_ELITE_4_WILL/KOGA/BRUNO/KAREN`,
|
||||
`EVENT_BEAT_CHAMPION_LANCE`, `EVENT_BEAT_ELITE_FOUR`, `STATUSFLAGS_HALL_OF_FAME_F`
|
||||
(Hall of Fame induction -> credits -> post-game respawn at `SPAWN_NEW_BARK`).
|
||||
|
||||
All eight maps live in map group `INDIGO` (group 16, `constants/map_constants.asm:321`).
|
||||
|
||||
---
|
||||
|
||||
## 1. Route order
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 0 | `MAP_VICTORY_ROAD` | `maps/VictoryRoad.asm` | (previous section) | warp 10 at `(13, 5)` -> `ROUTE_23` warp 3 | Victory Road's north exit; the walkthrough opens already standing on the Plateau approach |
|
||||
| 1 | `MAP_ROUTE_23` | `maps/Route23.asm` | warps 3/4 at `(9, 13)`/`(10, 13)` from Victory Road | warps 1/2 at `(9, 5)`/`(10, 5)` -> `INDIGO_PLATEAU_POKECENTER_1F` warps 1/2 | "Route 32 is very short, and you'll immediately be at the Pokémon League building" (the FAQ means Route **23**). `MAPCALLBACK_NEWMAP` registers the fly point here |
|
||||
| 2 | `MAP_INDIGO_PLATEAU_POKECENTER_1F` | `maps/IndigoPlateauPokecenter1F.asm` | warps 1/2 at `(5, 13)`/`(6, 13)` | warp 4 at `(14, 3)` -> `WILLS_ROOM` warp 1 | Heal (nurse, left), buy (clerk, right), optional Abra teleport home, optional Mon/Wed rival ambush, then through the north door |
|
||||
| 3 | `MAP_WILLS_ROOM` | `maps/WillsRoom.asm` | warp 1 at `(5, 17)` | warps 2/3 at `(4, 2)`/`(5, 2)` -> `KOGAS_ROOM` warps 1/2 | Elite Four 1 - Will |
|
||||
| 4 | `MAP_KOGAS_ROOM` | `maps/KogasRoom.asm` | warps 1/2 at `(4, 17)`/`(5, 17)` | warps 3/4 at `(4, 2)`/`(5, 2)` -> `BRUNOS_ROOM` warps 1/2 | Elite Four 2 - Koga |
|
||||
| 5 | `MAP_BRUNOS_ROOM` | `maps/BrunosRoom.asm` | warps 1/2 at `(4, 17)`/`(5, 17)` | warps 3/4 at `(4, 2)`/`(5, 2)` -> `KARENS_ROOM` warps 1/2 | Elite Four 3 - Bruno |
|
||||
| 6 | `MAP_KARENS_ROOM` | `maps/KarensRoom.asm` | warps 1/2 at `(4, 17)`/`(5, 17)` | warps 3/4 at `(4, 2)`/`(5, 2)` -> `LANCES_ROOM` warps 1/2 | Elite Four 4 - Karen |
|
||||
| 7 | `MAP_LANCES_ROOM` | `maps/LancesRoom.asm` | warps 1/2 at `(4, 23)`/`(5, 23)` | scripted `warp HALL_OF_FAME, 4, 13` at the tail of `LancesRoomLanceScript` (warps 3/4 at `(4, 0)`/`(5, 0)` are the same door) | Champion Lance, then the Mary / Prof. Oak cutscene |
|
||||
| 8 | `MAP_HALL_OF_FAME` | `maps/HallOfFame.asm` | scripted warp to `(4, 13)` | `halloffame` -> `Credits` -> title screen | Induction, credits, "The End" |
|
||||
|
||||
Post-credits: `engine/menus/intro_menu.asm` `.SpawnAfterE4` sends the next CONTINUE
|
||||
to `SPAWN_NEW_BARK`, not to the saved position. That is the start of the Kanto
|
||||
half and belongs to the next section.
|
||||
|
||||
---
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_ROUTE_23
|
||||
|
||||
- Script: `maps/Route23.asm`
|
||||
- Blocks: `maps/Route23.blk`
|
||||
- Header (`data/maps/maps.asm:349`): `map Route23, TILESET_KANTO, TOWN, LANDMARK_ROUTE_23, MUSIC_INDIGO_PLATEAU, FALSE, PALETTE_AUTO, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:322` -> `map_const ROUTE_23, 10, 9` (10 x 9 blocks = 20 x 18 tiles)
|
||||
- Attributes: `data/maps/attributes.asm:397` -> `map_attributes Route23, ROUTE_23, $0f`, **no connections** (despite `TOWN` environment)
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 9 | 5 | `INDIGO_PLATEAU_POKECENTER_1F` | 1 |
|
||||
| 2 | 10 | 5 | `INDIGO_PLATEAU_POKECENTER_1F` | 2 |
|
||||
| 3 | 9 | 13 | `VICTORY_ROAD` | 10 |
|
||||
| 4 | 10 | 13 | `VICTORY_ROAD` | 10 |
|
||||
|
||||
**Coord events** - none (`def_coord_events` is empty).
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 11 | 7 | `BGEVENT_READ` | `IndigoPlateauSign` (`IndigoPlateauSignText`) |
|
||||
|
||||
**Object events** - none (`def_object_events` is empty).
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route23FlypointCallback` (`50:5888`), registered as `callback MAPCALLBACK_NEWMAP`.
|
||||
Body is two opcodes: `setflag ENGINE_FLYPOINT_INDIGO_PLATEAU` / `endcallback`.
|
||||
This is the whole of the walkthrough's "You'll now be able to fly to this
|
||||
location directly", and it is also the gate on the Kanto half of the Fly map
|
||||
(see section 5 - Blockers).
|
||||
- `IndigoPlateauSign` -> `jumptext IndigoPlateauSignText`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_FLYPOINT_INDIGO_PLATEAU` | `constants/engine_flags.asm:78` | set by `Route23FlypointCallback` | after one step onto Route 23, Fly to Indigo Plateau is available; also unlocks the Kanto Fly map |
|
||||
|
||||
**Items** - none.
|
||||
|
||||
**Trainers** - none.
|
||||
|
||||
**Wild encounters** - none. `ROUTE_23` has no entry in `data/wild/johto_grass.asm`,
|
||||
`data/wild/kanto_grass.asm`, `data/wild/johto_water.asm` or
|
||||
`data/wild/kanto_water.asm` (grepped; `kanto_grass.asm` jumps
|
||||
`ROUTE_22` -> `ROUTE_24`). The header's `FISHGROUP_SHORE` is the header default and
|
||||
there is no water to fish from.
|
||||
|
||||
---
|
||||
|
||||
### MAP_INDIGO_PLATEAU_POKECENTER_1F
|
||||
|
||||
- Script: `maps/IndigoPlateauPokecenter1F.asm`
|
||||
- Blocks: `maps/IndigoPlateauPokecenter1F.blk`
|
||||
- Header (`data/maps/maps.asm:350`): `map IndigoPlateauPokecenter1F, TILESET_POKECENTER, INDOOR, LANDMARK_INDIGO_PLATEAU, MUSIC_INDIGO_PLATEAU, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:323` -> `map_const INDIGO_PLATEAU_POKECENTER_1F, 9, 7` (18 x 14 tiles)
|
||||
- Attributes: `data/maps/attributes.asm:598`, border `$00`, no connections
|
||||
|
||||
`object_const_def` order (the ids `moveobject` / `applymovement` / `appear` take):
|
||||
|
||||
```
|
||||
0 INDIGOPLATEAUPOKECENTER1F_NURSE
|
||||
1 INDIGOPLATEAUPOKECENTER1F_CLERK
|
||||
2 INDIGOPLATEAUPOKECENTER1F_COOLTRAINER_M
|
||||
3 INDIGOPLATEAUPOKECENTER1F_RIVAL
|
||||
4 INDIGOPLATEAUPOKECENTER1F_GRAMPS
|
||||
5 INDIGOPLATEAUPOKECENTER1F_ABRA
|
||||
```
|
||||
|
||||
(the macro is `const_def 2`, so the emitted numbers start at 2)
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 5 | 13 | `ROUTE_23` | 1 |
|
||||
| 2 | 6 | 13 | `ROUTE_23` | 2 |
|
||||
| 3 | 0 | 13 | `POKECENTER_2F` | 1 |
|
||||
| 4 | 14 | 3 | `WILLS_ROOM` | 1 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_INDIGOPLATEAUPOKECENTER1F_RIVAL_BATTLE` (= 0) | 16 | 4 | `PlateauRivalBattle1` (`5a:48ff`) | rival walks up from the left column and blocks you |
|
||||
| `SCENE_INDIGOPLATEAUPOKECENTER1F_RIVAL_BATTLE` (= 0) | 17 | 4 | `PlateauRivalBattle2` (`5a:4940`) | same fight, approached from the right column |
|
||||
|
||||
**BG events** - none.
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `..._NURSE` | `SPRITE_NURSE` | 3 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `IndigoPlateauPokecenter1FNurseScript` | -1 |
|
||||
| `..._CLERK` | `SPRITE_CLERK` | 11 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `IndigoPlateauPokecenter1FClerkScript` | -1 |
|
||||
| `..._COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 11 | 11 | `SPRITEMOVEDATA_WANDER` (radius 2,2) | `OBJECTTYPE_SCRIPT` | `IndigoPlateauPokecenter1FCooltrainerMScript` | -1 |
|
||||
| `..._RIVAL` | `SPRITE_RIVAL` | 16 | 9 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_INDIGO_PLATEAU_POKECENTER_RIVAL` |
|
||||
| `..._GRAMPS` | `SPRITE_GRAMPS` (`PAL_NPC_BLUE`) | 1 | 9 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `TeleportGuyScript` | `EVENT_TELEPORT_GUY` |
|
||||
| `..._ABRA` | `SPRITE_JYNX` (`PAL_NPC_BROWN`) | 0 | 9 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT` | `AbraScript` | `EVENT_TELEPORT_GUY` |
|
||||
|
||||
Note the last row: the "Abra" the walkthrough names is declared with
|
||||
`SPRITE_JYNX` (`constants/sprite_constants.asm:122`; `data/sprites/sprite_mons.asm`
|
||||
maps that slot to `JYNX`). Its script is `AbraScript`, which does
|
||||
`cry ABRA`. Verbatim from the file, not a typo of mine.
|
||||
|
||||
An object's event flag hides it when the flag is **set** (`Script_disappear`
|
||||
sets, `Script_appear` clears - `engine/overworld/scripting.asm:879-898`).
|
||||
`EVENT_INDIGO_PLATEAU_POKECENTER_RIVAL` is set on New Game by
|
||||
`engine/events/std_scripts.asm:556` (the `EVENT_INITIALIZED_EVENTS` block), so
|
||||
the rival starts hidden. `EVENT_TELEPORT_GUY` is not in that block, so the old
|
||||
man and his Pokémon are visible from the first visit.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `IndigoPlateauPokecenter1FPrepareElite4Callback` (`5a:48b6`), a
|
||||
`callback MAPCALLBACK_NEWMAP`. **This is the single most important script in
|
||||
the section for a bot**: every time you set foot in the lobby it resets the
|
||||
whole Elite Four run.
|
||||
|
||||
```
|
||||
setmapscene WILLS_ROOM, SCENE_WILLSROOM_LOCK_DOOR
|
||||
setmapscene KOGAS_ROOM, SCENE_KOGASROOM_LOCK_DOOR
|
||||
setmapscene BRUNOS_ROOM, SCENE_BRUNOSROOM_LOCK_DOOR
|
||||
setmapscene KARENS_ROOM, SCENE_KARENSROOM_LOCK_DOOR
|
||||
setmapscene LANCES_ROOM, SCENE_LANCESROOM_LOCK_DOOR
|
||||
setmapscene HALL_OF_FAME, SCENE_HALLOFFAME_ENTER
|
||||
clearevent EVENT_{WILLS,KOGAS,BRUNOS,KARENS,LANCES}_ROOM_ENTRANCE_CLOSED
|
||||
clearevent EVENT_{WILLS,KOGAS,BRUNOS,KARENS,LANCES}_ROOM_EXIT_OPEN
|
||||
clearevent EVENT_BEAT_ELITE_4_WILL / _KOGA / _BRUNO / _KAREN
|
||||
clearevent EVENT_BEAT_CHAMPION_LANCE
|
||||
setevent EVENT_LANCES_ROOM_OAK_AND_MARY ; hide Oak + Mary again
|
||||
endcallback
|
||||
```
|
||||
|
||||
Consequences: the five `EVENT_BEAT_ELITE_4_*` flags are **not** durable
|
||||
progress markers - they only survive within one uninterrupted run. The durable
|
||||
"I beat the League" flag is `EVENT_BEAT_ELITE_FOUR`, set in `HallOfFameEnterScript`.
|
||||
- `PlateauRivalBattle1` / `PlateauRivalBattle2` -> `PlateauRivalBattleCommon`.
|
||||
Guards, in order:
|
||||
1. `checkevent EVENT_BEAT_RIVAL_IN_MT_MOON` / `iffalse` -> done (so this only
|
||||
ever fires post-game, after the Mt. Moon rematch),
|
||||
2. `checkflag ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT` / `iftrue` -> done (once a day),
|
||||
3. `readvar VAR_WEEKDAY` with `ifequal SUNDAY / TUESDAY / THURSDAY / FRIDAY /
|
||||
SATURDAY` -> done. **Only Monday and Wednesday.**
|
||||
|
||||
Then `moveobject`/`appear` the rival, `showemote EMOTE_SHOCK`,
|
||||
`special FadeOutMusic`, a 5-step `applymovement`, `playmusic MUSIC_RIVAL_ENCOUNTER`,
|
||||
`setevent EVENT_INDIGO_PLATEAU_POKECENTER_RIVAL`, then a starter-dependent
|
||||
`loadtrainer RIVAL2, RIVAL2_2_*` + `startbattle`. Afterwards
|
||||
`PlateauRivalPostBattle` plays `MUSIC_RIVAL_AFTER`, walks him off with
|
||||
`disappear`, and `setflag ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT`.
|
||||
- `IndigoPlateauPokecenter1FNurseScript` -> `jumpstd PokecenterNurseScript` (free heal).
|
||||
- `IndigoPlateauPokecenter1FClerkScript` -> `pokemart MARTTYPE_STANDARD, MART_INDIGO_PLATEAU`.
|
||||
- `TeleportGuyScript` (`5a:49e5`): `yesorno`; on yes, `playsound SFX_WARP_TO`,
|
||||
`special FadeOutToWhite`, `warp NEW_BARK_TOWN, 13, 6`. The walkthrough's "Abra
|
||||
will teleport you back" lands you in **New Bark Town**, specifically, not
|
||||
"back to Johto" generically.
|
||||
- `AbraScript`: text + `cry ABRA`, nothing else.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_INDIGO_PLATEAU_POKECENTER_RIVAL` | `constants/event_flags.asm:1309` | set by `std_scripts.asm:556` on New Game and by `PlateauRivalBattleCommon`; cleared by `appear` | hides the rival object |
|
||||
| `EVENT_TELEPORT_GUY` | `constants/event_flags.asm:1310` | set by `HallOfFameEnterScript` | after the Hall of Fame the old man **and** his Pokémon vanish; the ride home is a pre-champion service only |
|
||||
| `EVENT_BEAT_RIVAL_IN_MT_MOON` | `constants/event_flags.asm:456` | read by both `PlateauRivalBattle*` | precondition for the lobby ambush |
|
||||
| `ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT` | `constants/engine_flags.asm:111` (in the `wDailyFlags2` block) | read/set by `PlateauRivalBattle*` | daily one-shot; cleared with the rest of the daily flags at midnight |
|
||||
| `SCENE_INDIGOPLATEAUPOKECENTER1F_RIVAL_BATTLE` | the map's own `def_scene_scripts` (value 0) | coord events | see the quirk note below |
|
||||
|
||||
Scene quirk worth knowing: this map declares exactly one scene script, so
|
||||
`SCENE_INDIGOPLATEAUPOKECENTER1F_RIVAL_BATTLE == 0`, which is also the default
|
||||
value of `wMapScenes` for the map. `PlateauRivalPostBattle`'s
|
||||
`setscene SCENE_INDIGOPLATEAUPOKECENTER1F_RIVAL_BATTLE` therefore writes 0 over
|
||||
0 and the two coord events stay armed forever. The real one-shot guards are
|
||||
`ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT` and the weekday check.
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `ULTRA_BALL`, `MAX_REPEL`, `HYPER_POTION`, `MAX_POTION`, `FULL_RESTORE`, `REVIVE`, `FULL_HEAL` | bought | `MartIndigoPlateau` (`data/items/marts.asm:384`, symbol `05:645a`), opened by `IndigoPlateauPokecenter1FClerkScript` | n/a |
|
||||
|
||||
This is where the walkthrough's "ten Hyper Potions, ten Revives, ten Full Heals"
|
||||
shopping list is actually satisfiable - the mart carries all three plus Full
|
||||
Restore.
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `RIVAL2` | `RIVAL2` (`$2a`) | `RIVAL2_2_CHIKORITA` / `RIVAL2_2_CYNDAQUIL` / `RIVAL2_2_TOTODILE` (chosen by which starter the player took: `checkevent EVENT_GOT_TOTODILE_FROM_ELM` -> Chikorita team, `EVENT_GOT_CHIKORITA_FROM_ELM` -> Cyndaquil team, else Totodile team) | `Rival2Group` entries 4/5/6 | `PlateauRivalBattleCommon` | daily, Mon/Wed only, post Mt. Moon |
|
||||
|
||||
`Rival2Group` (4)-(6), all `TRAINERTYPE_MOVES`:
|
||||
`45 SNEASEL`, `48 CROBAT`, `45 MAGNETON`, `46 GENGAR`, `46 ALAKAZAM`, and then
|
||||
`50 MEGANIUM` / `50 TYPHLOSION` / `50 FERALIGATR` respectively. The walkthrough
|
||||
does not mention this fight at all - a bot that walks the lobby on a Monday
|
||||
will hit it whether or not it planned to.
|
||||
|
||||
**Wild encounters** - none (indoor).
|
||||
|
||||
---
|
||||
|
||||
### MAP_WILLS_ROOM
|
||||
|
||||
- Script: `maps/WillsRoom.asm`
|
||||
- Blocks: `maps/WillsRoom.blk`
|
||||
- Header (`data/maps/maps.asm:351`): `map WillsRoom, TILESET_ELITE_FOUR_ROOM, INDOOR, LANDMARK_INDIGO_PLATEAU, MUSIC_INDIGO_PLATEAU, TRUE, PALETTE_DAY, FISHGROUP_SHORE` (the `TRUE` is the phone-service flag: **no phone calls** in any Elite Four room)
|
||||
- Dimensions: `constants/map_constants.asm:324` -> `map_const WILLS_ROOM, 5, 9` (10 x 18 tiles)
|
||||
- Attributes: `data/maps/attributes.asm:599`, border `$00`, no connections
|
||||
|
||||
`object_const_def`: `WILLSROOM_WILL` (emitted id 2).
|
||||
|
||||
Scene ids from the map's own `def_scene_scripts`:
|
||||
`SCENE_WILLSROOM_LOCK_DOOR = 0`, `SCENE_WILLSROOM_NOOP = 1`.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 5 | 17 | `INDIGO_PLATEAU_POKECENTER_1F` | 4 |
|
||||
| 2 | 4 | 2 | `KOGAS_ROOM` | 1 |
|
||||
| 3 | 5 | 2 | `KOGAS_ROOM` | 2 |
|
||||
|
||||
**Coord events** - none. The door-lock cutscene is a **scene script**, not a
|
||||
coord event: `scene_script WillsRoomLockDoorScene, SCENE_WILLSROOM_LOCK_DOOR`
|
||||
fires on map load via `sdefer`.
|
||||
|
||||
**BG events** - none.
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `WILLSROOM_WILL` | `SPRITE_WILL` (`PAL_NPC_RED`) | 5 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `WillScript_Battle` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `WillsRoomDoorLocksBehindYouScript` (`5a:4d8c`), deferred by the scene script:
|
||||
`applymovement PLAYER, WillsRoom_EnterMovement` (4 x `step UP`, so you enter at
|
||||
`(5, 17)` and end at `(5, 13)`), `reanchormap $85`, `playsound SFX_STRENGTH`,
|
||||
`earthquake 80`, `changeblock 4, 14, $2a` (wall), `refreshmap`,
|
||||
`setscene SCENE_WILLSROOM_NOOP`, `setevent EVENT_WILLS_ROOM_ENTRANCE_CLOSED`.
|
||||
`changeblock` operands are in the same tile space as warps but are halved
|
||||
internally (`Script_changeblock` adds 4 then `GetBlockLocation` shifts right,
|
||||
`home/map.asm:2099`), so `4, 14` is the block covering tiles `(4..5, 14..15)` -
|
||||
the corridor square immediately below where the walk-in leaves you. That is
|
||||
what makes the walkthrough's "You can't go back to the Pokémon Center to heal
|
||||
between battles" literally true.
|
||||
- `WillsRoomDoorsCallback`, `callback MAPCALLBACK_TILES`: re-applies
|
||||
`changeblock 4, 14, $2a` while `EVENT_WILLS_ROOM_ENTRANCE_CLOSED` is set and
|
||||
`changeblock 4, 2, $16` (open door) while `EVENT_WILLS_ROOM_EXIT_OPEN` is set,
|
||||
so the room's geometry survives a battle reload.
|
||||
- `WillScript_Battle` (`5a:4da4`): `faceplayer`, `opentext`,
|
||||
`checkevent EVENT_BEAT_ELITE_4_WILL` / `iftrue WillScript_AfterBattle`,
|
||||
`writetext WillScript_WillBeforeText`, `winlosstext WillScript_WillBeatenText, 0`,
|
||||
`loadtrainer WILL, WILL1`, `startbattle`, `reloadmapafterbattle`,
|
||||
`setevent EVENT_BEAT_ELITE_4_WILL`, defeat text, `playsound SFX_ENTER_DOOR`,
|
||||
`changeblock 4, 2, $16` (open the north door), `refreshmap`,
|
||||
`setevent EVENT_WILLS_ROOM_EXIT_OPEN`.
|
||||
|
||||
Note there is **no `winlosstext` loss branch** (the second operand is `0`) and
|
||||
no `BATTLETYPE_CANLOSE`: losing to any Elite Four member is an ordinary
|
||||
blackout, which is why the FAQ says "If you lose, you have to start all over".
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_WILLS_ROOM_ENTRANCE_CLOSED` | `constants/event_flags.asm:440` | set by `WillsRoomDoorLocksBehindYouScript`, read by `WillsRoomDoorsCallback`, cleared by the lobby callback | south door sealed |
|
||||
| `EVENT_WILLS_ROOM_EXIT_OPEN` | `constants/event_flags.asm:441` | set by `WillScript_Battle`, read by `WillsRoomDoorsCallback`, cleared by the lobby callback | north door open -> `(4, 2)` / `(5, 2)` are walkable warps |
|
||||
| `EVENT_BEAT_ELITE_4_WILL` | `constants/event_flags.asm:980` | set by `WillScript_Battle`, cleared by the lobby callback | run-local "Will is done" |
|
||||
| `SCENE_WILLSROOM_LOCK_DOOR` / `SCENE_WILLSROOM_NOOP` | this map's `def_scene_scripts` (0 / 1) | `setmapscene` from the lobby; `setscene` in the lock script | 0 = play the seal-in cutscene on entry |
|
||||
|
||||
**Items** - none.
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `WILL1` | `WILL` (`$b`) | 1 | `WillGroup` (`data/trainers/parties.asm:197`, symbol `0e:5bf4`) | `WillScript_Battle` | none |
|
||||
|
||||
`WillGroup`, `TRAINERTYPE_MOVES`, in **send-out order**:
|
||||
|
||||
| # | lvl | species | moves |
|
||||
|---|---|---|---|
|
||||
| 1 | 40 | `XATU` | Quick Attack, Future Sight, Confuse Ray, Psychic |
|
||||
| 2 | 41 | `JYNX` | DoubleSlap, Lovely Kiss, Ice Punch, Psychic |
|
||||
| 3 | 41 | `EXEGGUTOR` | Reflect, Leech Seed, Egg Bomb, Psychic |
|
||||
| 4 | 41 | `SLOWBRO` | Curse, Amnesia, Body Slam, Psychic |
|
||||
| 5 | 42 | `XATU` | Quick Attack, Future Sight, Confuse Ray, Psychic |
|
||||
|
||||
Class attributes (`data/trainers/attributes.asm`, `; Will` block):
|
||||
items `MAX_POTION, NO_ITEM`; base reward 25; DVs `atk 13 / def 12 / spd 13 / spc 13`
|
||||
(`data/trainers/dvs.asm:15`). Money = base reward x level of the last enemy mon
|
||||
(`ComputeTrainerReward`, `engine/battle/read_trainer_party.asm:300`) = **25 x 42 = 1050**.
|
||||
|
||||
**Wild encounters** - none.
|
||||
|
||||
---
|
||||
|
||||
### MAP_KOGAS_ROOM
|
||||
|
||||
- Script: `maps/KogasRoom.asm`
|
||||
- Blocks: `maps/KogasRoom.blk`
|
||||
- Header (`data/maps/maps.asm:352`): `map KogasRoom, TILESET_ELITE_FOUR_ROOM, INDOOR, LANDMARK_INDIGO_PLATEAU, MUSIC_INDIGO_PLATEAU, TRUE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:325` -> `map_const KOGAS_ROOM, 5, 9`
|
||||
- Attributes: `data/maps/attributes.asm:600`, border `$00`
|
||||
|
||||
`object_const_def`: `KOGASROOM_KOGA`.
|
||||
Scenes: `SCENE_KOGASROOM_LOCK_DOOR = 0`, `SCENE_KOGASROOM_NOOP = 1`.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 17 | `WILLS_ROOM` | 2 |
|
||||
| 2 | 5 | 17 | `WILLS_ROOM` | 3 |
|
||||
| 3 | 4 | 2 | `BRUNOS_ROOM` | 1 |
|
||||
| 4 | 5 | 2 | `BRUNOS_ROOM` | 2 |
|
||||
|
||||
**Coord events** - none. **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `KOGASROOM_KOGA` | `SPRITE_KOGA` (`PAL_NPC_BLUE`) | 5 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `KogaScript_Battle` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
Structurally identical to Will's room: `KogasRoomDoorLocksBehindYouScript`
|
||||
(4 x `step UP`, `changeblock 4, 14, $2a`, `setevent EVENT_KOGAS_ROOM_ENTRANCE_CLOSED`,
|
||||
`setscene SCENE_KOGASROOM_NOOP`), `KogasRoomDoorsCallback` (`MAPCALLBACK_TILES`),
|
||||
and `KogaScript_Battle` (`5a:5006`) -> `loadtrainer KOGA, KOGA1` /
|
||||
`setevent EVENT_BEAT_ELITE_4_KOGA` / `changeblock 4, 2, $16` /
|
||||
`setevent EVENT_KOGAS_ROOM_EXIT_OPEN`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_KOGAS_ROOM_ENTRANCE_CLOSED` | `constants/event_flags.asm:442` | lock script / tiles callback | south door sealed |
|
||||
| `EVENT_KOGAS_ROOM_EXIT_OPEN` | `constants/event_flags.asm:443` | `KogaScript_Battle` / tiles callback | north door open |
|
||||
| `EVENT_BEAT_ELITE_4_KOGA` | `constants/event_flags.asm:981` | `KogaScript_Battle` | run-local |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `KOGA1` | `KOGA` (`$f`) | 1 | `KogaGroup` (`data/trainers/parties.asm:249`, symbol `0e:5c89`) | `KogaScript_Battle` | none |
|
||||
|
||||
| # | lvl | species | moves |
|
||||
|---|---|---|---|
|
||||
| 1 | 40 | `ARIADOS` | Double Team, Spider Web, Baton Pass, Giga Drain |
|
||||
| 2 | 41 | `VENOMOTH` | Supersonic, Gust, Psychic, Toxic |
|
||||
| 3 | 43 | `FORRETRESS` | Protect, Swift, Explosion, Spikes |
|
||||
| 4 | 42 | `MUK` | Minimize, Acid Armor, Sludge Bomb, Toxic |
|
||||
| 5 | 44 | `CROBAT` | Double Team, Quick Attack, Wing Attack, Toxic |
|
||||
|
||||
Class attributes (`; Koga`): items `FULL_HEAL, FULL_RESTORE` (the walkthrough's
|
||||
"three Full Restores" is HGSS; in GS the AI holds **one** Full Heal and **one**
|
||||
Full Restore per class-attribute slot); base reward 25; DVs `13/12/13/13`
|
||||
(`data/trainers/dvs.asm:19`). Money = 25 x 44 = **1100**.
|
||||
|
||||
**Wild encounters** - none.
|
||||
|
||||
---
|
||||
|
||||
### MAP_BRUNOS_ROOM
|
||||
|
||||
- Script: `maps/BrunosRoom.asm`
|
||||
- Blocks: `maps/BrunosRoom.blk`
|
||||
- Header (`data/maps/maps.asm:353`): `map BrunosRoom, TILESET_ELITE_FOUR_ROOM, INDOOR, LANDMARK_INDIGO_PLATEAU, MUSIC_INDIGO_PLATEAU, TRUE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:326` -> `map_const BRUNOS_ROOM, 5, 9`
|
||||
- Attributes: `data/maps/attributes.asm:601`, border `$00`
|
||||
|
||||
`object_const_def`: `BRUNOSROOM_BRUNO`.
|
||||
Scenes: `SCENE_BRUNOSROOM_LOCK_DOOR = 0`, `SCENE_BRUNOSROOM_NOOP = 1`.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 17 | `KOGAS_ROOM` | 3 |
|
||||
| 2 | 5 | 17 | `KOGAS_ROOM` | 4 |
|
||||
| 3 | 4 | 2 | `KARENS_ROOM` | 1 |
|
||||
| 4 | 5 | 2 | `KARENS_ROOM` | 2 |
|
||||
|
||||
**Coord events** - none. **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `BRUNOSROOM_BRUNO` | `SPRITE_BRUNO` (`PAL_NPC_BROWN`) | 5 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `BrunoScript_Battle` | -1 |
|
||||
|
||||
**Scripts of interest** - `BrunosRoomDoorLocksBehindYouScript`,
|
||||
`BrunosRoomDoorsCallback`, `BrunoScript_Battle` (`5a:5271`); same shape as Will's
|
||||
room, with `loadtrainer BRUNO, BRUNO1` and
|
||||
`setevent EVENT_BEAT_ELITE_4_BRUNO` / `EVENT_BRUNOS_ROOM_EXIT_OPEN`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BRUNOS_ROOM_ENTRANCE_CLOSED` | `constants/event_flags.asm:444` | lock script / tiles callback | south door sealed |
|
||||
| `EVENT_BRUNOS_ROOM_EXIT_OPEN` | `constants/event_flags.asm:445` | `BrunoScript_Battle` / tiles callback | north door open |
|
||||
| `EVENT_BEAT_ELITE_4_BRUNO` | `constants/event_flags.asm:982` | `BrunoScript_Battle` | run-local |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `BRUNO1` | `BRUNO` (`$d`) | 1 | `BrunoGroup` (`data/trainers/parties.asm:229`, symbol `0e:5c3d`) | `BrunoScript_Battle` | none |
|
||||
|
||||
| # | lvl | species | moves |
|
||||
|---|---|---|---|
|
||||
| 1 | 42 | `HITMONTOP` | Pursuit, Quick Attack, Dig, Detect |
|
||||
| 2 | 42 | `HITMONLEE` | Swagger, Double Kick, Hi Jump Kick, Foresight |
|
||||
| 3 | 42 | `HITMONCHAN` | ThunderPunch, Ice Punch, Fire Punch, Mach Punch |
|
||||
| 4 | 43 | `ONIX` | Bind, Earthquake, Sandstorm, Rock Slide |
|
||||
| 5 | 46 | `MACHAMP` | Rock Slide, Foresight, Vital Throw, Cross Chop |
|
||||
|
||||
Class attributes (`; Bruno`): items `MAX_POTION, NO_ITEM`; base reward 25; DVs
|
||||
`13/12/13/13` (`data/trainers/dvs.asm:17`). Money = 25 x 46 = **1150**.
|
||||
|
||||
**Wild encounters** - none.
|
||||
|
||||
---
|
||||
|
||||
### MAP_KARENS_ROOM
|
||||
|
||||
- Script: `maps/KarensRoom.asm`
|
||||
- Blocks: `maps/KarensRoom.blk`
|
||||
- Header (`data/maps/maps.asm:354`): `map KarensRoom, TILESET_ELITE_FOUR_ROOM, INDOOR, LANDMARK_INDIGO_PLATEAU, MUSIC_INDIGO_PLATEAU, TRUE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:327` -> `map_const KARENS_ROOM, 5, 9`
|
||||
- Attributes: `data/maps/attributes.asm:602`, border `$00`
|
||||
|
||||
`object_const_def`: `KARENSROOM_KAREN`.
|
||||
Scenes: `SCENE_KARENSROOM_LOCK_DOOR = 0`, `SCENE_KARENSROOM_NOOP = 1`.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 17 | `BRUNOS_ROOM` | 3 |
|
||||
| 2 | 5 | 17 | `BRUNOS_ROOM` | 4 |
|
||||
| 3 | 4 | 2 | `LANCES_ROOM` | 1 |
|
||||
| 4 | 5 | 2 | `LANCES_ROOM` | 2 |
|
||||
|
||||
**Coord events** - none. **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `KARENSROOM_KAREN` | `SPRITE_KAREN` (`PAL_NPC_RED`) | 5 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `KarenScript_Battle` | -1 |
|
||||
|
||||
**Scripts of interest** - `KarensRoomDoorLocksBehindYouScript`,
|
||||
`KarensRoomDoorsCallback`, `KarenScript_Battle` (`5a:549a`) with
|
||||
`loadtrainer KAREN, KAREN1`, `setevent EVENT_BEAT_ELITE_4_KAREN`,
|
||||
`changeblock 4, 2, $16`, `setevent EVENT_KARENS_ROOM_EXIT_OPEN`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_KARENS_ROOM_ENTRANCE_CLOSED` | `constants/event_flags.asm:446` | lock script / tiles callback | south door sealed |
|
||||
| `EVENT_KARENS_ROOM_EXIT_OPEN` | `constants/event_flags.asm:447` | `KarenScript_Battle` / tiles callback | north door open |
|
||||
| `EVENT_BEAT_ELITE_4_KAREN` | `constants/event_flags.asm:983` | `KarenScript_Battle` | run-local |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `KAREN1` | `KAREN` (`$e`) | 1 | `KarenGroup` (`data/trainers/parties.asm:239`, symbol `0e:5c63`) | `KarenScript_Battle` | none |
|
||||
|
||||
| # | lvl | species | moves |
|
||||
|---|---|---|---|
|
||||
| 1 | 42 | `UMBREON` | Sand-Attack, Confuse Ray, Faint Attack, Mean Look |
|
||||
| 2 | 42 | `VILEPLUME` | Stun Spore, Acid, Moonlight, Petal Dance |
|
||||
| 3 | 45 | `GENGAR` | Lick, Spite, Curse, Destiny Bond |
|
||||
| 4 | 44 | `MURKROW` | Quick Attack, Whirlwind, Pursuit, Faint Attack |
|
||||
| 5 | 47 | `HOUNDOOM` | Roar, Pursuit, Flamethrower, Crunch |
|
||||
|
||||
Class attributes (`; Karen`): items `FULL_HEAL, MAX_POTION` - **no Full Restore**,
|
||||
contra the walkthrough; base reward 25; DVs `atk 7 / def 15 / spd 13 / spc 15`
|
||||
(`data/trainers/dvs.asm:18`, the only Elite Four member with a different DV row).
|
||||
Money = 25 x 47 = **1175**.
|
||||
|
||||
**Wild encounters** - none.
|
||||
|
||||
---
|
||||
|
||||
### MAP_LANCES_ROOM
|
||||
|
||||
- Script: `maps/LancesRoom.asm`
|
||||
- Blocks: `maps/LancesRoom.blk`
|
||||
- Header (`data/maps/maps.asm:355`): `map LancesRoom, TILESET_CHAMPIONS_ROOM, INDOOR, LANDMARK_INDIGO_PLATEAU, MUSIC_INDIGO_PLATEAU, TRUE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:328` -> `map_const LANCES_ROOM, 5, 12` (10 x 24 tiles - the tallest room in the run)
|
||||
- Attributes: `data/maps/attributes.asm:603`, border `$00`
|
||||
|
||||
`object_const_def`: `LANCESROOM_LANCE`, `LANCESROOM_MARY`, `LANCESROOM_OAK`.
|
||||
Scenes: `SCENE_LANCESROOM_LOCK_DOOR = 0`, `SCENE_LANCESROOM_APPROACH_LANCE = 1`.
|
||||
Note this map's second scene is **not** a noop-by-name: it is the state in which
|
||||
the two coord events are live.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 23 | `KARENS_ROOM` | 3 |
|
||||
| 2 | 5 | 23 | `KARENS_ROOM` | 4 |
|
||||
| 3 | 4 | 0 | `HALL_OF_FAME` | 1 |
|
||||
| 4 | 5 | 0 | `HALL_OF_FAME` | 2 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_LANCESROOM_APPROACH_LANCE` (= 1) | 4 | 5 | `Script_ApproachLanceFromLeft` (`5a:5716`) | `FadeOutMusic`, walk `UP UP UP` + face right, fall into `LancesRoomLanceScript` |
|
||||
| `SCENE_LANCESROOM_APPROACH_LANCE` (= 1) | 5 | 5 | `Script_ApproachLanceFromRight` (`5a:5720`) | `FadeOutMusic`, walk `UP LEFT UP UP` + face right, then the same |
|
||||
|
||||
**BG events** - none.
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `LANCESROOM_LANCE` | `SPRITE_LANCE` | 5 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `LancesRoomLanceScript` | -1 |
|
||||
| `LANCESROOM_MARY` | `SPRITE_TEACHER` (`PAL_NPC_GREEN`) | 4 | 7 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_LANCES_ROOM_OAK_AND_MARY` |
|
||||
| `LANCESROOM_OAK` | `SPRITE_OAK` | 4 | 7 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | `EVENT_LANCES_ROOM_OAK_AND_MARY` |
|
||||
|
||||
Mary and Oak share the same spawn tile `(4, 7)` and the same hide flag; both are
|
||||
`appear`ed one at a time during the cutscene.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `LancesRoomDoorLocksBehindYouScript` (`5a:56ff`): 4 x `step UP` from
|
||||
`(4|5, 23)` to y = 19, `reanchormap $85`, `SFX_STRENGTH`, `earthquake 80`,
|
||||
`changeblock 4, 22, $34` (wall - the champion's-room tileset uses a different
|
||||
wall block id than the Elite Four rooms' `$2a`), `refreshmap`,
|
||||
`setscene SCENE_LANCESROOM_APPROACH_LANCE`,
|
||||
`setevent EVENT_LANCES_ROOM_ENTRANCE_CLOSED`.
|
||||
- `LancesRoomLanceScript` (`5a:5727`), the section's climax:
|
||||
`turnobject LANCESROOM_LANCE, LEFT`, intro text,
|
||||
`winlosstext LanceBattleWinText, 0`, `setlasttalked LANCESROOM_LANCE`,
|
||||
`loadtrainer CHAMPION, LANCE`, `startbattle`, `dontrestartmapmusic`,
|
||||
`reloadmapafterbattle`, `setevent EVENT_BEAT_CHAMPION_LANCE`, after-battle
|
||||
text, `SFX_ENTER_DOOR`, `changeblock 4, 0, $0b` (open the north door),
|
||||
`setevent EVENT_LANCES_ROOM_ENTRANCE_CLOSED` (again - the script re-sets the
|
||||
entrance flag here, it does not clear it), then
|
||||
`musicfadeout MUSIC_BEAUTY_ENCOUNTER, 16` and the cutscene:
|
||||
`appear LANCESROOM_MARY` -> `LancesRoomMovementData_MaryRushesIn` ->
|
||||
`appear LANCESROOM_OAK` -> `LancesRoomMovementData_OakWalksIn` ->
|
||||
`follow`/`stopfollow` pairs -> Mary's interview attempt -> Lance leads the
|
||||
player north (`follow LANCESROOM_LANCE, PLAYER`,
|
||||
`LancesRoomMovementData_LanceLeadsPlayerToHallOfFame`), `disappear` both, then
|
||||
`special FadeOutToWhite` and `warp HALL_OF_FAME, 4, 13`.
|
||||
|
||||
This is the only exit that matters: warps 3/4 exist but the script drives the
|
||||
transition itself.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_LANCES_ROOM_ENTRANCE_CLOSED` | `constants/event_flags.asm:448` | lock script + `LancesRoomLanceScript`; read by `LancesRoomDoorsCallback` | south door sealed |
|
||||
| `EVENT_LANCES_ROOM_EXIT_OPEN` | `constants/event_flags.asm:449` | read by `LancesRoomDoorsCallback`; cleared by the lobby callback | **never set by this map** - the north door is opened by a direct `changeblock 4, 0, $0b` in the battle script, and the flag only matters if you re-enter the map |
|
||||
| `EVENT_BEAT_CHAMPION_LANCE` | `constants/event_flags.asm:984` | set by `LancesRoomLanceScript`, cleared by the lobby callback | run-local |
|
||||
| `EVENT_LANCES_ROOM_OAK_AND_MARY` | `constants/event_flags.asm:1281` | set on New Game (`std_scripts.asm:523`) and by the lobby callback; cleared by `appear` during the cutscene | hides Mary and Oak until the champion falls |
|
||||
| `SCENE_LANCESROOM_APPROACH_LANCE` | this map's `def_scene_scripts` (= 1) | `setscene` at the end of the lock script | arms the two coord events at y = 5 |
|
||||
|
||||
**Items** - none.
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `LANCE` | `CHAMPION` (`$10`) | 1 | `ChampionGroup` (`data/trainers/parties.asm:259`, symbol `0e:5cae`) | `LancesRoomLanceScript` | none |
|
||||
|
||||
| # | lvl | species | moves |
|
||||
|---|---|---|---|
|
||||
| 1 | 44 | `GYARADOS` | Flail, Rain Dance, Surf, Hyper Beam |
|
||||
| 2 | 47 | `DRAGONITE` | Thunder Wave, Twister, Thunder, Hyper Beam |
|
||||
| 3 | 47 | `DRAGONITE` | Thunder Wave, Twister, Blizzard, Hyper Beam |
|
||||
| 4 | 46 | `AERODACTYL` | Wing Attack, AncientPower, Rock Slide, Hyper Beam |
|
||||
| 5 | 46 | `CHARIZARD` | Flamethrower, Wing Attack, Slash, Hyper Beam |
|
||||
| 6 | 50 | `DRAGONITE` | Fire Blast, Safeguard, Outrage, Hyper Beam |
|
||||
|
||||
Class attributes (`; Champion`): items `FULL_HEAL, FULL_RESTORE`; base reward 25;
|
||||
DVs `13/12/13/13` (`data/trainers/dvs.asm:20`). Money = 25 x 50 = **1250**.
|
||||
`data/trainers/leaders.asm:20` lists `CHAMPION`, but the file's own comment notes
|
||||
`CHAMPION` and `RED` are unused for the leader battle-music check.
|
||||
|
||||
**Wild encounters** - none.
|
||||
|
||||
---
|
||||
|
||||
### MAP_HALL_OF_FAME
|
||||
|
||||
- Script: `maps/HallOfFame.asm`
|
||||
- Blocks: `maps/HallOfFame.blk`
|
||||
- Header (`data/maps/maps.asm:356`): `map HallOfFame, TILESET_ICE_PATH, INDOOR, LANDMARK_INDIGO_PLATEAU, MUSIC_NEW_BARK_TOWN, TRUE, PALETTE_DAY, FISHGROUP_SHORE` (yes: `TILESET_ICE_PATH` and the New Bark Town theme)
|
||||
- Dimensions: `constants/map_constants.asm:329` -> `map_const HALL_OF_FAME, 5, 7` (10 x 14 tiles)
|
||||
- Attributes: `data/maps/attributes.asm:604`, border `$00`
|
||||
|
||||
`object_const_def`: `HALLOFFAME_LANCE`.
|
||||
Scenes: `SCENE_HALLOFFAME_ENTER = 0`, `SCENE_HALLOFFAME_NOOP = 1`.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 13 | `LANCES_ROOM` | 3 |
|
||||
| 2 | 5 | 13 | `LANCES_ROOM` | 4 |
|
||||
|
||||
**Coord events** - none (the induction is a scene script). **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `HALLOFFAME_LANCE` | `SPRITE_LANCE` | 4 | 12 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `HallOfFameEnterScript` (`5a:5cff`), deferred from
|
||||
`scene_script HallOfFameEnterScene, SCENE_HALLOFFAME_ENTER`. In order:
|
||||
`follow HALLOFFAME_LANCE, PLAYER`,
|
||||
`applymovement HALLOFFAME_LANCE, HallOfFame_WalkUpWithLance` (8 x `step UP`,
|
||||
1 x `step RIGHT`, `turn_head LEFT`), `stopfollow`, Lance's speech,
|
||||
`applymovement PLAYER, HallOfFame_SlowlyApproachMachine` (one `slow_step UP`),
|
||||
`setscene SCENE_HALLOFFAME_NOOP`, then the state writes:
|
||||
|
||||
```
|
||||
setval HEALMACHINE_HALL_OF_FAME ; constants/script_constants.asm:295
|
||||
special HealMachineAnim
|
||||
setevent EVENT_BEAT_ELITE_FOUR ; constants/event_flags.asm:77
|
||||
setevent EVENT_TELEPORT_GUY ; removes the Abra ride home
|
||||
setevent EVENT_RIVAL_SPROUT_TOWER
|
||||
clearevent EVENT_RED_IN_MT_SILVER ; Red becomes available later
|
||||
setevent EVENT_OLIVINE_PORT_SPRITES_BEFORE_HALL_OF_FAME
|
||||
clearevent EVENT_OLIVINE_PORT_SPRITES_AFTER_HALL_OF_FAME
|
||||
setmapscene SPROUT_TOWER_3F, SCENE_SPROUTTOWER3F_NOOP
|
||||
special HealParty
|
||||
checkevent EVENT_GOT_SS_TICKET_FROM_ELM / iftrue .SkipPhoneCall
|
||||
specialphonecall SPECIALCALL_SSTICKET ; constants/phone_constants.asm:49
|
||||
halloffame
|
||||
```
|
||||
|
||||
So the S.S. Ticket call from Elm is queued **here**, and the Kanto half of the
|
||||
game is switched on by this one script.
|
||||
- `halloffame` ($9f) -> `Script_halloffame` (`engine/overworld/scripting.asm:2207`)
|
||||
-> `HallOfFame` (`21:640a`, `engine/events/halloffame.asm:3`):
|
||||
fade out music, `wSpawnAfterChampion = SPAWN_LANCE`,
|
||||
`set STATUSFLAGS_HALL_OF_FAME_F`, bump `wHallOfFameCount` (capped at
|
||||
`HOF_MASTER_COUNT = 200`), `farcall SaveGameData`, `GetHallOfFameParty`,
|
||||
`AddHallOfFameEntry`, `AnimateHallOfFame`, then `jp Credits` with the
|
||||
**pre-set** copy of `wStatusFlags` (which is why a first-time champion cannot
|
||||
hold B to skip the roll).
|
||||
- After the credits, `ReturnFromCredits` (`engine/overworld/scripting.asm:2216`)
|
||||
ends the script and returns `MAPSTATUS_DONE`; the game goes to the title
|
||||
screen. On the next CONTINUE, `engine/menus/intro_menu.asm:296` `.SpawnAfterE4`
|
||||
sets `wDefaultSpawnpoint = SPAWN_NEW_BARK` and enters via `MAPSETUP_WARP` -
|
||||
the walkthrough's "the game isn't actually over".
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BEAT_ELITE_FOUR` | `constants/event_flags.asm:77` | set by `HallOfFameEnterScript` | the durable "League cleared" flag; the five `EVENT_BEAT_ELITE_4_*` flags are wiped by the lobby callback and must not be used for this |
|
||||
| `EVENT_TELEPORT_GUY` | `constants/event_flags.asm:1310` | set here | Abra ride home disappears |
|
||||
| `EVENT_RED_IN_MT_SILVER` | `constants/event_flags.asm:1284` | set on New Game (`std_scripts.asm:526`), **cleared** here | Red can now exist at Mt. Silver once the rest of his gate is met |
|
||||
| `EVENT_RIVAL_SPROUT_TOWER` / `SCENE_SPROUTTOWER3F_NOOP` | `constants/event_flags.asm:1126` / Sprout Tower 3F's scenes | set here | post-game Sprout Tower state |
|
||||
| `EVENT_OLIVINE_PORT_SPRITES_BEFORE_HALL_OF_FAME` / `_AFTER_HALL_OF_FAME` | `constants/event_flags.asm:1241-1242` | flipped here | Olivine Port repopulates for the Fast Ship |
|
||||
| `EVENT_GOT_SS_TICKET_FROM_ELM` | `constants/event_flags.asm:45` | read here | if unset, `specialphonecall SPECIALCALL_SSTICKET` queues Elm's call (`engine/phone/scripts/elm.asm:69`) |
|
||||
| `STATUSFLAGS_HALL_OF_FAME_F` | `constants/ram_constants.asm` | set by `HallOfFame::` | unlocks the Kanto Pokégear map, gates credit skipping |
|
||||
| `SCENE_HALLOFFAME_ENTER` / `_NOOP` | this map's `def_scene_scripts` (0 / 1) | `setmapscene` from the lobby callback; `setscene` mid-script | 0 = play the induction |
|
||||
|
||||
**Items** - none. **Trainers** - none. **Wild encounters** - none.
|
||||
|
||||
---
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Entry to Victory Road (and therefore to this whole section) | `maps/VictoryRoadGate.asm:_VictoryRoadGateBadgeCheckScript` - `readvar VAR_BADGES` / `ifgreater NUM_JOHTO_BADGES - 1, .AllEightBadges` | all 8 Johto badges | `.AllEightBadges` does `setscene SCENE_VICTORYROADGATE_NOOP`; otherwise you get `applymovement PLAYER, VictoryRoadGateStepDownMovement` and are pushed back. **This gate belongs to the previous section**, listed here only because it is the precondition for everything below |
|
||||
| Fly to Indigo Plateau | `maps/Route23.asm:Route23FlypointCallback` | one `MAPCALLBACK_NEWMAP` on Route 23 | `setflag ENGINE_FLYPOINT_INDIGO_PLATEAU` |
|
||||
| No retreat once inside an Elite Four room | `maps/WillsRoom.asm:WillsRoomDoorLocksBehindYouScript` (and the Koga/Bruno/Karen/Lance twins) - `changeblock 4, 14, $2a` (Lance: `changeblock 4, 22, $34`) plus `EVENT_*_ROOM_ENTRANCE_CLOSED` re-applied by each map's `MAPCALLBACK_TILES` | none - it is unconditional on entry | never, within a run. The lobby's `MAPCALLBACK_NEWMAP` clears the flags, but you can only reach the lobby by finishing or blacking out |
|
||||
| Each room's north door | `WillScript_Battle` / `KogaScript_Battle` / `BrunoScript_Battle` / `KarenScript_Battle` - `changeblock 4, 2, $16` + `setevent EVENT_*_ROOM_EXIT_OPEN`, replayed by the map's `MAPCALLBACK_TILES` | beat that member | door block becomes `$16` (open) and warps at `(4, 2)`/`(5, 2)` become reachable |
|
||||
| Lance's north door | `maps/LancesRoom.asm:LancesRoomLanceScript` - `changeblock 4, 0, $0b` | beat Lance | opened, but the script warps you through immediately anyway |
|
||||
| Approaching Lance at all | `def_coord_events` at `(4, 5)` / `(5, 5)` gated on `SCENE_LANCESROOM_APPROACH_LANCE` | that scene is only set at the end of `LancesRoomDoorLocksBehindYouScript` | walking in through warp 1/2 always sets it; a bot teleported straight to y < 5 would skip the fight |
|
||||
| Lobby rival ambush | `maps/IndigoPlateauPokecenter1F.asm:PlateauRivalBattle1/2` | `EVENT_BEAT_RIVAL_IN_MT_MOON` set, `ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT` clear, `VAR_WEEKDAY` in {MONDAY, WEDNESDAY} | not a progress gate - a hazard. Avoid tiles `(16, 4)` / `(17, 4)` if the run cannot afford a level-45-50 six-mon fight |
|
||||
| No HM field move is required anywhere in this section | grep: no `Route23`/Elite Four map references any `engine/overworld/` field-move script; the walkthrough's "you do NOT need to have every HM" is correct | - | - |
|
||||
|
||||
---
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
Coordinates are the raw asm map coordinates (tile grid, origin top-left, before
|
||||
the +4 border offset the object/warp readers apply).
|
||||
|
||||
| # | Map | Target | Intent | Precondition | Postcondition |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `VICTORY_ROAD` | warp at `(13, 5)` | walk onto warp | 8 Johto badges (already spent at `VictoryRoadGate`) | on `ROUTE_23` at warp 3 `(9, 13)` |
|
||||
| 2 | `ROUTE_23` | any tile (map load) | step | - | `ENGINE_FLYPOINT_INDIGO_PLATEAU` set |
|
||||
| 3 | `ROUTE_23` | `(9, 5)` or `(10, 5)` | walk onto warp | - | in `INDIGO_PLATEAU_POKECENTER_1F` at `(5, 13)`/`(6, 13)`; the `MAPCALLBACK_NEWMAP` resets all Elite Four scenes/flags |
|
||||
| 4 | `INDIGO_PLATEAU_POKECENTER_1F` | `INDIGOPLATEAUPOKECENTER1F_NURSE` at `(3, 7)` | talk (A, facing up from `(3, 8)`) | - | party healed (`jumpstd PokecenterNurseScript`) |
|
||||
| 5 | `INDIGO_PLATEAU_POKECENTER_1F` | `INDIGOPLATEAUPOKECENTER1F_CLERK` at `(11, 7)` | talk, buy | money | stock Hyper Potion / Full Heal / Revive / Full Restore from `MartIndigoPlateau` |
|
||||
| 6 | `INDIGO_PLATEAU_POKECENTER_1F` | avoid `(16, 4)` and `(17, 4)` | pathing constraint | only matters if `EVENT_BEAT_RIVAL_IN_MT_MOON` is set and today is Mon/Wed | skipping avoids `RIVAL2_2_*` |
|
||||
| 7 | `INDIGO_PLATEAU_POKECENTER_1F` | warp at `(14, 3)` | walk onto warp | - | in `WILLS_ROOM` at `(5, 17)`; scene 0 fires `WillsRoomDoorLocksBehindYouScript` |
|
||||
| 8 | `WILLS_ROOM` | (automatic) | wait out `applymovement` | - | player at `(5, 13)`, `EVENT_WILLS_ROOM_ENTRANCE_CLOSED` set, scene = 1 |
|
||||
| 9 | `WILLS_ROOM` | `WILLSROOM_WILL` at `(5, 7)` | walk to `(5, 8)`, face up, A | - | win -> `EVENT_BEAT_ELITE_4_WILL`, `EVENT_WILLS_ROOM_EXIT_OPEN` |
|
||||
| 10 | `WILLS_ROOM` | `(4, 2)` or `(5, 2)` | walk onto warp | `EVENT_WILLS_ROOM_EXIT_OPEN` | in `KOGAS_ROOM` at `(4, 17)`/`(5, 17)` |
|
||||
| 11 | `KOGAS_ROOM` | `KOGASROOM_KOGA` at `(5, 7)` | walk to `(5, 8)`, face up, A | door-lock cutscene done (player at `(4|5, 13)`) | `EVENT_BEAT_ELITE_4_KOGA`, `EVENT_KOGAS_ROOM_EXIT_OPEN` |
|
||||
| 12 | `KOGAS_ROOM` | `(4, 2)` / `(5, 2)` | walk onto warp | exit open | in `BRUNOS_ROOM` at `(4, 17)`/`(5, 17)` |
|
||||
| 13 | `BRUNOS_ROOM` | `BRUNOSROOM_BRUNO` at `(5, 7)` | talk | cutscene done | `EVENT_BEAT_ELITE_4_BRUNO`, `EVENT_BRUNOS_ROOM_EXIT_OPEN` |
|
||||
| 14 | `BRUNOS_ROOM` | `(4, 2)` / `(5, 2)` | walk onto warp | exit open | in `KARENS_ROOM` at `(4, 17)`/`(5, 17)` |
|
||||
| 15 | `KARENS_ROOM` | `KARENSROOM_KAREN` at `(5, 7)` | talk | cutscene done | `EVENT_BEAT_ELITE_4_KAREN`, `EVENT_KARENS_ROOM_EXIT_OPEN` |
|
||||
| 16 | `KARENS_ROOM` | `(4, 2)` / `(5, 2)` | walk onto warp | exit open | in `LANCES_ROOM` at `(4, 23)`/`(5, 23)` |
|
||||
| 17 | `LANCES_ROOM` | (automatic) | wait out `applymovement` | - | player at `(4|5, 19)`, `EVENT_LANCES_ROOM_ENTRANCE_CLOSED` set, scene = `SCENE_LANCESROOM_APPROACH_LANCE` |
|
||||
| 18 | `LANCES_ROOM` | walk to `(4, 5)` (or `(5, 5)`) | walk onto coord event | scene = 1 | `Script_ApproachLanceFromLeft/Right` -> Champion battle vs `CHAMPION`/`LANCE` |
|
||||
| 19 | `LANCES_ROOM` | (automatic) | hold A through the Mary / Oak cutscene | won | `EVENT_BEAT_CHAMPION_LANCE`, then `warp HALL_OF_FAME, 4, 13` |
|
||||
| 20 | `HALL_OF_FAME` | (automatic) | hold A through Lance's speech | scene = `SCENE_HALLOFFAME_ENTER` | `EVENT_BEAT_ELITE_FOUR`, `EVENT_TELEPORT_GUY`, S.S. Ticket call queued if needed, `halloffame` runs |
|
||||
| 21 | (screens) | Hall of Fame roster -> credits -> "The End" | wait; B skips **only** if `STATUSFLAGS_HALL_OF_FAME_F` was already set before this induction | - | back to title; save has been written by `SaveGameData` inside the ceremony |
|
||||
| 22 | (title) | CONTINUE | select | `wSpawnAfterChampion == SPAWN_LANCE` | game resumes at `SPAWN_NEW_BARK`, not at Indigo Plateau |
|
||||
|
||||
Bot notes:
|
||||
|
||||
- Do **not** re-enter `INDIGO_PLATEAU_POKECENTER_1F` mid-run to heal. You cannot
|
||||
(the doors are sealed), and if some other path got you there, its
|
||||
`MAPCALLBACK_NEWMAP` wipes `EVENT_BEAT_ELITE_4_*` and re-arms all five
|
||||
door-lock scenes, restarting the gauntlet from Will.
|
||||
- Every fight uses `winlosstext <win>, 0` - no loss branch, no
|
||||
`BATTLETYPE_CANLOSE` - so a loss is a full blackout to the last Pokémon Center.
|
||||
- After each `startbattle` the scripts run `reloadmapafterbattle`, which re-runs
|
||||
the map's `MAPCALLBACK_TILES` and therefore restores both the sealed south door
|
||||
and (once its flag is set) the opened north door.
|
||||
|
||||
---
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Map/warp/coord/object tables for all 8 maps | extracted generically by `src/import/RomExtractorGen2.lua` into the Gold cache and read by `src/world/gen2/World.lua` + `src/world/gen2/Map.lua` | implemented (data-driven; nothing map-specific is hand-written) |
|
||||
| `MAPCALLBACK_NEWMAP` / `MAPCALLBACK_TILES` dispatch (the lobby reset, the door callbacks) | `src/world/gen2/World.lua`, asserted end-to-end by `tests/drivers/gold_map_callbacks.lua` | implemented |
|
||||
| `changeblock` / `refreshmap` door swaps | `src/world/gen2/World.lua:1454` (`World:changeBlock`), `src/script/gen2/Opcodes.lua` | implemented |
|
||||
| `reanchormap` | `src/script/gen2/Vm.lua:166` - grouped with `opentext`/`closetext` as a no-op | partial (the camera re-anchor the cart does after the wall drops is not reproduced; cosmetic, but the seal-in frame may differ) |
|
||||
| `earthquake 80` shake | `src/script/gen2/Vm.lua:1044` -> `earthquakeFn`, `World:earthquake` (`src/world/gen2/World.lua:1537`) | implemented |
|
||||
| Scene scripts / `sdefer` (all five door locks, the Hall of Fame induction) | `src/script/gen2/Vm.lua:92` - `sdefer` is executed **immediately** rather than deferred until the map settles | partial (ordering deviation, called out in the source comment) |
|
||||
| `setmapscene` / `setscene` | `src/script/gen2/Opcodes.lua`, `World:mapSceneOf` (`src/world/gen2/World.lua:1179`) | implemented |
|
||||
| `loadtrainer` / `startbattle` / `winlosstext` / `reloadmapafterbattle` | `src/script/gen2/Vm.lua`, `src/battle/gen2/Battle.lua`, `src/world/gen2/Trainers.lua` | implemented |
|
||||
| Trainer party data (`WillGroup` … `ChampionGroup`), class DVs, class items | extracted by `src/import/RomExtractorGen2.lua` (`TrainerGroups`, ~line 3871) | implemented (data-driven) |
|
||||
| AI item use (Koga/Lance Full Restore, Will/Bruno Max Potion, Karen Full Heal) | `src/battle/gen2/Ai.lua:1369-1375`, `src/battle/gen2/Battle.lua:1747` | implemented |
|
||||
| Prize money `base x last level` | `src/battle/gen2/Prize.lua` (`Prize.rewardLevel`), used at `src/battle/gen2/Battle.lua:1438` | implemented |
|
||||
| `pokemart MARTTYPE_STANDARD, MART_INDIGO_PLATEAU` | `src/script/gen2/Vm.lua:597`, `src/ui/gen2/MartMenu.lua`, mart tables extracted at `src/import/RomExtractorGen2.lua:2582` | implemented |
|
||||
| `jumpstd PokecenterNurseScript` (lobby heal) | `src/script/gen2/Vm.lua` `jumpstd` + std-script table | implemented |
|
||||
| `TeleportGuyScript` warp to New Bark Town, `AbraScript` `cry ABRA` | generic `warp`/`yesorno`/`cry` opcodes | implemented (data-driven) |
|
||||
| `halloffame` opcode -> induction bookkeeping | `src/script/gen2/Opcodes.lua:171`, `src/script/gen2/Vm.lua:1382`, `src/world/gen2/World.lua:2374` (`World:hallOfFame`), `src/core/gen2/HallOfFame.lua` (`induct`, `bumpCount`, `buildParty`) | implemented |
|
||||
| Hall of Fame roster screen + credits, first-time-champion skip lockout | `src/ui/gen2/HallOfFame.lua`, `src/ui/gen2/Credits.lua`; eyeball driver `tests/drivers/gold_halloffame_shots.lua` | implemented |
|
||||
| `wSpawnAfterChampion` -> post-credits respawn at `SPAWN_NEW_BARK` | `src/core/gen2/HallOfFame.lua:51` (`POST_CREDITS_SPAWN`) and `:254` (`consumePostGameSpawn`) | **partial** - the logic exists and is unit-tested (`tests/gen2_halloffame_test.lua:164`), but grepping `src/` finds **no caller**: nothing on the CONTINUE path consumes it, so a port save resumes where it stood rather than at New Bark Town |
|
||||
| `specialphonecall SPECIALCALL_SSTICKET` from the induction | `src/script/gen2/Vm.lua` (`specialphonecall`), `src/core/gen2/Phone.lua:394` (`SPECIALCALL_SSTICKET`, id 5) | implemented |
|
||||
| `ENGINE_FLYPOINT_INDIGO_PLATEAU` -> Fly menu + Kanto map unlock | `src/world/gen2/FieldMoves.lua:368` (`SPAWN_INDIGO`, flag 63), `:414` (Kanto half gate) | implemented |
|
||||
| `ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT` daily reset | `src/core/gen2/Apricorns.lua:88` (`DAILY_ENGINE_FLAGS`, id 92) | implemented |
|
||||
| A Gold-side driver that actually walks Route 23 -> Hall of Fame | none found (`tests/drivers/gold_*.lua` has boot/walk/battle/HOF-screen drivers but no League run; `tests/drivers/bot_route.lua` and `tests/parity_lance.lua` are the **Gen 1** Kanto Elite Four) | missing |
|
||||
|
||||
---
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
1. **"Route 32 is very short, and you'll immediately be at the Pokémon League
|
||||
building."** The map between Victory Road and the Plateau is `MAP_ROUTE_23`
|
||||
(`constants/map_constants.asm:322`), not Route 32 (which is south of Violet
|
||||
City). Walkthrough typo.
|
||||
2. **The walkthrough is describing HeartGold/SoulSilver parties, not Gold.** Four
|
||||
concrete divergences, all verified against `data/trainers/parties.asm`:
|
||||
- Will's send-out order is Xatu, Jynx, **Exeggutor, Slowbro**, Xatu; the FAQ
|
||||
lists Slowbro third and Exeggutor fifth.
|
||||
- Koga's order is Ariados, **Venomoth, Forretress**, Muk, Crobat; the FAQ
|
||||
swaps 2 and 3.
|
||||
- Bruno's order is Hitmontop, **Hitmonlee, Hitmonchan**, Onix, Machamp; the
|
||||
FAQ swaps 2 and 3.
|
||||
- Karen and Lance match in order and level.
|
||||
3. **Prize money.** The FAQ's 4200 / 4400 / 4600 / 4700 / 5000 G are 100 x the
|
||||
last mon's level (HGSS). GS pays `base reward x level`
|
||||
(`ComputeTrainerReward`, `engine/battle/read_trainer_party.asm:300`) with a
|
||||
base reward of 25 for every one of these classes
|
||||
(`data/trainers/attributes.asm`), i.e. **1050 / 1100 / 1150 / 1175 / 1250 G**.
|
||||
The EXP figures quoted per Pokémon were not checked against
|
||||
`data/pokemon/base_stats/` and should be treated as HGSS numbers too.
|
||||
4. **Held items and AI items.** "Koga has three Full Restores", "Karen has one
|
||||
Full Restore", "Lance has three Full Restores", "Houndoom holding a Sitrus
|
||||
Berry", "One of the Dragonites may be holding a Sitrus Berry". In GS these
|
||||
parties are `TRAINERTYPE_MOVES`, which carries **no held item field at all**,
|
||||
and the AI's item pool is the two-byte class row: Will `MAX_POTION, NO_ITEM`;
|
||||
Bruno `MAX_POTION, NO_ITEM`; Karen `FULL_HEAL, MAX_POTION` (no Full Restore);
|
||||
Koga `FULL_HEAL, FULL_RESTORE`; Champion `FULL_HEAL, FULL_RESTORE`. Sitrus
|
||||
Berry does not exist in Gen 2.
|
||||
5. **Move names.** "Dragon Rush" (Lance's Dragonites) is a Gen 4 move; the GS
|
||||
Dragonites run Thunder Wave / Twister / Thunder or Blizzard / Hyper Beam, and
|
||||
the level-50 one runs Fire Blast / Safeguard / Outrage / Hyper Beam. Lugia's
|
||||
"Aero Blast" and Donphan/Sudowoodo suggestions in the sample lineup are fine
|
||||
as species, but none of that is asm-checkable.
|
||||
6. **"Routes 46 and 47 aren't bad, either."** `ROUTE_47` does not exist in
|
||||
pokegold (`constants/map_constants.asm` has `ROUTE_45` and `ROUTE_46` and then
|
||||
moves on). Route 47/48 are HGSS additions.
|
||||
7. **"You can go back to the Move Deleter in Blackthorn City."** There is a
|
||||
`maps/MoveDeletersHouse.asm` reached from `maps/BlackthornCity.asm`; I did not
|
||||
read its script, so the claim is plausible but unverified here.
|
||||
8. **"If you trade a Red Scale to Mr. Pokémon, he will give you EXP Share."**
|
||||
`maps/MrPokemonsHouse.asm` does `checkitem RED_SCALE` (line 53),
|
||||
`verbosegiveitem EXP_SHARE` (line 72), `takeitem RED_SCALE` (line 74), so the
|
||||
claim holds; the surrounding flag guards were not read (out of scope).
|
||||
9. **The "Abra" sprite.** `maps/IndigoPlateauPokecenter1F.asm:324` declares
|
||||
`SPRITE_JYNX` for the object whose script is `AbraScript` and whose cry is
|
||||
`cry ABRA`. There is no `SPRITE_ABRA` in `constants/sprite_constants.asm`.
|
||||
Whether this renders as a Jynx on hardware, or whether some `variablesprite`
|
||||
elsewhere rewrites it, was not chased down - no `variablesprite` targeting
|
||||
`SPRITE_JYNX` appears in `engine/events/std_scripts.asm`'s init block.
|
||||
10. **`EVENT_LANCES_ROOM_EXIT_OPEN` is never set.** `LancesRoomDoorsCallback`
|
||||
reads it, the lobby callback clears it, but no script in the tree sets it
|
||||
(grepped `maps/`). The champion's north door is opened by a bare
|
||||
`changeblock 4, 0, $0b`, which does not survive a map reload. Harmless in
|
||||
practice because the script warps you out immediately, but a bot that
|
||||
somehow re-enters Lance's room post-battle will find the door shut again.
|
||||
11. **`setevent EVENT_LANCES_ROOM_ENTRANCE_CLOSED` appears twice**: once in
|
||||
`LancesRoomDoorLocksBehindYouScript` (correct) and again immediately after
|
||||
the champion's after-battle text in `LancesRoomLanceScript`. The second one
|
||||
looks like it was meant to be the `EXIT_OPEN` set in item 10; recorded as
|
||||
observed, not corrected.
|
||||
12. **The lobby's redundant `setscene`.** `PlateauRivalPostBattle` sets the scene
|
||||
to `SCENE_INDIGOPLATEAUPOKECENTER1F_RIVAL_BATTLE`, which is 0, which is the
|
||||
map's only scene and its default. Whether this was intended to disarm the
|
||||
coord events (it does not) is a judgement call; the effective guards are the
|
||||
engine flag and the weekday check.
|
||||
13. **"Fully heal your Pokémon and save after every trainer battle."** There is
|
||||
no heal source inside the sealed rooms - no nurse object, no
|
||||
`special HealParty` until `HallOfFameEnterScript`. The advice is about bag
|
||||
items, and the asm agrees that nothing else is available.
|
||||
@@ -0,0 +1,680 @@
|
||||
# Section 20 - Saffron City Gym
|
||||
|
||||
Source: `../section-20-saffron-city-gym.txt` (the FAQ numbers this chapter "26 > Saffron City Gym")
|
||||
Maps covered: `MAP_ROUTE_6`, `MAP_ROUTE_6_UNDERGROUND_PATH_ENTRANCE`, `MAP_ROUTE_6_SAFFRON_GATE`, `MAP_SAFFRON_CITY`, `MAP_MR_PSYCHICS_HOUSE`, `MAP_SILPH_CO_1F`, `MAP_SAFFRON_MAGNET_TRAIN_STATION`, `MAP_SAFFRON_MART`, `MAP_FIGHTING_DOJO`, `MAP_SAFFRON_GYM`, `MAP_SAFFRON_POKECENTER_1F`
|
||||
Badges / key milestones in this section: MARSHBADGE (`ENGINE_MARSHBADGE`), TM29 Psychic, Up-Grade, Focus Band, `ENGINE_FLYPOINT_SAFFRON` unlocked on first entry to Saffron City.
|
||||
|
||||
Map group / id pairs a bot needs (from `constants/map_constants.asm`):
|
||||
|
||||
| Map constant | group | id |
|
||||
|---|---|---|
|
||||
| `MAP_ROUTE_6` | 12 (`VERMILION`) | 1 |
|
||||
| `MAP_ROUTE_6_SAFFRON_GATE` | 12 | 12 |
|
||||
| `MAP_ROUTE_6_UNDERGROUND_PATH_ENTRANCE` | 12 | 13 |
|
||||
| `MAP_SAFFRON_CITY` | 25 (`SAFFRON`) | 2 |
|
||||
| `MAP_FIGHTING_DOJO` | 25 | 3 |
|
||||
| `MAP_SAFFRON_GYM` | 25 | 4 |
|
||||
| `MAP_SAFFRON_MART` | 25 | 5 |
|
||||
| `MAP_SAFFRON_POKECENTER_1F` | 25 | 6 |
|
||||
| `MAP_MR_PSYCHICS_HOUSE` | 25 | 8 |
|
||||
| `MAP_SAFFRON_MAGNET_TRAIN_STATION` | 25 | 9 |
|
||||
| `MAP_SILPH_CO_1F` | 25 | 10 |
|
||||
|
||||
## 1. Route order
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 0 | `MAP_VERMILION_CITY` | `maps/VermilionCity.asm` (previous section) | - | north map connection | "Heal at the Pokémon Center, then head north onto Route 6" - Vermilion belongs to the previous section |
|
||||
| 1 | `MAP_ROUTE_6` | `maps/Route6.asm` | south connection from Vermilion City | warp 2 `(6, 1)` -> `ROUTE_6_SAFFRON_GATE` warp 3 | "barren wasteland"; the Underground Path door is blocked |
|
||||
| 1a | `MAP_ROUTE_6_UNDERGROUND_PATH_ENTRANCE` | `maps/Route6UndergroundPathEntrance.asm` | warp 1 `(17, 3)` on Route 6 | - | Walkthrough only notes it is blocked; do not enter this section |
|
||||
| 2 | `MAP_ROUTE_6_SAFFRON_GATE` | `maps/Route6SaffronGate.asm` | warp 3/4 `(4, 7)` / `(5, 7)` | warp 1/2 `(4, 0)` / `(5, 0)` -> `SAFFRON_CITY` warps 12 / 13 | "make your way all the way up through the checkpoint house" |
|
||||
| 3 | `MAP_SAFFRON_CITY` | `maps/SaffronCity.asm` | warp 12 `(16, 33)` / warp 13 `(17, 33)` | see below | Hub for the rest of the section; `MAPCALLBACK_NEWMAP` sets the fly point |
|
||||
| 4 | `MAP_MR_PSYCHICS_HOUSE` | `maps/MrPsychicsHouse.asm` | Saffron warp 5 `(27, 29)` | warp 1/2 `(2, 7)` / `(3, 7)` -> Saffron warp 5 | "Mr. Psychic ... gets you TM29 Psychic" |
|
||||
| 5 | `MAP_SILPH_CO_1F` | `maps/SilphCo1F.asm` | Saffron warp 7 `(18, 21)` | warp 1/2 `(2, 7)` / `(3, 7)` -> Saffron warp 7 | "Talk to the man blocking the pathway ... he'll give you Up-Grade" |
|
||||
| 6 | `MAP_SAFFRON_MAGNET_TRAIN_STATION` | `maps/SaffronMagnetTrainStation.asm` | Saffron warp 6 `(8, 3)` | warp 1/2 `(8, 17)` / `(9, 17)` -> Saffron warp 6 | "On the northwest side of town is the Magnet Train ... currently closed" |
|
||||
| 7 | `MAP_SAFFRON_MART` | `maps/SaffronMart.asm` | Saffron warp 3 `(25, 11)` | warp 1/2 `(2, 7)` / `(3, 7)` -> Saffron warp 3 | "There's a PokeMart if you want to buy or sell items" |
|
||||
| 8 | `MAP_FIGHTING_DOJO` | `maps/FightingDojo.asm` | Saffron warp 1 `(26, 3)` | warp 1/2 `(4, 11)` / `(5, 11)` -> Saffron warp 1 | "The former fighting gym has a Focus Band you can pick up" |
|
||||
| 9 | `MAP_SAFFRON_GYM` | `maps/SaffronGym.asm` | Saffron warp 2 `(34, 3)` | warp 1/2 `(8, 17)` / `(9, 17)` -> Saffron warp 2 | "head to the gym on the right ... Sabrina" -> MARSHBADGE |
|
||||
| 9a | `MAP_SAFFRON_POKECENTER_1F` | `maps/SaffronPokecenter1F.asm` | Saffron warp 4 `(9, 29)` | warp 1/2 `(3, 7)` / `(4, 7)` -> Saffron warp 4 | Optional mid-gym heal trip described in the walkthrough |
|
||||
|
||||
Spillover: the walkthrough's opening line ("Heal at the Pokémon Center") refers to Vermilion City, and its closing text ends inside the Saffron Gym exit - nothing past Saffron is described. `MAP_COPYCATS_HOUSE_1F` (Saffron warp 8, `(9, 11)`) exists on this map but the walkthrough never enters it in this section.
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_ROUTE_6
|
||||
|
||||
- Script: `maps/Route6.asm`
|
||||
- Blocks: `maps/Route6.blk` (`data/maps/blocks.asm:562` `Route6_Blocks`)
|
||||
- Header: `data/maps/maps.asm:294` -> `TILESET_KANTO`, `ROUTE`, `LANDMARK_ROUTE_6`, `MUSIC_ROUTE_3`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_POND`
|
||||
- Dimensions: `constants/map_constants.asm:271` `map_const ROUTE_6, 10, 9` (10x9 blocks = 20x18 cells)
|
||||
- Connections: `data/maps/attributes.asm:346-348` - north `SaffronCity` (offset -5), south `VermilionCity` (offset -5). No east/west.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 17 | 3 | `ROUTE_6_UNDERGROUND_PATH_ENTRANCE` | 1 |
|
||||
| 2 | 6 | 1 | `ROUTE_6_SAFFRON_GATE` | 3 |
|
||||
|
||||
**Coord events** (`def_coord_events`) - none.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 19 | 5 | `BGEVENT_READ` | `Route6UndergroundPathSign` (`Route6UndergroundPathSignText`) |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE6_POKEFAN_M` | `SPRITE_POKEFAN_M` | 17 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` (sight field 2, unused) | `Route6PokefanMScript` (`4f:581a`) | `EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route6PokefanMScript` (`maps/Route6.asm:9`, sym `4f:581a`): a bare `jumptextfaceplayer Route6PokefanMText` - "The road is closed until the problem at the POWER PLANT is solved." No flag reads. He stands at `(17, 4)`, directly south of the Underground Path door at `(17, 3)`, so his body is the block, not a script check.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH` | `constants/event_flags.asm:1299` | set by `maps/PowerPlant.asm:164`; consumed as the object's event flag on `maps/Route6.asm:43` and `maps/Route5.asm:56` | Flag **set** hides the object (`CheckObjectFlag`, `engine/overworld/map_objects_2.asm:32`). Clear (default) = the man is standing there = Underground Path shut |
|
||||
|
||||
**Items** - none on this map.
|
||||
|
||||
**Trainers** - none on this map.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
`data/wild/kanto_grass.asm:480` `def_grass_wildmons ROUTE_6`, encounter rates 10/10/10 percent (morn/day/nite). Gold block (`IF DEF(_GOLD)`, lines 483-506):
|
||||
|
||||
| slot | morn | day | nite |
|
||||
|---|---|---|---|
|
||||
| 1 | L13 `PIDGEY` | L13 `PIDGEY` | L13 `ODDISH` |
|
||||
| 2 | L13 `BELLSPROUT` | L13 `BELLSPROUT` | L14 `ODDISH` |
|
||||
| 3 | L14 `PIDGEY` | L14 `PIDGEY` | L13 `BELLSPROUT` |
|
||||
| 4 | L15 `MAGNEMITE` | L15 `MAGNEMITE` | L15 `MAGNEMITE` |
|
||||
| 5 | L12 `ABRA` | L12 `ABRA` | L12 `ABRA` |
|
||||
| 6 | L14 `ABRA` | L14 `ABRA` | L14 `ABRA` |
|
||||
| 7 | L14 `ABRA` | L14 `ABRA` | L14 `ABRA` |
|
||||
|
||||
Silver (`ELIF DEF(_SILVER)`, lines 508-531) swaps the third slot for `MEOWTH` (L14) morn/day and slots 2/3 for `MEOWTH`/`BELLSPROUT` at nite.
|
||||
|
||||
Water: `data/wild/kanto_water.asm:12` `def_water_wildmons ROUTE_6`, 2 percent - L10 `PSYDUCK`, L5 `PSYDUCK`, L10 `GOLDUCK`.
|
||||
|
||||
Fishing group is `FISHGROUP_POND` (`data/maps/maps.asm:294`).
|
||||
|
||||
### MAP_ROUTE_6_UNDERGROUND_PATH_ENTRANCE
|
||||
|
||||
- Script: `maps/Route6UndergroundPathEntrance.asm`
|
||||
- Blocks: shared `maps/UndergroundPathEntrance.blk` (`data/maps/blocks.asm:466`)
|
||||
- Header: `data/maps/maps.asm:306` -> `TILESET_GATE`, `GATE`, `LANDMARK_ROUTE_6`, `MUSIC_ROUTE_3`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:283` `map_const ROUTE_6_UNDERGROUND_PATH_ENTRANCE, 4, 4`
|
||||
- Connections: none (indoor)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 7 | `ROUTE_6` | 1 |
|
||||
| 2 | 4 | 7 | `ROUTE_6` | 1 |
|
||||
| 3 | 4 | 3 | `UNDERGROUND_PATH` | 2 |
|
||||
|
||||
No coord events, no bg events, no object events. Nothing gates warp 3 from the inside - the only block is the NPC body on Route 6.
|
||||
|
||||
### MAP_ROUTE_6_SAFFRON_GATE
|
||||
|
||||
- Script: `maps/Route6SaffronGate.asm`
|
||||
- Blocks: shared `maps/NorthSouthGate.blk` (`data/maps/blocks.asm:210,215`)
|
||||
- Header: `data/maps/maps.asm:305` -> `TILESET_GATE`, `GATE`, `LANDMARK_ROUTE_6`, `MUSIC_ROUTE_3`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:282` `map_const ROUTE_6_SAFFRON_GATE, 5, 4`
|
||||
- Scene scripts: one entry, `scene_script Route6SaffronGateNoopScene` with no scene-id argument (`maps/Route6SaffronGate.asm:6`), so no `SCENE_*` constant is generated and the scene is a bare `end`.
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 0 | `SAFFRON_CITY` | 12 |
|
||||
| 2 | 5 | 0 | `SAFFRON_CITY` | 13 |
|
||||
| 3 | 4 | 7 | `ROUTE_6` | 2 |
|
||||
| 4 | 5 | 7 | `ROUTE_6` | 2 |
|
||||
|
||||
**Coord events** - none. **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE6SAFFRONGATE_OFFICER` | `SPRITE_OFFICER` | 0 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route6SaffronGuardScript` (`59:523c`) | -1 (always) |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route6SaffronGuardScript` (`maps/Route6SaffronGate.asm:13`, sym `59:523c`): `faceplayer` / `opentext` / `checkevent EVENT_RETURNED_MACHINE_PART` / `iftrue .ReturnedPart`. Before the Power Plant is fixed he explains the Magnet Train is dead; after, a one-liner. He stands at `(0, 4)`, off the walking lane - he never blocks passage.
|
||||
|
||||
### MAP_SAFFRON_CITY
|
||||
|
||||
- Script: `maps/SaffronCity.asm`
|
||||
- Blocks: `maps/SaffronCity.blk` (`data/maps/blocks.asm:24`)
|
||||
- Header: `data/maps/maps.asm:490` -> `TILESET_KANTO`, `TOWN`, `LANDMARK_SAFFRON_CITY`, `MUSIC_VIRIDIAN_CITY`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:454` `map_const SAFFRON_CITY, 20, 18` (40x36 cells)
|
||||
- Connections: `data/maps/attributes.asm:350-354` - north `Route5` (+5), south `Route6` (+5), west `Route7` (+9), east `Route8` (+9)
|
||||
- Spawn / fly: `data/maps/spawn_points.asm:21` `spawn SAFFRON_CITY, 9, 30`; `data/maps/flypoints.asm:26` `db LANDMARK_SAFFRON_CITY, SPAWN_SAFFRON`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 26 | 3 | `FIGHTING_DOJO` | 1 |
|
||||
| 2 | 34 | 3 | `SAFFRON_GYM` | 1 |
|
||||
| 3 | 25 | 11 | `SAFFRON_MART` | 2 |
|
||||
| 4 | 9 | 29 | `SAFFRON_POKECENTER_1F` | 1 |
|
||||
| 5 | 27 | 29 | `MR_PSYCHICS_HOUSE` | 1 |
|
||||
| 6 | 8 | 3 | `SAFFRON_MAGNET_TRAIN_STATION` | 2 |
|
||||
| 7 | 18 | 21 | `SILPH_CO_1F` | 1 |
|
||||
| 8 | 9 | 11 | `COPYCATS_HOUSE_1F` | 1 |
|
||||
| 9 | 18 | 3 | `ROUTE_5_SAFFRON_GATE` | 3 |
|
||||
| 10 | 0 | 24 | `ROUTE_7_SAFFRON_GATE` | 3 |
|
||||
| 11 | 0 | 25 | `ROUTE_7_SAFFRON_GATE` | 4 |
|
||||
| 12 | 16 | 33 | `ROUTE_6_SAFFRON_GATE` | 1 |
|
||||
| 13 | 17 | 33 | `ROUTE_6_SAFFRON_GATE` | 2 |
|
||||
| 14 | 39 | 22 | `ROUTE_8_SAFFRON_GATE` | 1 |
|
||||
| 15 | 39 | 23 | `ROUTE_8_SAFFRON_GATE` | 2 |
|
||||
|
||||
**Coord events** - none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 21 | 5 | `BGEVENT_READ` | `SaffronCitySign` |
|
||||
| 33 | 5 | `BGEVENT_READ` | `SaffronGymSign` |
|
||||
| 25 | 5 | `BGEVENT_READ` | `FightingDojoSign` |
|
||||
| 15 | 21 | `BGEVENT_READ` | `SilphCoSign` |
|
||||
| 25 | 29 | `BGEVENT_READ` | `MrPsychicsHouseSign` |
|
||||
| 11 | 5 | `BGEVENT_READ` | `SaffronCityMagnetTrainStationSign` |
|
||||
| 10 | 29 | `BGEVENT_READ` | `SaffronCityPokecenterSign` (`jumpstd PokecenterSignScript`) |
|
||||
| 26 | 11 | `BGEVENT_READ` | `SaffronCityMartSign` (`jumpstd MartSignScript`) |
|
||||
|
||||
No hidden items on this map.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `SAFFRONCITY_LASS1` | `SPRITE_LASS` | 7 | 14 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (rx 2) | `OBJECTTYPE_SCRIPT` | `SaffronCityLass1Script` | -1 |
|
||||
| `SAFFRONCITY_POKEFAN_M` | `SPRITE_POKEFAN_M` | 19 | 30 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (rx 2) | `OBJECTTYPE_SCRIPT` | `SaffronCityPokefanMScript` | -1 |
|
||||
| `SAFFRONCITY_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 32 | 7 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (rx 1) | `OBJECTTYPE_SCRIPT` | `SaffronCityCooltrainerMScript` | -1 |
|
||||
| `SAFFRONCITY_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 20 | 24 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (rx 2) | `OBJECTTYPE_SCRIPT` | `SaffronCityCooltrainerFScript` | -1 |
|
||||
| `SAFFRONCITY_FISHER` | `SPRITE_FISHER` | 27 | 12 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `SaffronCityFisherScript` | -1 |
|
||||
| `SAFFRONCITY_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 15 | 19 | `SPRITEMOVEDATA_WALK_UP_DOWN` (ry 1) | `OBJECTTYPE_SCRIPT` | `SaffronCityYoungster1Script` | -1 |
|
||||
| `SAFFRONCITY_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 35 | 22 | `SPRITEMOVEDATA_WANDER` (rx 1, ry 1) | `OBJECTTYPE_SCRIPT` | `SaffronCityYoungster2Script` | -1 |
|
||||
| `SAFFRONCITY_LASS2` | `SPRITE_LASS` | 19 | 8 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `SaffronCityLass2Script` | -1 |
|
||||
|
||||
Every Saffron City NPC is `OBJECTTYPE_SCRIPT`; none of them blocks a warp tile.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `SaffronCityFlypointCallback` (`maps/SaffronCity.asm:17`), registered as `callback MAPCALLBACK_NEWMAP`: `setflag ENGINE_FLYPOINT_SAFFRON` / `endcallback`. Walking in from the Route 6 gate is what unlocks Fly to Saffron - there is no NPC or item involved.
|
||||
- `SaffronCityLass1Script`, `SaffronCityPokefanMScript`, `SaffronCityFisherScript` all branch on `checkevent EVENT_RETURNED_MACHINE_PART`. Purely dialogue; useful only as a state probe.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_FLYPOINT_SAFFRON` | `constants/engine_flags.asm:74` | set by `SaffronCityFlypointCallback` | Fly destination unlocked; also the `SPAWN_SAFFRON` blackout target once the player has been here |
|
||||
| `EVENT_RETURNED_MACHINE_PART` | `constants/event_flags.asm:200` | set in `maps/PowerPlant.asm:161`; read by four Saffron scripts and `Route6SaffronGuardScript` | Not set in this section. Its absence is what keeps the Magnet Train shut |
|
||||
|
||||
**Items** - none directly on the city map.
|
||||
|
||||
**Trainers** - none.
|
||||
|
||||
**Wild encounters** - none. `SAFFRON_CITY` has no entry in `data/wild/kanto_grass.asm`, `kanto_water.asm`, `fish.asm` or `treemons.asm` (grep over `data/wild/` for `SAFFRON` returns nothing).
|
||||
|
||||
### MAP_MR_PSYCHICS_HOUSE
|
||||
|
||||
- Script: `maps/MrPsychicsHouse.asm`
|
||||
- Blocks: shared `maps/House1.blk` (`data/maps/blocks.asm:195,201`)
|
||||
- Header: `data/maps/maps.asm:496` -> `TILESET_HOUSE`, `INDOOR`, `LANDMARK_SAFFRON_CITY`, `MUSIC_VIRIDIAN_CITY`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:460` `map_const MR_PSYCHICS_HOUSE, 4, 4`
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 2 | 7 | `SAFFRON_CITY` | 5 |
|
||||
| 2 | 3 | 7 | `SAFFRON_CITY` | 5 |
|
||||
|
||||
**Coord events** - none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 0 | 1 | `BGEVENT_READ` | `MrPsychicsHouseBookshelf` (`jumpstd DifficultBookshelfScript`) |
|
||||
| 1 | 1 | `BGEVENT_READ` | `MrPsychicsHouseBookshelf` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `MRPSYCHICSHOUSE_FISHING_GURU` | `SPRITE_FISHING_GURU` | 5 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `MrPsychic` (`61:4b15`) | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `MrPsychic` (`maps/MrPsychicsHouse.asm:9`, sym `61:4b15`):
|
||||
`faceplayer` / `opentext` / `checkevent EVENT_GOT_TM29_PSYCHIC` / `iftrue .AlreadyGotItem` / `writetext MrPsychicText1` / `promptbutton` / `verbosegiveitem TM_PSYCHIC_M` / `iffalse .Done` / `setevent EVENT_GOT_TM29_PSYCHIC` / `.AlreadyGotItem: writetext MrPsychicText2` / `waitbutton` / `.Done: closetext` / `end`.
|
||||
One `verbosegiveitem`; a full bag drops through `.Done` **without** setting the flag, so a bot must re-talk after making room.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_GOT_TM29_PSYCHIC` | `constants/event_flags.asm:226` | read+set by `MrPsychic` | One-shot guard on TM29 |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `TM_PSYCHIC_M` (TM29, `constants/item_constants.asm:250` `add_tm PSYCHIC_M ; dd`) | talk to `MRPSYCHICSHOUSE_FISHING_GURU` at `(5, 3)` | `MrPsychic`, `verbosegiveitem` | `EVENT_GOT_TM29_PSYCHIC` |
|
||||
|
||||
### MAP_SILPH_CO_1F
|
||||
|
||||
- Script: `maps/SilphCo1F.asm`
|
||||
- Blocks: `maps/SilphCo1F.blk` (`data/maps/blocks.asm:874`)
|
||||
- Header: `data/maps/maps.asm:498` -> `TILESET_FACILITY`, `INDOOR`, `LANDMARK_SAFFRON_CITY`, `MUSIC_VIRIDIAN_CITY`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:462` `map_const SILPH_CO_1F, 8, 4`
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 2 | 7 | `SAFFRON_CITY` | 7 |
|
||||
| 2 | 3 | 7 | `SAFFRON_CITY` | 7 |
|
||||
|
||||
**Coord events** - none. **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `SILPHCO1F_RECEPTIONIST` | `SPRITE_RECEPTIONIST` | 4 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `SilphCoReceptionistScript` | -1 |
|
||||
| `SILPHCO1F_OFFICER` | `SPRITE_OFFICER` | 13 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `SilphCoOfficerScript` (`61:4f81`) | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `SilphCoOfficerScript` (`maps/SilphCo1F.asm:13`, sym `61:4f81`): `faceplayer` / `opentext` / `checkevent EVENT_GOT_UP_GRADE` / `iftrue .GotUpGrade` / `writetext SilphCoOfficerText` / `promptbutton` / `verbosegiveitem UP_GRADE` / `iffalse .NoRoom` / `setevent EVENT_GOT_UP_GRADE` / `.GotUpGrade: writetext ...` / `waitbutton` / `.NoRoom: closetext` / `end`. Same full-bag shape as Mr. Psychic. There is no upstairs on this map at all - the "man blocking the stairway" is `(13, 1)`, standing on the only path north; nothing scripted opens it.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_GOT_UP_GRADE` | `constants/event_flags.asm:221` | read+set by `SilphCoOfficerScript` | One-shot guard on Up-Grade |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `UP_GRADE` (`constants/item_constants.asm:180`, `$ac`) | talk to `SILPHCO1F_OFFICER` at `(13, 1)` | `SilphCoOfficerScript`, `verbosegiveitem` | `EVENT_GOT_UP_GRADE` |
|
||||
|
||||
### MAP_SAFFRON_MAGNET_TRAIN_STATION
|
||||
|
||||
- Script: `maps/SaffronMagnetTrainStation.asm`
|
||||
- Blocks: `maps/SaffronMagnetTrainStation.blk`
|
||||
- Header: `data/maps/maps.asm:497` -> `TILESET_TRAIN_STATION`, `INDOOR`, `LANDMARK_SAFFRON_CITY`, `MUSIC_VIRIDIAN_CITY`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:461` `map_const SAFFRON_MAGNET_TRAIN_STATION, 10, 9`
|
||||
- Scene variable: `data/maps/scenes.asm:17` `scene_var SAFFRON_MAGNET_TRAIN_STATION, wSaffronMagnetTrainStationSceneID`
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 8 | 17 | `SAFFRON_CITY` | 6 |
|
||||
| 2 | 9 | 17 | `SAFFRON_CITY` | 6 |
|
||||
| 3 | 6 | 5 | `GOLDENROD_MAGNET_TRAIN_STATION` | 4 |
|
||||
| 4 | 11 | 5 | `GOLDENROD_MAGNET_TRAIN_STATION` | 3 |
|
||||
|
||||
**Coord events**
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_SAFFRONMAGNETTRAINSTATION_ARRIVE_FROM_GOLDENROD` (= 0, generated by the `scene_script` macro at `maps/SaffronMagnetTrainStation.asm:9`) | 11 | 6 | `Script_ArriveFromGoldenrod` | Arrival cutscene when riding in from Goldenrod; not reachable in this section |
|
||||
|
||||
**BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `SAFFRONMAGNETTRAINSTATION_OFFICER` | `SPRITE_OFFICER` | 9 | 9 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `SaffronMagnetTrainStationOfficerScript` (`61:4bc2`) | -1 |
|
||||
| `SAFFRONMAGNETTRAINSTATION_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 10 | 14 | `SPRITEMOVEDATA_WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `SaffronMagnetTrainStationGymGuideScript` | -1 |
|
||||
| `SAFFRONMAGNETTRAINSTATION_TEACHER` | `SPRITE_TEACHER` | 6 | 11 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `SaffronMagnetTrainStationTeacherScript` | `EVENT_SAFFRON_TRAIN_STATION_POPULATION` |
|
||||
| `SAFFRONMAGNETTRAINSTATION_LASS` | `SPRITE_LASS` | 6 | 10 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `SaffronMagnetTrainStationLassScript` | `EVENT_SAFFRON_TRAIN_STATION_POPULATION` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `SaffronMagnetTrainStationOfficerScript` (`maps/SaffronMagnetTrainStation.asm:16`, sym `61:4bc2`): `checkevent EVENT_RESTORED_POWER_TO_KANTO` / `iftrue .MagnetTrainToGoldenrod`, else "the MAGNET TRAIN isn't operating now" and `end`. The ride arm additionally runs `yesorno`, `checkitem PASS`, two `applymovement`s, `setval TRUE` / `special MagnetTrain` / `warpcheck` / `newloadmap MAPSETUP_TRAIN`. In this section both `EVENT_RESTORED_POWER_TO_KANTO` and `PASS` are missing, so the officer is a dead end.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_RESTORED_POWER_TO_KANTO` | `constants/event_flags.asm:204` | set at `maps/PowerPlant.asm:165`; read by the officer script | Gate on the Magnet Train being operable at all |
|
||||
| `EVENT_SAFFRON_TRAIN_STATION_POPULATION` | `constants/event_flags.asm:1300` | cleared at `maps/PowerPlant.asm:160` | While **set** (the state in this section) the Teacher and Lass are hidden - the station looks empty on purpose |
|
||||
|
||||
### MAP_SAFFRON_MART
|
||||
|
||||
- Script: `maps/SaffronMart.asm`
|
||||
- Blocks: shared `maps/Mart.blk` (`data/maps/blocks.asm:333,335`)
|
||||
- Header: `data/maps/maps.asm:493` -> `TILESET_MART`, `INDOOR`, `LANDMARK_SAFFRON_CITY`, `MUSIC_VIRIDIAN_CITY`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:457` `map_const SAFFRON_MART, 6, 4`
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 2 | 7 | `SAFFRON_CITY` | 3 |
|
||||
| 2 | 3 | 7 | `SAFFRON_CITY` | 3 |
|
||||
|
||||
**Coord events** - none. **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `SAFFRONMART_CLERK` | `SPRITE_CLERK` | 1 | 3 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `SaffronMartClerkScript` | -1 |
|
||||
| `SAFFRONMART_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 7 | 2 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `SaffronMartCooltrainerMScript` | -1 |
|
||||
| `SAFFRONMART_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 7 | 6 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (rx 1) | `OBJECTTYPE_SCRIPT` | `SaffronMartCooltrainerFScript` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `SaffronMartClerkScript`: `opentext` / `pokemart MARTTYPE_STANDARD, MART_SAFFRON` / `closetext` / `end`.
|
||||
|
||||
**Items** - shop stock, `data/items/marts.asm:362` `MartSaffron`: `GREAT_BALL`, `ULTRA_BALL`, `HYPER_POTION`, `MAX_POTION`, `FULL_HEAL`, `X_ATTACK`, `X_DEFEND`, `FLOWER_MAIL`.
|
||||
|
||||
### MAP_FIGHTING_DOJO
|
||||
|
||||
- Script: `maps/FightingDojo.asm`
|
||||
- Blocks: `maps/FightingDojo.blk` (`data/maps/blocks.asm:871`)
|
||||
- Header: `data/maps/maps.asm:491` -> `TILESET_TRAIN_STATION`, `INDOOR`, `LANDMARK_SAFFRON_CITY`, `MUSIC_VIRIDIAN_CITY`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:455` `map_const FIGHTING_DOJO, 5, 6`
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 11 | `SAFFRON_CITY` | 1 |
|
||||
| 2 | 5 | 11 | `SAFFRON_CITY` | 1 |
|
||||
|
||||
**Coord events** - none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 4 | 0 | `BGEVENT_READ` | `FightingDojoSign1` |
|
||||
| 5 | 0 | `BGEVENT_READ` | `FightingDojoSign2` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `FIGHTINGDOJO_BLACK_BELT` | `SPRITE_BLACK_BELT` | 4 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `FightingDojoBlackBelt` | -1 |
|
||||
| `FIGHTINGDOJO_POKE_BALL` | `SPRITE_POKE_BALL` | 3 | 1 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `FightingDojoFocusBand` (`61:400b`) | `EVENT_PICKED_UP_FOCUS_BAND` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `FightingDojoFocusBand` (`maps/FightingDojo.asm:19`, sym `61:400b`): the two-byte `itemball FOCUS_BAND` struct (macro at `macros/scripts/maps.asm:154`, quantity defaults to 1), not a script. The engine's own item-ball path runs it.
|
||||
- There are **no trainers** in the Fighting Dojo in Gen 2. `FightingDojoBlackBeltText` explains the Karate King is away in a Johto cave (Rock Tunnel-era content elsewhere), so nothing here fights back.
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `FOCUS_BAND` (`constants/item_constants.asm:127`, `$77`) | walk onto / press A at `(3, 1)` | `FightingDojoFocusBand` (`OBJECTTYPE_ITEMBALL`) | `EVENT_PICKED_UP_FOCUS_BAND` (`constants/event_flags.asm:1312`) - once set the ball object is masked |
|
||||
|
||||
### MAP_SAFFRON_GYM
|
||||
|
||||
- Script: `maps/SaffronGym.asm`
|
||||
- Blocks: `maps/SaffronGym.blk` (`data/maps/blocks.asm:861`)
|
||||
- Header: `data/maps/maps.asm:492` -> `TILESET_UNDERGROUND`, `INDOOR`, `LANDMARK_SAFFRON_CITY`, `MUSIC_GYM`, phone `TRUE` (phone calls suppressed), `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:456` `map_const SAFFRON_GYM, 10, 9` (20x18 cells)
|
||||
- Connections: none
|
||||
|
||||
**Warps** (`def_warp_events`, `maps/SaffronGym.asm:294-326`) - warps 1-2 are the street doors; warps 3-32 are the 30 teleport pads. Every pad's destination is `SAFFRON_GYM` and the player lands on the destination warp's own tile.
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 8 | 17 | `SAFFRON_CITY` | 2 |
|
||||
| 2 | 9 | 17 | `SAFFRON_CITY` | 2 |
|
||||
| 3 | 11 | 15 | `SAFFRON_GYM` | 18 |
|
||||
| 4 | 19 | 15 | `SAFFRON_GYM` | 19 |
|
||||
| 5 | 19 | 11 | `SAFFRON_GYM` | 20 |
|
||||
| 6 | 1 | 11 | `SAFFRON_GYM` | 21 |
|
||||
| 7 | 5 | 3 | `SAFFRON_GYM` | 22 |
|
||||
| 8 | 11 | 5 | `SAFFRON_GYM` | 23 |
|
||||
| 9 | 1 | 15 | `SAFFRON_GYM` | 24 |
|
||||
| 10 | 19 | 3 | `SAFFRON_GYM` | 25 |
|
||||
| 11 | 15 | 17 | `SAFFRON_GYM` | 26 |
|
||||
| 12 | 5 | 17 | `SAFFRON_GYM` | 27 |
|
||||
| 13 | 5 | 9 | `SAFFRON_GYM` | 28 |
|
||||
| 14 | 9 | 3 | `SAFFRON_GYM` | 29 |
|
||||
| 15 | 15 | 9 | `SAFFRON_GYM` | 30 |
|
||||
| 16 | 15 | 5 | `SAFFRON_GYM` | 31 |
|
||||
| 17 | 1 | 5 | `SAFFRON_GYM` | 32 |
|
||||
| 18 | 19 | 17 | `SAFFRON_GYM` | 3 |
|
||||
| 19 | 19 | 9 | `SAFFRON_GYM` | 4 |
|
||||
| 20 | 1 | 9 | `SAFFRON_GYM` | 5 |
|
||||
| 21 | 5 | 5 | `SAFFRON_GYM` | 6 |
|
||||
| 22 | 11 | 3 | `SAFFRON_GYM` | 7 |
|
||||
| 23 | 1 | 17 | `SAFFRON_GYM` | 8 |
|
||||
| 24 | 19 | 5 | `SAFFRON_GYM` | 9 |
|
||||
| 25 | 15 | 15 | `SAFFRON_GYM` | 10 |
|
||||
| 26 | 5 | 15 | `SAFFRON_GYM` | 11 |
|
||||
| 27 | 5 | 11 | `SAFFRON_GYM` | 12 |
|
||||
| 28 | 9 | 5 | `SAFFRON_GYM` | 13 |
|
||||
| 29 | 15 | 11 | `SAFFRON_GYM` | 14 |
|
||||
| 30 | 15 | 3 | `SAFFRON_GYM` | 15 |
|
||||
| 31 | 1 | 3 | `SAFFRON_GYM` | 16 |
|
||||
| 32 | 11 | 9 | `SAFFRON_GYM` | 17 |
|
||||
|
||||
**Teleporter graph, resolved to landing coordinates.** Every pair is mutual, so the graph is 15 undirected edges. Room labels are derived from the pad clusters and the object coordinates; they are a reading aid, the coordinates are the ground truth.
|
||||
|
||||
| room (occupant) | pads (x, y) |
|
||||
|---|---|
|
||||
| ENTRANCE (doors `(8,17)`/`(9,17)`, guide `(9,14)`, statue `(8,15)`) | `(11,15)` |
|
||||
| BL (Psychic Franklin `(3,16)`) | `(1,15)` `(5,15)` `(1,17)` `(5,17)` |
|
||||
| BR (Medium Rebecca `(17,16)`) | `(15,15)` `(19,15)` `(15,17)` `(19,17)` |
|
||||
| ML | `(1,9)` `(5,9)` `(1,11)` `(5,11)` |
|
||||
| CENTER (Sabrina `(9,8)`) | `(11,9)` |
|
||||
| MR | `(15,9)` `(19,9)` `(15,11)` `(19,11)` |
|
||||
| TL (Medium Doris `(3,4)`) | `(1,3)` `(5,3)` `(1,5)` `(5,5)` |
|
||||
| TC | `(9,3)` `(11,3)` `(9,5)` `(11,5)` |
|
||||
| TR (Psychic Jared `(17,4)`) | `(15,3)` `(19,3)` `(15,5)` `(19,5)` |
|
||||
|
||||
| step on pad | warp idx | lands at | room reached |
|
||||
|---|---|---|---|
|
||||
| `(11,15)` ENTRANCE | 3 | `(19,17)` | BR |
|
||||
| `(19,17)` BR | 18 | `(11,15)` | ENTRANCE |
|
||||
| `(19,15)` BR | 4 | `(19,9)` | MR |
|
||||
| `(19,9)` MR | 19 | `(19,15)` | BR |
|
||||
| `(15,17)` BR | 11 | `(5,15)` | BL |
|
||||
| `(5,15)` BL | 26 | `(15,17)` | BR |
|
||||
| `(15,15)` BR | 25 | `(19,3)` | TR |
|
||||
| `(19,3)` TR | 10 | `(15,15)` | BR |
|
||||
| `(1,15)` BL | 9 | `(19,5)` | TR |
|
||||
| `(19,5)` TR | 24 | `(1,15)` | BL |
|
||||
| `(1,17)` BL | 23 | `(11,5)` | TC |
|
||||
| `(11,5)` TC | 8 | `(1,17)` | BL |
|
||||
| `(5,17)` BL | 12 | `(5,11)` | ML |
|
||||
| `(5,11)` ML | 27 | `(5,17)` | BL |
|
||||
| `(1,9)` ML | 20 | `(19,11)` | MR |
|
||||
| `(19,11)` MR | 5 | `(1,9)` | ML |
|
||||
| `(5,9)` ML | 13 | `(9,5)` | TC |
|
||||
| `(9,5)` TC | 28 | `(5,9)` | ML |
|
||||
| `(1,11)` ML | 6 | `(5,5)` | TL |
|
||||
| `(5,5)` TL | 21 | `(1,11)` | ML |
|
||||
| `(15,9)` MR | 15 | `(15,3)` | TR |
|
||||
| `(15,3)` TR | 30 | `(15,9)` | MR |
|
||||
| `(15,11)` MR | 29 | `(9,3)` | TC |
|
||||
| `(9,3)` TC | 14 | `(15,11)` | MR |
|
||||
| `(5,3)` TL | 7 | `(11,3)` | TC |
|
||||
| `(11,3)` TC | 22 | `(5,3)` | TL |
|
||||
| `(1,3)` TL | 31 | `(15,5)` | TR |
|
||||
| `(15,5)` TR | 16 | `(1,3)` | TL |
|
||||
| **`(1,5)` TL** | **17** | **`(11,9)`** | **CENTER (Sabrina)** |
|
||||
| **`(11,9)` CENTER** | **32** | **`(1,5)`** | **TL** |
|
||||
|
||||
`(1,5)` in the top-left room is the only pad that reaches Sabrina, and `(11,9)` is the only way out of her chamber. This matches the walkthrough's "go to the bottom left teleporter and you'll go straight to the gym leader".
|
||||
|
||||
**Coord events** - none. The gym has no trip-wires; every transition is a warp tile.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 8 | 15 | `BGEVENT_READ` | `SaffronGymStatue` (`61:4170`) |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | sight | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `SAFFRONGYM_SABRINA` | `SPRITE_SABRINA` | 9 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `SaffronGymSabrinaScript` (`61:40cf`) | -1 |
|
||||
| `SAFFRONGYM_GRANNY1` | `SPRITE_GRANNY` | 17 | 16 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerMediumRebecca` (`61:410c`) | -1 |
|
||||
| `SAFFRONGYM_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 3 | 16 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerPsychicFranklin` (`61:4120`) | -1 |
|
||||
| `SAFFRONGYM_GRANNY2` | `SPRITE_GRANNY` | 3 | 4 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 2 | `TrainerMediumDoris` (`61:4134`) | -1 |
|
||||
| `SAFFRONGYM_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 17 | 4 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 2 | `TrainerPsychicJared` (`61:4148`) | -1 |
|
||||
| `SAFFRONGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 9 | 14 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `SaffronGymGuideScript` | -1 |
|
||||
|
||||
All four trainers spin (`SPINRANDOM_FAST`), so their line of sight rotates - a bot cannot assume a fixed approach lane; walking into the room will trigger them.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `SaffronGymSabrinaScript` (`maps/SaffronGym.asm:14`, sym `61:40cf`):
|
||||
`faceplayer` / `opentext` / `checkflag ENGINE_MARSHBADGE` / `iftrue .FightDone` / `writetext SabrinaIntroText` / `waitbutton` / `closetext` / `winlosstext SabrinaWinLossText, 0` / `loadtrainer SABRINA, SABRINA1` / `startbattle` / `reloadmapafterbattle` / then a burst of five `setevent`s: `EVENT_BEAT_SABRINA`, `EVENT_BEAT_MEDIUM_REBECCA`, `EVENT_BEAT_MEDIUM_DORIS`, `EVENT_BEAT_PSYCHIC_FRANKLIN`, `EVENT_BEAT_PSYCHIC_JARED` / `opentext` / `writetext ReceivedMarshBadgeText` / `playsound SFX_GET_BADGE` / `waitsfx` / `setflag ENGINE_MARSHBADGE` / `writetext SabrinaMarshBadgeText` / `waitbutton` / `closetext` / `end`.
|
||||
Two consequences a bot should exploit: **the four gym trainers are auto-cleared when Sabrina falls**, so they are entirely skippable; and the badge is a `setflag` on `ENGINE_MARSHBADGE`, which is also the re-entry guard (`.FightDone`).
|
||||
- `TrainerMediumRebecca` / `TrainerPsychicFranklin` / `TrainerMediumDoris` / `TrainerPsychicJared` (`maps/SaffronGym.asm:47,58,69,80`): each is a `trainer CLASS, ID, EVENT_BEAT_*, SeenText, BeatenText, 0, .Script` struct (`macros/scripts/maps.asm:142`; field order is group, id, flag, seen, win, loss, after) followed by an `endifjustbattled` / `opentext` / `writetext ...AfterBattleText` / `waitbutton` / `closetext` / `end` after-battle script.
|
||||
- `SaffronGymStatue` (`maps/SaffronGym.asm:107`, sym `61:4170`): `checkflag ENGINE_MARSHBADGE` / `iftrue .Beaten` / `jumpstd GymStatue1Script`; the beaten arm does `gettrainername STRING_BUFFER_4, SABRINA, SABRINA1` then `jumpstd GymStatue2Script`. Handy as a cheap post-badge assertion.
|
||||
- `SaffronGymGuideScript` (`maps/SaffronGym.asm:91`): branches on `checkevent EVENT_BEAT_SABRINA`; no items, no flags written.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_MARSHBADGE` | `constants/engine_flags.asm:52` (bit 5 of `wKantoBadges`; Kanto order is Boulder, Cascade, Thunder, Rainbow, Soul, **Marsh**, Volcano, Earth) | `checkflag` in `SaffronGymSabrinaScript` and `SaffronGymStatue`; `setflag` after the win | The section's terminal postcondition |
|
||||
| `EVENT_BEAT_SABRINA` | `constants/event_flags.asm:720` | set by `SaffronGymSabrinaScript`; read by `SaffronGymGuideScript` | Leader defeated |
|
||||
| `EVENT_BEAT_MEDIUM_REBECCA` | `constants/event_flags.asm:935` | trainer struct flag; also force-set by the Sabrina script | Rebecca cleared |
|
||||
| `EVENT_BEAT_MEDIUM_DORIS` | `constants/event_flags.asm:936` | as above | Doris cleared |
|
||||
| `EVENT_BEAT_PSYCHIC_FRANKLIN` | `constants/event_flags.asm:564` | as above | Franklin cleared |
|
||||
| `EVENT_BEAT_PSYCHIC_JARED` | `constants/event_flags.asm:573` | as above | Jared cleared |
|
||||
|
||||
**Items** - none in the gym.
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `SABRINA` / `SABRINA1` | `SABRINA` (`constants/trainer_constants.asm:309`, class 23) | 1 | `SabrinaGroup` `; SABRINA (1)` (`data/trainers/parties.asm:1400-1406`), `TRAINERTYPE_MOVES`: L46 `ESPEON` (Sand-Attack, Quick Attack, Swift, Psychic), L46 `MR__MIME` (Barrier, Reflect, Baton Pass, Psychic), L48 `ALAKAZAM` (Recover, Future Sight, Psychic, Reflect) | `SaffronGymSabrinaScript` | none; leader (`data/trainers/leaders.asm:29`) |
|
||||
| `MEDIUM` / `REBECCA` | `MEDIUM` | 6 (`constants/trainer_constants.asm:590`) | `MediumGroup` `; MEDIUM (6)`: L35 `DROWZEE`, L35 `HYPNO` | `TrainerMediumRebecca` | none |
|
||||
| `PSYCHIC_T` / `FRANKLIN` | `PSYCHIC_T` (class 34) | 2 (`constants/trainer_constants.asm:513`) | `PsychicGroup` `; PSYCHIC_T (2)` (`data/trainers/parties.asm:2516`): L37 `KADABRA` | `TrainerPsychicFranklin` | none |
|
||||
| `MEDIUM` / `DORIS` | `MEDIUM` | 7 (`constants/trainer_constants.asm:591`) | `MediumGroup` `; MEDIUM (7)`: L34 `SLOWPOKE`, L36 `SLOWBRO` | `TrainerMediumDoris` | none |
|
||||
| `PSYCHIC_T` / `JARED` | `PSYCHIC_T` | 11 (`constants/trainer_constants.asm:522`) | `PsychicGroup` `; PSYCHIC_T (11)` (`data/trainers/parties.asm:2569`): L32 `MR__MIME`, L32 `EXEGGCUTE`, **L35** `EXEGGCUTE` | `TrainerPsychicJared` | none |
|
||||
|
||||
Prize money: `ComputeTrainerReward` (`engine/battle/read_trainer_party.asm:300`) is `base reward x last mon's level`, and `engine/battle/core.asm:2344-2358` pays that quarter out **four times**, so the visible payout is `4 x base x level`. Base rewards: Sabrina 25 (`data/trainers/attributes.asm:211`), Medium 10 (`:343`), Psychic T 8 (`:313`). That reproduces the walkthrough's 4800G / 1400G / 1440G / 1184G / 1120G exactly. Sabrina also carries a `HYPER_POTION` as an AI item (`data/trainers/attributes.asm:210`).
|
||||
|
||||
**Wild encounters** - none (indoor gym).
|
||||
|
||||
### MAP_SAFFRON_POKECENTER_1F
|
||||
|
||||
- Script: `maps/SaffronPokecenter1F.asm`
|
||||
- Blocks: shared `maps/Pokecenter1F.blk` (`data/maps/blocks.asm:362,364`)
|
||||
- Header: `data/maps/maps.asm:494` -> `TILESET_POKECENTER`, `INDOOR`, `LANDMARK_SAFFRON_CITY`, `MUSIC_POKEMON_CENTER`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:458` `map_const SAFFRON_POKECENTER_1F, 5, 4`
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 7 | `SAFFRON_CITY` | 4 |
|
||||
| 2 | 4 | 7 | `SAFFRON_CITY` | 4 |
|
||||
| 3 | 0 | 7 | `POKECENTER_2F` | 1 |
|
||||
|
||||
**Object events** (nurse only listed; the rest are flavour)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `SAFFRONPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `SaffronPokecenter1FNurseScript` (`jumpstd PokecenterNurseScript`) | -1 |
|
||||
| `SAFFRONPOKECENTER1F_TEACHER` | `SPRITE_TEACHER` | 7 | 2 | `SPRITEMOVEDATA_WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `SaffronPokecenter1FTeacherScript` | -1 |
|
||||
| `SAFFRONPOKECENTER1F_FISHER` | `SPRITE_FISHER` | 8 | 6 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `SaffronPokecenter1FFisherScript` | -1 |
|
||||
| `SAFFRONPOKECENTER1F_YOUNGSTER` | `SPRITE_YOUNGSTER` | 1 | 4 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `SaffronPokecenter1FYoungsterScript` | -1 |
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Underground Path (Route 6 warp 1 at `(17, 3)`) | `maps/Route6.asm:43` - `ROUTE6_POKEFAN_M` object standing at `(17, 4)` with event flag `EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH`; visibility decided by `CheckObjectFlag`, `engine/overworld/map_objects_2.asm:32` | Nothing the player can do in this section | `maps/PowerPlant.asm:164` `setevent EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH` (flag set = object masked), which is part of returning the `MACHINE_PART` |
|
||||
| Magnet Train Saffron -> Goldenrod | `maps/SaffronMagnetTrainStation.asm:19` `checkevent EVENT_RESTORED_POWER_TO_KANTO`, then `checkitem PASS` at `:30` | `EVENT_RESTORED_POWER_TO_KANTO` **and** `PASS` in bag | `maps/PowerPlant.asm:165` sets the event; the `PASS` comes from elsewhere. Both false here, so the officer only prints "isn't operating now" |
|
||||
| Silph Co. upper floors | `maps/SilphCo1F.asm:69` - `SILPHCO1F_OFFICER` object at `(13, 1)` with event flag `-1` (never hides). No stairs warp exists on `SilphCo1F` at all | Permanently blocked in Gen 2 | Never; this is not a gate that opens |
|
||||
| Saffron Gym maze | The 30 pad warps at `maps/SaffronGym.asm:296-326`. Pads are teleport panels (`COLL_WARP_PANEL`-class tiles), so they fire on landing, not on a directional press | Navigation only - no badge, item or flag check anywhere in `SaffronGym.asm` | n/a. The single route to Sabrina is `(11,15) -> (19,17) -> ... -> (1,5) -> (11,9)`; see the pad table |
|
||||
| MARSHBADGE re-fight guard | `maps/SaffronGym.asm:17` `checkflag ENGINE_MARSHBADGE` / `iftrue .FightDone` | Badge not yet held | Beating Sabrina once; afterwards she only talks |
|
||||
| Gym trainers | Each `trainer` struct's `EVENT_BEAT_*` flag; the trainer only challenges while its flag is clear | Walking into their sight cone (sight 3 for Rebecca/Franklin, 2 for Doris/Jared) | Beating them, **or** beating Sabrina, which sets all four flags at `maps/SaffronGym.asm:27-30` |
|
||||
|
||||
No HM field move is required anywhere in this section - no `engine/overworld/cut.asm`, `surf.asm`, `strength.asm` or `whirlpool.asm` check is reachable from these maps. The Bicycle the walkthrough recommends is convenience only.
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
Coordinates are asm cell coordinates on the named map. "Warp N" means the `def_warp_events` index in that map's table.
|
||||
|
||||
1. `MAP_ROUTE_6` - walk north along the route to warp 2 at `(6, 1)`. Precondition: none. Postcondition: on `MAP_ROUTE_6_SAFFRON_GATE` warp 3 `(4, 7)`.
|
||||
- Optional: read the sign at `(19, 5)`. Do **not** try warp 1 at `(17, 3)`; `ROUTE6_POKEFAN_M` occupies `(17, 4)` while `EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH` is clear.
|
||||
2. `MAP_ROUTE_6_SAFFRON_GATE` - walk from `(4, 7)` to warp 1 at `(4, 0)`. Postcondition: on `MAP_SAFFRON_CITY` warp 12 `(16, 33)`; `MAPCALLBACK_NEWMAP` fires and `ENGINE_FLYPOINT_SAFFRON` becomes set.
|
||||
3. `MAP_SAFFRON_CITY` - walk to warp 5 at `(27, 29)`. Enter `MAP_MR_PSYCHICS_HOUSE`.
|
||||
4. `MAP_MR_PSYCHICS_HOUSE` - talk to `MRPSYCHICSHOUSE_FISHING_GURU` at `(5, 3)` (approach from `(4, 3)` facing right, he faces left). Precondition: `EVENT_GOT_TM29_PSYCHIC` clear and TM pocket has room. Postcondition: `TM_PSYCHIC_M` in bag, `EVENT_GOT_TM29_PSYCHIC` set. Exit warp 1 `(2, 7)`.
|
||||
5. `MAP_SAFFRON_CITY` - walk to warp 7 at `(18, 21)`. Enter `MAP_SILPH_CO_1F`.
|
||||
6. `MAP_SILPH_CO_1F` - talk to `SILPHCO1F_OFFICER` at `(13, 1)`. Precondition: `EVENT_GOT_UP_GRADE` clear, item pocket has room. Postcondition: `UP_GRADE` in bag, `EVENT_GOT_UP_GRADE` set. Exit warp 1 `(2, 7)`.
|
||||
7. (Optional, dialogue only) `MAP_SAFFRON_CITY` warp 6 at `(8, 3)` -> `MAP_SAFFRON_MAGNET_TRAIN_STATION`; talk to `SAFFRONMAGNETTRAINSTATION_OFFICER` at `(9, 9)`. Expect the "isn't operating now" arm because `EVENT_RESTORED_POWER_TO_KANTO` is clear. Exit warp 1 `(8, 17)`.
|
||||
8. (Optional) `MAP_SAFFRON_CITY` warp 3 at `(25, 11)` -> `MAP_SAFFRON_MART`; talk to `SAFFRONMART_CLERK` at `(1, 3)` for `MART_SAFFRON`. Exit warp 1 `(2, 7)`.
|
||||
9. `MAP_SAFFRON_CITY` - warp 1 at `(26, 3)` -> `MAP_FIGHTING_DOJO`. Walk onto / face `FIGHTINGDOJO_POKE_BALL` at `(3, 1)`. Precondition: `EVENT_PICKED_UP_FOCUS_BAND` clear. Postcondition: `FOCUS_BAND` in bag, `EVENT_PICKED_UP_FOCUS_BAND` set (ball object masked from then on). Exit warp 1 `(4, 11)`.
|
||||
10. (Optional heal) `MAP_SAFFRON_CITY` warp 4 at `(9, 29)` -> `MAP_SAFFRON_POKECENTER_1F`; talk to nurse at `(3, 1)`. Exit warp 1 `(3, 7)`.
|
||||
11. `MAP_SAFFRON_CITY` - warp 2 at `(34, 3)` -> `MAP_SAFFRON_GYM`, landing on warp 1 `(8, 17)`.
|
||||
12. `MAP_SAFFRON_GYM` - walk to the pad at `(11, 15)`. Warps to `(19, 17)`.
|
||||
13. `MAP_SAFFRON_GYM` BR room - battle `SAFFRONGYM_GRANNY1` (Medium Rebecca) at `(17, 16)`, sight 3. Precondition: `EVENT_BEAT_MEDIUM_REBECCA` clear. Postcondition: that flag set. (Skippable: her flag is force-set by the Sabrina script.)
|
||||
14. `MAP_SAFFRON_GYM` - step on pad `(15, 17)`. Warps to `(5, 15)`.
|
||||
15. `MAP_SAFFRON_GYM` BL room - battle `SAFFRONGYM_YOUNGSTER1` (Psychic Franklin) at `(3, 16)`, sight 3. Postcondition: `EVENT_BEAT_PSYCHIC_FRANKLIN` set.
|
||||
16. `MAP_SAFFRON_GYM` - step on pad `(1, 15)`. Warps to `(19, 5)`.
|
||||
17. `MAP_SAFFRON_GYM` TR room - battle `SAFFRONGYM_YOUNGSTER2` (Psychic Jared) at `(17, 4)`, sight 2. Postcondition: `EVENT_BEAT_PSYCHIC_JARED` set.
|
||||
18. `MAP_SAFFRON_GYM` - step on pad `(15, 5)`. Warps to `(1, 3)`.
|
||||
19. `MAP_SAFFRON_GYM` TL room - battle `SAFFRONGYM_GRANNY2` (Medium Doris) at `(3, 4)`, sight 2. Postcondition: `EVENT_BEAT_MEDIUM_DORIS` set.
|
||||
20. `MAP_SAFFRON_GYM` - step on pad `(1, 5)`. Warps to `(11, 9)`, Sabrina's chamber.
|
||||
21. `MAP_SAFFRON_GYM` - talk to `SAFFRONGYM_SABRINA` at `(9, 8)` (stand at `(9, 9)` facing up, or `(10, 8)` facing left). Precondition: `ENGINE_MARSHBADGE` clear. Battle `SABRINA`/`SABRINA1`. Postcondition: `EVENT_BEAT_SABRINA`, the four trainer flags, and `ENGINE_MARSHBADGE` all set; +4800G.
|
||||
22. `MAP_SAFFRON_GYM` exit - `(11, 9)` -> `(1, 5)`; walk right to `(5, 5)` -> `(1, 11)`; walk right to `(5, 11)` -> `(5, 17)`; walk up to `(5, 15)` -> `(15, 17)`; walk right to `(19, 17)` -> `(11, 15)`; walk down to warp 1/2 at `(8, 17)`/`(9, 17)` -> `MAP_SAFFRON_CITY` warp 2 `(34, 3)`.
|
||||
23. Mid-gym heal-and-return (if step 21 needs a fresh party): exit via step 22, heal at `MAP_SAFFRON_POKECENTER_1F`, re-enter, then `(11,15) -> (19,17)`, up to `(19,15) -> (19,9)`, left to `(15,9) -> (15,3)`, down to `(15,5) -> (1,3)`, down to `(1,5) -> (11,9)`. This is the walkthrough's "1st teleporter, then up, left, down, down" and it checks out against the pad table.
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Map load, block grid, `COLL_*` collision, connections | `src/world/gen2/Map.lua`, `src/world/gen2/Permissions.lua`, `src/world/gen2/World.lua` | Implemented (generic; Gold maps come from `src/import/RomExtractorGen2.lua`, not hand-ported) |
|
||||
| Warp panels / teleport pads (the whole gym maze) | `src/world/gen2/Permissions.lua:195` `COLL_WARP_PANEL = 0x7c` in `WARP_FACING_DOWN`, `Permissions.warpFacesDown`; warp taking in `src/world/gen2/World.lua:1566` `World:warpTo` | Implemented (panels warp on landing, which is what all 30 gym pads are) |
|
||||
| Map-to-self warps (every gym pad targets `SAFFRON_GYM`) | `src/world/gen2/World.lua` warp handling | Implemented, but untested for this map - no driver exercises a same-map warp chain |
|
||||
| `MAPCALLBACK_NEWMAP` -> `setflag ENGINE_FLYPOINT_SAFFRON` | `src/world/gen2/World.lua:5659` `self:runMapCallback("MAPCALLBACK_NEWMAP")`; fly-point row at `src/world/gen2/FieldMoves.lua:365` (`LANDMARK_SAFFRON_CITY` / `SPAWN_SAFFRON` / flag 59) | Implemented |
|
||||
| Script opcodes used by every script in this section (`checkevent`, `iftrue`, `iffalse`, `setevent`, `checkflag`, `setflag`, `verbosegiveitem`, `promptbutton`, `writetext`, `waitbutton`, `jumptext`, `jumptextfaceplayer`, `jumpstd`, `gettrainername`, `pokemart`, `yesorno`, `checkitem`, `applymovement`, `special`, `warpcheck`, `newloadmap`, `loadtrainer`, `startbattle`, `reloadmapafterbattle`, `winlosstext`, `endifjustbattled`, `playsound`, `waitsfx`) | `src/script/gen2/Opcodes.lua`, `src/script/gen2/Vm.lua` (`checkflag` :195, `verbosegiveitem` :490, `pokemart` :597, `jumpstd` :742, `loadtrainer` :806, `startbattle` :817, `winlosstext` :918) | Implemented |
|
||||
| Object visibility from event flags (the Route 6 blocker, the station's hidden Teacher/Lass, the picked-up Focus Band ball) | `src/world/gen2/Events.lua:1-2` documents and implements "flag SET -> object hidden", matching `CheckObjectFlag` | Implemented |
|
||||
| Overworld trainer sight + battle handoff (Rebecca, Franklin, Doris, Jared) | `src/world/gen2/Trainers.lua` (eyesight from `home/trainers.asm`, party build) | Implemented |
|
||||
| Gym leader battle, badge grant, prize money x4 | `src/battle/gen2/Battle.lua`; `src/battle/gen2/Prize.lua:82` `Prize.reward(baseMoney, level)` and `:181` `local quarter = ...` (the four-way payout) | Implemented |
|
||||
| Item ball pickup (`FOCUS_BAND` in the Fighting Dojo) | Extracted at `src/import/RomExtractorGen2.lua:2968-2969` into `obj.itemball`, but **nothing reads `obj.itemball` at runtime** (grep for `.itemball` in `src/` hits only the extractor). `src/script/gen2/CallAsm.lua:551` explicitly stubs `TryReceiveItem` | **Missing** - the ball object will exist and be masked correctly by its flag, but stepping on it grants nothing |
|
||||
| Magnet Train ride (`special MagnetTrain`) | `src/core/gen2/MagnetTrain.lua`, `src/ui/gen2/MagnetTrainRide.lua` | Implemented (not exercised in this section - the officer's gate is false here) |
|
||||
| Mart (`pokemart MARTTYPE_STANDARD, MART_SAFFRON`) | `src/ui/gen2/MartMenu.lua`, `Vm.lua:597` | Implemented |
|
||||
| Pokecenter heal (`jumpstd PokecenterNurseScript`) | std scripts extracted (`src/import/RomExtractorGen2.lua:2901`), heal hook `src/script/gen2/Specials.lua:451` | Implemented |
|
||||
| Bicycle | `src/world/gen2/Bike.lua` | Implemented |
|
||||
| Any Saffron-specific hand-ported content | none | **Missing by design** - the Gen 2 port is data-driven off the ROM; there are no `SAFFRON` symbols anywhere under `src/` except Gen 1 save-conversion tables (`src/save_convert/`), which are unrelated |
|
||||
| Driver coverage for this section | `tests/drivers/gold_*.lua` (25 drivers: boot, walk, warp, trainer, battle, map callbacks, ...) | **Missing** - no driver reaches Kanto or Saffron; `gold_warp_scene.lua` / `gold_walk_smoke.lua` are Johto-early smoke tests |
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
- **Psychic Jared's third Pokemon.** The walkthrough lists "Level 32 Exeggcute (672 EXP)" twice. `data/trainers/parties.asm:2569-2574` gives `PSYCHIC_T (11) JARED` as L32 `MR__MIME`, L32 `EXEGGCUTE`, **L35** `EXEGGCUTE`. The asm wins; the walkthrough's second Exeggcute level and its EXP figure are both wrong.
|
||||
- **EXP values.** None of the per-Pokemon EXP numbers in the walkthrough (765, 1237, 1149, 735, 672, 720, 1264, 1941, 1339, 1912) were checked against `data/pokemon/base_stats/` - they are outside what a bot needs and were not verified.
|
||||
- **"PokeMart ... upper-right corner of Saffron City".** The mart door is warp 3 at `(25, 11)` on a 40x36-cell map, i.e. north-of-centre and east of the middle, not the corner. The two gyms it says are "further north" are at `(26, 3)` and `(34, 3)`, which is correct.
|
||||
- **"Silph Co ... the multi-story building above the Pokémon Center".** Silph is warp 7 at `(18, 21)`; the Pokecenter is warp 4 at `(9, 29)`. Silph is north-**east** of it, and `SilphCo1F` has no stairs warp - only the two street doors. There is no upper floor in Gen 2.
|
||||
- **"You now have 10 badges!"** `ENGINE_MARSHBADGE` is bit 5 of `wKantoBadges` (`constants/engine_flags.asm:52`), so it is the 6th Kanto badge in flag order, not the 10th badge overall. The count of 10 only holds for a player who took Vermilion before Saffron, as this FAQ's chapter order does. Nothing in the asm enforces that order.
|
||||
- **The walkthrough's mid-maze "left/right (whichever way works) till you find that one trainer again (Franklin)" heal route** is deliberately vague in the source text and was not reduced to a single pad sequence. The precise post-heal route it gives afterwards ("1st teleporter, then up, left, down, down") *does* resolve cleanly and is recorded in bot checklist step 23.
|
||||
- **The "large man who blocks the entire entrance to the Underground Path"** is described as taunting the player about moving boulders. `Route6PokefanMText` (`maps/Route6.asm:15`) contains only the Power Plant line; no boulder or strength text exists on this map. Treat the boulder remark as FAQ colour.
|
||||
@@ -0,0 +1,896 @@
|
||||
# Section 21 - Power Plant and Cerulean City Gym
|
||||
|
||||
Source: `../section-21-power-plant-and-cerulean-city-gym.txt`
|
||||
Maps covered: `MAP_ROUTE_5`, `MAP_ROUTE_5_SAFFRON_GATE`, `MAP_ROUTE_5_CLEANSE_TAG_HOUSE`,
|
||||
`MAP_ROUTE_5_UNDERGROUND_PATH_ENTRANCE`, `MAP_CERULEAN_CITY`, `MAP_CERULEAN_GYM`,
|
||||
`MAP_ROUTE_9`, `MAP_ROUTE_10_NORTH`, `MAP_POWER_PLANT`, `MAP_ROUTE_24`,
|
||||
`MAP_ROUTE_25`, `MAP_BILLS_HOUSE`
|
||||
Badges / key milestones in this section: CASCADEBADGE (`ENGINE_CASCADEBADGE`) from Misty;
|
||||
the whole Machine Part chain (`EVENT_MET_MANAGER_AT_POWER_PLANT` ->
|
||||
`EVENT_MET_ROCKET_GRUNT_AT_CERULEAN_GYM` -> Route 24 grunt battle ->
|
||||
`EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM`); Misty's date on Route 25 clears
|
||||
`EVENT_TRAINERS_IN_CERULEAN_GYM` and is what actually populates the gym.
|
||||
|
||||
Coordinate note: `warp_event` / `bg_event` / `object_event` / `coord_event` x,y are in
|
||||
*map cells* (walk tiles), while `map_const NAME, W, H` in `constants/map_constants.asm`
|
||||
is in *blocks*. Cells = 2x blocks, so `map_const ROUTE_9, 30, 9` is a 60x18 cell map and
|
||||
`warp_event 48, 15` is inside it.
|
||||
|
||||
---
|
||||
|
||||
## 1. Route order
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `MAP_ROUTE_5_SAFFRON_GATE` | `maps/Route5SaffronGate.asm` | from `SAFFRON_CITY` warp 9 into gate warps 3/4 (`4,7` / `5,7`) | gate warps 1/2 (`4,0` / `5,0`) -> `ROUTE_5` warps 2/3 | "head north through the route-changing station" |
|
||||
| 2 | `MAP_ROUTE_5` | `maps/Route5.asm` | gate warps | north connection -> `CERULEAN_CITY` | walk north; optional detour to the old Day Care building |
|
||||
| 3 | `MAP_ROUTE_5_CLEANSE_TAG_HOUSE` | `maps/Route5CleanseTagHouse.asm` | `ROUTE_5` warp 4 at `10,11` | warps 1/2 at `2,7` / `3,7` -> `ROUTE_5` warp 4 | granny gives CLEANSE_TAG |
|
||||
| 4 | `MAP_CERULEAN_CITY` | `maps/CeruleanCity.asm` | south connection from `ROUTE_5` | warp 5 at `30,23` -> `CERULEAN_GYM` | first (empty) gym visit |
|
||||
| 5 | `MAP_CERULEAN_GYM` | `maps/CeruleanGym.asm` | `CERULEAN_CITY` warp 5 | warps 1/2 at `4,15` / `5,15` | gym is empty: `EVENT_TRAINERS_IN_CERULEAN_GYM` is still set from `InitializeEventsScript` |
|
||||
| 6 | `MAP_ROUTE_9` | `maps/Route9.asm` | east connection from `CERULEAN_CITY` | south connection -> `ROUTE_10_NORTH` | six trainers; needs CUT at the west entrance |
|
||||
| 7 | `MAP_ROUTE_10_NORTH` | `maps/Route10North.asm` | north connection from `ROUTE_9` | warp 2 at `3,9` -> `POWER_PLANT` | Pokemon Center at warp 1 (`11,1`); Surf the river to the plant |
|
||||
| 8 | `MAP_POWER_PLANT` | `maps/PowerPlant.asm` | `ROUTE_10_NORTH` warp 2 | warps 1/2 at `2,17` / `3,17` | talk to the MANAGER, then trip the `5,12` coord event on the way out |
|
||||
| 9 | `MAP_CERULEAN_CITY` | `maps/CeruleanCity.asm` | FLY (`SPAWN_CERULEAN`, `data/maps/spawn_points.asm:17`) | warp 5 -> `CERULEAN_GYM` | "Fly back to Cerulean City" |
|
||||
| 10 | `MAP_CERULEAN_GYM` | `maps/CeruleanGym.asm` | `CERULEAN_CITY` warp 5 | warps 1/2 | `SCENE_CERULEANGYM_GRUNT_RUNS_OUT` fires on entry |
|
||||
| 11 | `MAP_ROUTE_24` | `maps/Route24.asm` | north connection from `CERULEAN_CITY` | north connection -> `ROUTE_25` | Rocket grunt on the old Nugget Bridge |
|
||||
| 12 | `MAP_ROUTE_25` | `maps/Route25.asm` | south connection from `ROUTE_24` | warp 1 at `47,5` -> `BILLS_HOUSE` | six-pack trainers, Kevin's Nugget, Protein, Misty's date coord events |
|
||||
| 13 | `MAP_BILLS_HOUSE` | `maps/BillsHouse.asm` | `ROUTE_25` warp 1 | warps 1/2 at `2,7` / `3,7` | Bill's grandpa evolution-stone chain |
|
||||
| 14 | `MAP_CERULEAN_CITY` -> `MAP_CERULEAN_GYM` | `maps/CeruleanGym.asm` | FLY, then warp 5 | - | three Swimmers, hidden MACHINE_PART at `3,8`, Misty |
|
||||
|
||||
Spills into the next section: `maps/Route9.asm` warp 1 at `48,15` leads to `ROCK_TUNNEL_1F`,
|
||||
and the Machine Part is *returned* to `PowerPlantManager` (which is what unlocks the Magnet
|
||||
Train and the Underground Path). The walkthrough text stops at Misty; Rock Tunnel / Lavender
|
||||
and the return trip belong to the neighbouring sections.
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_ROUTE_5
|
||||
|
||||
- Script: `maps/Route5.asm`
|
||||
- Blocks: `maps/Route5.blk`
|
||||
- Header: `data/maps/maps.asm:489` -> `map Route5, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_5, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:453` -> `map_const ROUTE_5, 10, 9` (group `SAFFRON` = 25, id 1)
|
||||
- Connections (`data/maps/attributes.asm:356`): north `CERULEAN_CITY` (offset -5), south `SAFFRON_CITY` (offset -5)
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 17 | 15 | `ROUTE_5_UNDERGROUND_PATH_ENTRANCE` | 1 |
|
||||
| 2 | 8 | 17 | `ROUTE_5_SAFFRON_GATE` | 1 |
|
||||
| 3 | 9 | 17 | `ROUTE_5_SAFFRON_GATE` | 2 |
|
||||
| 4 | 10 | 11 | `ROUTE_5_CLEANSE_TAG_HOUSE` | 1 |
|
||||
|
||||
**Coord events** - none (`def_coord_events` is empty).
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 17 | 17 | `BGEVENT_READ` | `Route5UndergroundPathSign` |
|
||||
| 10 | 11 | `BGEVENT_READ` | `HouseForSaleSign` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE5_POKEFAN_M` | `SPRITE_POKEFAN_M` | 17 | 16 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Route5PokefanMScript` | `EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route5PokefanMScript` - one `jumptextfaceplayer Route5PokefanMText`: "The road is closed
|
||||
until the problem at the POWER PLANT is solved." He stands at `17,16`, directly below the
|
||||
Underground Path door at `17,15`, so the tile is physically blocked. His object row carries
|
||||
`EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH`; object events are hidden when their
|
||||
flag is *set*, and that flag is not in `InitializeEventsScript`, so at this point in the
|
||||
game it is clear and he is present. `PowerPlantManager` `setevent`s it after the part is
|
||||
returned (`maps/PowerPlant.asm:164`), removing him.
|
||||
- `Route5UndergroundPathSign` / `HouseForSaleSign` - plain `jumptext`. Note the "House for
|
||||
Sale" sign shares the exact tile `10,11` with warp 4 into the Cleanse Tag house.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH` | `constants/event_flags.asm:1299` | object row `maps/Route5.asm:56` and `maps/Route6.asm:43`; `setevent` at `maps/PowerPlant.asm:164` | clear = NPC present and Underground Path door unreachable; set = NPC gone |
|
||||
|
||||
**Items** - none on the route proper.
|
||||
|
||||
**Wild encounters** - `data/wild/kanto_grass.asm:425` `def_grass_wildmons ROUTE_5`,
|
||||
10/10/10 percent. Gold morn+day: L13 PIDGEY, L13 BELLSPROUT, L14 PIDGEY, L15 PIDGEY,
|
||||
L12 ABRA, L14 ABRA, L14 ABRA. Gold nite: L13 ODDISH, L14 ODDISH, L13 BELLSPROUT,
|
||||
L15 GLOOM, L12 ABRA, L14 ABRA, L14 ABRA. No water table. Fishing group `FISHGROUP_SHORE`
|
||||
(`data/wild/fish.asm` `.Shore_*`).
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_5_SAFFRON_GATE
|
||||
|
||||
- Script: `maps/Route5SaffronGate.asm`
|
||||
- Blocks: shared, `data/maps/blocks.asm`
|
||||
- Header: `data/maps/maps.asm:502` -> `map Route5SaffronGate, TILESET_GATE, GATE, LANDMARK_ROUTE_5, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:466` -> `map_const ROUTE_5_SAFFRON_GATE, 5, 4` (group 25, id 14)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 0 | `ROUTE_5` | 2 |
|
||||
| 2 | 5 | 0 | `ROUTE_5` | 3 |
|
||||
| 3 | 4 | 7 | `SAFFRON_CITY` | 9 |
|
||||
| 4 | 5 | 7 | `SAFFRON_CITY` | 9 |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE5SAFFRONGATE_OFFICER` | `SPRITE_OFFICER` | 0 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route5SaffronGateOfficerScript` | -1 |
|
||||
|
||||
Nothing here gates progress: the officer is a single `jumptextfaceplayer`, and there are no
|
||||
coord or bg events.
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_5_CLEANSE_TAG_HOUSE
|
||||
|
||||
- Script: `maps/Route5CleanseTagHouse.asm`
|
||||
- Blocks: shared, `data/maps/blocks.asm:196` (`Route5CleanseTagHouse_Blocks`)
|
||||
- Header: `data/maps/maps.asm:503` -> `map Route5CleanseTagHouse, TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_5, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:467` -> `map_const ROUTE_5_CLEANSE_TAG_HOUSE, 4, 4` (group 25, id 15)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 2 | 7 | `ROUTE_5` | 4 |
|
||||
| 2 | 3 | 7 | `ROUTE_5` | 4 |
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 0 | 1 | `BGEVENT_READ` | `HouseForSaleBookshelf` (`jumpstd DifficultBookshelfScript`) |
|
||||
| 1 | 1 | `BGEVENT_READ` | `HouseForSaleBookshelf` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE5CLEANSETAGHOUSE_GRANNY` | `SPRITE_GRANNY` | 2 | 5 | `SPRITEMOVEDATA_SPINCLOCKWISE` | `OBJECTTYPE_SCRIPT` | `Route5CleanseTagHouseGrannyScript` | -1 |
|
||||
| `ROUTE5CLEANSETAGHOUSE_TEACHER` | `SPRITE_TEACHER` | 5 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `Route5CleanseTagHouseTeacherScript` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route5CleanseTagHouseGrannyScript` (`pokegold.sym` `61:57de`) -
|
||||
`faceplayer` / `opentext` / `checkevent EVENT_GOT_CLEANSE_TAG` / `iftrue .GotCleanseTag`;
|
||||
otherwise `writetext Route5CleanseTagHouseGrannyText1`, `promptbutton`,
|
||||
`verbosegiveitem CLEANSE_TAG`, `iffalse .NoRoom`, `setevent EVENT_GOT_CLEANSE_TAG`,
|
||||
then the "you are protected now" line. Bag-full drops straight to `.NoRoom` without
|
||||
setting the flag, so a bot must have a free pocket slot or repeat the talk.
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `CLEANSE_TAG` | talk to `ROUTE5CLEANSETAGHOUSE_GRANNY` at `2,5` | `Route5CleanseTagHouseGrannyScript`, `verbosegiveitem CLEANSE_TAG` | `EVENT_GOT_CLEANSE_TAG` (`constants/event_flags.asm:218`) |
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_5_UNDERGROUND_PATH_ENTRANCE
|
||||
|
||||
- Script: `maps/Route5UndergroundPathEntrance.asm`
|
||||
- Header: `data/maps/maps.asm:501` -> `map Route5UndergroundPathEntrance, TILESET_GATE, GATE, LANDMARK_ROUTE_5, MUSIC_ROUTE_3, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:465` -> `map_const ROUTE_5_UNDERGROUND_PATH_ENTRANCE, 4, 4` (group 25, id 13)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 7 | `ROUTE_5` | 1 |
|
||||
| 2 | 4 | 7 | `ROUTE_5` | 1 |
|
||||
| 3 | 4 | 3 | `UNDERGROUND_PATH` | 1 |
|
||||
|
||||
Only reachable once the Route 5 Pokefan is gone. Listed here because the walkthrough calls
|
||||
him out; nothing in this section actually enters it.
|
||||
|
||||
---
|
||||
|
||||
### MAP_CERULEAN_CITY
|
||||
|
||||
- Script: `maps/CeruleanCity.asm`
|
||||
- Blocks: `maps/CeruleanCity.blk`
|
||||
- Header: `data/maps/maps.asm:221` -> `map CeruleanCity, TILESET_KANTO, TOWN, LANDMARK_CERULEAN_CITY, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_AUTO, FISHGROUP_LAKE`
|
||||
- Dimensions: `constants/map_constants.asm:203` -> `map_const CERULEAN_CITY, 20, 18` (group `CERULEAN` = 7, id 17)
|
||||
- Connections (`data/maps/attributes.asm:360`): north `ROUTE_24` (+6), south `ROUTE_5` (+5), west `ROUTE_4` (+5), east `ROUTE_9` (+9)
|
||||
- Fly / spawn: `data/maps/spawn_points.asm:17` `spawn CERULEAN_CITY, 19, 22`;
|
||||
`data/maps/flypoints.asm:21` `db LANDMARK_CERULEAN_CITY, SPAWN_CERULEAN`
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 7 | 15 | `CERULEAN_GYM_BADGE_SPEECH_HOUSE` | 1 |
|
||||
| 2 | 28 | 17 | `CERULEAN_POLICE_STATION` | 1 |
|
||||
| 3 | 13 | 19 | `CERULEAN_TRADE_SPEECH_HOUSE` | 1 |
|
||||
| 4 | 19 | 21 | `CERULEAN_POKECENTER_1F` | 1 |
|
||||
| 5 | 30 | 23 | `CERULEAN_GYM` | 1 |
|
||||
| 6 | 25 | 29 | `CERULEAN_MART` | 2 |
|
||||
|
||||
**Coord events** - none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 23 | 23 | `BGEVENT_READ` | `CeruleanCitySign` |
|
||||
| 27 | 25 | `BGEVENT_READ` | `CeruleanGymSign` |
|
||||
| 11 | 29 | `BGEVENT_READ` | `CeruleanBikeShopSign` ("The BIKE SHOP has moved to GOLDENROD") |
|
||||
| 25 | 17 | `BGEVENT_READ` | `CeruleanPoliceSign` |
|
||||
| 23 | 7 | `BGEVENT_READ` | `CeruleanCapeSign` |
|
||||
| 14 | 29 | `BGEVENT_READ` | `CeruleanLockedDoor` |
|
||||
| 20 | 21 | `BGEVENT_READ` | `CeruleanCityPokecenterSign` |
|
||||
| 26 | 29 | `BGEVENT_READ` | `CeruleanCityMartSign` |
|
||||
| 2 | 12 | `BGEVENT_ITEM` | `CeruleanCityHiddenBerserkGene` -> `hiddenitem BERSERK_GENE, EVENT_FOUND_BERSERK_GENE_IN_CERULEAN_CITY` (`pokegold.sym` `4f:60ad`) |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `CERULEANCITY_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 15 | 23 | `SPRITEMOVEDATA_WANDER` (2,2) | `OBJECTTYPE_SCRIPT` | `CeruleanCityCooltrainerMScript` | -1 |
|
||||
| `CERULEANCITY_SUPER_NERD` | `SPRITE_SUPER_NERD` | 23 | 15 | `SPRITEMOVEDATA_WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `CeruleanCitySuperNerdScript` | -1 |
|
||||
| `CERULEANCITY_SLOWPOKE` | `SPRITE_SLOWPOKE` | 20 | 24 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `CeruleanCitySlowbro` | -1 |
|
||||
| `CERULEANCITY_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 21 | 24 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `CeruleanCityCooltrainerFScript` | -1 |
|
||||
| `CERULEANCITY_FISHER` | `SPRITE_FISHER` | 30 | 26 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (1,0) | `OBJECTTYPE_SCRIPT` | `CeruleanCityFisherScript` | -1 |
|
||||
| `CERULEANCITY_YOUNGSTER` | `SPRITE_YOUNGSTER` | 6 | 12 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` (1,0) | `OBJECTTYPE_SCRIPT` | `CeruleanCityYoungsterScript` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `CeruleanCityFlypointCallback` - `MAPCALLBACK_NEWMAP`, `setflag ENGINE_FLYPOINT_CERULEAN`
|
||||
then `endcallback`. Simply entering the city registers the Fly destination the walkthrough
|
||||
relies on twice.
|
||||
- `CeruleanCityCooltrainerMScript` - `checkevent EVENT_RETURNED_MACHINE_PART`; before the
|
||||
part is returned he is the hint "KANTO's POWER PLANT is toward the end of ROUTE 9".
|
||||
- `CeruleanCityFisherScript` - `checkevent EVENT_RETURNED_MACHINE_PART` (iftrue -> generic
|
||||
line), else `checkevent EVENT_MET_ROCKET_GRUNT_AT_CERULEAN_GYM` -> "I saw this shady guy
|
||||
go off toward CERULEAN's CAPE", i.e. the pointer to Route 24/25.
|
||||
- `CeruleanCityYoungsterScript` - after his first line, if
|
||||
`EVENT_FOUND_BERSERK_GENE_IN_CERULEAN_CITY` is clear he plays the ITEMFINDER ping loop
|
||||
(`SFX_SECOND_PART_OF_ITEMFINDER` / `SFX_TRANSACTION` x4) and `showemote EMOTE_SHOCK`.
|
||||
That is the in-game hint for the hidden BERSERK_GENE at `2,12`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_FLYPOINT_CERULEAN` | `constants/engine_flags.asm:70` | `CeruleanCityFlypointCallback` | Fly target unlocked on first entry |
|
||||
| `EVENT_MET_ROCKET_GRUNT_AT_CERULEAN_GYM` | `constants/event_flags.asm:202` | set by `CeruleanGymGruntRunsOutScript`; read by `CeruleanCityFisherScript` | grunt cutscene has played |
|
||||
| `EVENT_RETURNED_MACHINE_PART` | `constants/event_flags.asm:200` | set by `PowerPlantManager`; read here and in ~10 other maps | post-section state |
|
||||
| `EVENT_FOUND_BERSERK_GENE_IN_CERULEAN_CITY` | `constants/event_flags.asm:250` | `CeruleanCityHiddenBerserkGene`, `CeruleanCityYoungsterScript` | hidden item consumed |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `BERSERK_GENE` | face the tile and press A (hidden) | `bg_event 2, 12, BGEVENT_ITEM, CeruleanCityHiddenBerserkGene` | `EVENT_FOUND_BERSERK_GENE_IN_CERULEAN_CITY` |
|
||||
|
||||
**Wild encounters** - no grass table. Water: `data/wild/kanto_water.asm:131`
|
||||
`def_water_wildmons CERULEAN_CITY`, 4 percent: L10 GOLDEEN, L5 GOLDEEN, L10 SEAKING.
|
||||
Fishing group `FISHGROUP_LAKE`.
|
||||
|
||||
---
|
||||
|
||||
### MAP_CERULEAN_GYM
|
||||
|
||||
- Script: `maps/CeruleanGym.asm`
|
||||
- Blocks: `maps/CeruleanGym.blk`
|
||||
- Header: `data/maps/maps.asm:210` -> `map CeruleanGym, TILESET_PORT, INDOOR, LANDMARK_CERULEAN_CITY, MUSIC_GYM, TRUE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
(the `data/maps/maps.asm:209` comment notes the known bug that you can fish in the gym pool)
|
||||
- Dimensions: `constants/map_constants.asm:192` -> `map_const CERULEAN_GYM, 5, 8` (group 7, id 6)
|
||||
- Scenes (`def_scene_scripts`, index order from `macros/scripts/maps.asm` `def_scene_scripts`/`scene_const`):
|
||||
`SCENE_CERULEANGYM_NOOP` = 0, `SCENE_CERULEANGYM_GRUNT_RUNS_OUT` = 1.
|
||||
Scene var: `data/maps/scenes.asm:13` `scene_var CERULEAN_GYM, wCeruleanGymSceneID`.
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 15 | `CERULEAN_CITY` | 5 |
|
||||
| 2 | 5 | 15 | `CERULEAN_CITY` | 5 |
|
||||
|
||||
**Coord events** - none. The grunt cutscene runs from the *scene script*
|
||||
(`CeruleanGymGruntRunsOutScene` -> `sdefer CeruleanGymGruntRunsOutScript`), which fires on
|
||||
map load while `wCeruleanGymSceneID == SCENE_CERULEANGYM_GRUNT_RUNS_OUT`, not from a tile.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 3 | 8 | `BGEVENT_ITEM` | `CeruleanGymHiddenMachinePart` -> `hiddenitem MACHINE_PART, EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM` (`pokegold.sym` `54:440e`) |
|
||||
| 2 | 13 | `BGEVENT_READ` | `CeruleanGymStatue1` |
|
||||
| 6 | 13 | `BGEVENT_READ` | `CeruleanGymStatue2` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `CERULEANGYM_ROCKET` | `SPRITE_ROCKET` | 4 | 10 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` (inert; driven by the scene script) | `EVENT_CERULEAN_GYM_ROCKET` |
|
||||
| `CERULEANGYM_MISTY` | `SPRITE_MISTY` | 5 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CeruleanGymMistyScript` | `EVENT_TRAINERS_IN_CERULEAN_GYM` |
|
||||
| `CERULEANGYM_SWIMMER_GIRL1` | `SPRITE_SWIMMER_GIRL` | 4 | 6 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerSwimmerfDiana` | `EVENT_TRAINERS_IN_CERULEAN_GYM` |
|
||||
| `CERULEANGYM_SWIMMER_GIRL2` | `SPRITE_SWIMMER_GIRL` | 1 | 9 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerSwimmerfBriana` | `EVENT_TRAINERS_IN_CERULEAN_GYM` |
|
||||
| `CERULEANGYM_SWIMMER_GUY` | `SPRITE_SWIMMER_GUY` | 8 | 9 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerSwimmermParker` | `EVENT_TRAINERS_IN_CERULEAN_GYM` |
|
||||
| `CERULEANGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 13 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CeruleanGymGuideScript` | `EVENT_TRAINERS_IN_CERULEAN_GYM` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `CeruleanGymGruntRunsOutScript` (`pokegold.sym` `54:4336`) - the grunt runs down four
|
||||
`big_step DOWN`, `playsound SFX_TACKLE`, jumps into the player
|
||||
(`CeruleanGymGruntRunsIntoYouMovement`: `fix_facing` / `set_sliding` / `jump_step UP`),
|
||||
`playmusic MUSIC_ROCKET_ENCOUNTER`, three text boxes with a `showemote EMOTE_SHOCK`
|
||||
between them, then `CeruleanGymGruntRunsOutMovement` (`big_step RIGHT`, `big_step DOWN`),
|
||||
`playsound SFX_EXIT_BUILDING`, `disappear CERULEANGYM_ROCKET`. State changes, in order:
|
||||
`setevent EVENT_MET_ROCKET_GRUNT_AT_CERULEAN_GYM`,
|
||||
`clearevent EVENT_ROUTE_24_ROCKET` (spawns the grunt on Route 24),
|
||||
`clearevent EVENT_ROUTE_25_MISTY_BOYFRIEND` (spawns Misty + her date on Route 25),
|
||||
`setscene SCENE_CERULEANGYM_NOOP`,
|
||||
`setmapscene ROUTE_25, SCENE_ROUTE25_MISTYS_DATE`,
|
||||
`setmapscene POWER_PLANT, SCENE_POWERPLANT_NOOP`, then
|
||||
`special RestartMapMusic` and `turnobject PLAYER, DOWN`.
|
||||
- `CeruleanGymMistyScript` (`pokegold.sym` `54:438a`) - `checkflag ENGINE_CASCADEBADGE`,
|
||||
`iftrue .FightDone`; else `MistyIntroText`, `winlosstext MistyWinLossText, 0`,
|
||||
`loadtrainer MISTY, MISTY1`, `startbattle`, `reloadmapafterbattle`, then
|
||||
`setevent EVENT_BEAT_MISTY` **and** `setevent EVENT_BEAT_SWIMMERF_DIANA`,
|
||||
`EVENT_BEAT_SWIMMERF_BRIANA`, `EVENT_BEAT_SWIMMERM_PARKER` (beating Misty retroactively
|
||||
marks the three gym trainers beaten), `ReceivedCascadeBadgeText`, `playsound SFX_GET_BADGE`,
|
||||
`setflag ENGINE_CASCADEBADGE`.
|
||||
- `CeruleanGymStatue1` / `CeruleanGymStatue2` - `checkevent EVENT_TRAINERS_IN_CERULEAN_GYM`;
|
||||
if *false* (trainers present) they fall through to the shared `CeruleanGymStatue`
|
||||
(`jumpstd GymStatue1Script`, or `GymStatue2Script` with `gettrainername STRING_BUFFER_4, MISTY, MISTY1`
|
||||
once `ENGINE_CASCADEBADGE` is set). If true (gym empty) they print the "Sorry, I'll be out
|
||||
for a while - MISTY" / "Since MISTY's out, we'll be away too" notes.
|
||||
- `CeruleanGymGuideScript` - `checkevent EVENT_BEAT_MISTY` for the post-win line.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_TRAINERS_IN_CERULEAN_GYM` | `constants/event_flags.asm:1297` | set in `engine/events/std_scripts.asm:550` (`InitializeEventsScript`); cleared by `Route25MistyDate1Script` / `Route25MistyDate2Script` | set = Misty, three Swimmers and the guide are all hidden. This is why the first gym visit is empty. |
|
||||
| `EVENT_CERULEAN_GYM_ROCKET` | `constants/event_flags.asm:1295` | set at `std_scripts.asm:547`; cleared by `PowerPlantManager` (`maps/PowerPlant.asm:146`) | clear = grunt object exists so the scene can run |
|
||||
| `EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM` | `constants/event_flags.asm:251` | set at `std_scripts.asm:546`; cleared by `PowerPlantManager` (`maps/PowerPlant.asm:147`) | set = the hidden item at `3,8` yields nothing. Talking to the MANAGER is what arms it. |
|
||||
| `EVENT_MET_ROCKET_GRUNT_AT_CERULEAN_GYM` | `constants/event_flags.asm:202` | `CeruleanGymGruntRunsOutScript` | cutscene done |
|
||||
| `EVENT_BEAT_MISTY` | `constants/event_flags.asm:716` | `CeruleanGymMistyScript` | gym cleared |
|
||||
| `ENGINE_CASCADEBADGE` | `constants/engine_flags.asm:48` | `CeruleanGymMistyScript` `checkflag` / `setflag` | the badge itself |
|
||||
| `SCENE_CERULEANGYM_GRUNT_RUNS_OUT` (= 1) | `maps/CeruleanGym.asm:12` via `scene_const` | `setmapscene` at `maps/PowerPlant.asm:148`; cleared to `SCENE_CERULEANGYM_NOOP` at `maps/CeruleanGym.asm:49` | entering the gym with this scene id plays the grunt scene |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `MACHINE_PART` | stand at the pool and press A into `3,8` | `bg_event 3, 8, BGEVENT_ITEM, CeruleanGymHiddenMachinePart` | `EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM` (must be cleared first by the MANAGER) |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `SWIMMERF, BRIANA` | `SWIMMERF` | 19 (`parties.asm:1885`) | L35 SEAKING, L35 SEAKING | `TrainerSwimmerfBriana` | none |
|
||||
| `SWIMMERM, PARKER` | `SWIMMERM` | 21 (`parties.asm:1775`) | L32 HORSEA, L32 HORSEA, L35 SEADRA | `TrainerSwimmermParker` | none |
|
||||
| `SWIMMERF, DIANA` | `SWIMMERF` | 18 (`parties.asm:1880`) | L37 GOLDUCK | `TrainerSwimmerfDiana` | none |
|
||||
| `MISTY, MISTY1` | `MISTY` | 1 (`parties.asm:281`, `TRAINERTYPE_MOVES`) | L42 GOLDUCK (SURF, DISABLE, PSYCH_UP, PSYCHIC_M); L42 QUAGSIRE (SURF, AMNESIA, EARTHQUAKE, RAIN_DANCE); L44 LAPRAS (SURF, PERISH_SONG, BLIZZARD, RAIN_DANCE); L47 STARMIE (SURF, CONFUSE_RAY, RECOVER, ICE_BEAM) | `CeruleanGymMistyScript` | leader, no phone |
|
||||
|
||||
Base rewards (`data/trainers/attributes.asm`): SWIMMERF 5, SWIMMERM 2, MISTY 25. Payout is
|
||||
`ComputeTrainerReward` (`engine/battle/read_trainer_party.asm:300`) = base x last mon level,
|
||||
added four times by the `ld c, 4` loop in `engine/battle/core.asm:2340`, i.e. base x level x 4.
|
||||
That reproduces every number in the walkthrough (Misty 25 x 47 x 4 = 4700).
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_9
|
||||
|
||||
- Script: `maps/Route9.asm`
|
||||
- Blocks: `maps/Route9.blk`
|
||||
- Header: `data/maps/maps.asm:217` -> `map Route9, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_9, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_LAKE`
|
||||
- Dimensions: `constants/map_constants.asm:199` -> `map_const ROUTE_9, 30, 9` (group 7, id 13)
|
||||
- Connections (`data/maps/attributes.asm:366`): south `ROUTE_10_NORTH` (+20), west `CERULEAN_CITY` (-9)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 48 | 15 | `ROCK_TUNNEL_1F` | 1 |
|
||||
|
||||
**Coord events** - none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 15 | 7 | `BGEVENT_READ` | `Route9Sign` |
|
||||
| 10 | 5 | `BGEVENT_ITEM` | `Route9HiddenEther` -> `hiddenitem ETHER, EVENT_ROUTE_9_HIDDEN_ETHER` (`pokegold.sym` `50:407d`) |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE9_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 23 | 11 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerCamperDean` | -1 |
|
||||
| `ROUTE9_LASS1` | `SPRITE_LASS` | 35 | 8 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 2) | `TrainerPicnickerHeidi` | -1 |
|
||||
| `ROUTE9_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 11 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 2) | `TrainerCamperSid` | -1 |
|
||||
| `ROUTE9_LASS2` | `SPRITE_LASS` | 9 | 10 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerPicnickerEdna` | -1 |
|
||||
| `ROUTE9_POKEFAN_M1` | `SPRITE_POKEFAN_M` | 32 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 4) | `TrainerHikerTim` | -1 |
|
||||
| `ROUTE9_POKEFAN_M2` | `SPRITE_POKEFAN_M` | 33 | 15 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 4) | `TrainerHikerSidney` | -1 |
|
||||
|
||||
**Scripts of interest** - all six are the plain `trainer CLASS, ID, EVENT_BEAT_*, seen, beaten, 0, .Script`
|
||||
form with an `endifjustbattled` + one after-battle text. No flags beyond `EVENT_BEAT_*`.
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `ETHER` | hidden, face `10,5` | `bg_event 10, 5, BGEVENT_ITEM, Route9HiddenEther` | `EVENT_ROUTE_9_HIDDEN_ETHER` (`constants/event_flags.asm:242`) |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `PICNICKER, EDNA` | `PICNICKER` | 14 (`parties.asm:2659`) | L30 NIDORINA, L34 RAICHU | `TrainerPicnickerEdna` | none |
|
||||
| `CAMPER, SID` | `CAMPER` | 8 (`parties.asm:2744`) | L32 DUGTRIO, L29 PRIMEAPE, L29 POLIWRATH | `TrainerCamperSid` | none |
|
||||
| `CAMPER, DEAN` | `CAMPER` | 7 (`parties.asm:2738`) | L33 GOLDUCK, L31 SANDSLASH | `TrainerCamperDean` | none |
|
||||
| `HIKER, SIDNEY` | `HIKER` | 15 (`parties.asm:2210`) | L34 DUGTRIO, L32 ONIX | `TrainerHikerSidney` | none |
|
||||
| `HIKER, TIM` | `HIKER` | 13 (`parties.asm:2197`) | L31 GRAVELER x3 | `TrainerHikerTim` | none |
|
||||
| `PICNICKER, HEIDI` | `PICNICKER` | 13 (`parties.asm:2653`) | L32 SKIPLOOM, L32 SKIPLOOM | `TrainerPicnickerHeidi` | none |
|
||||
|
||||
Base rewards: PICNICKER 5, CAMPER 5, HIKER 8 (`data/trainers/attributes.asm`).
|
||||
|
||||
**Wild encounters** - `data/wild/kanto_grass.asm:645` `def_grass_wildmons ROUTE_9`, 10/10/10 percent,
|
||||
version-split with `IF DEF(_GOLD)`. Gold morn/day: L13 MANKEY, L15 RATTATA, L13 SPEAROW,
|
||||
L15 RATICATE, L15 FEAROW, L15 PRIMEAPE, L15 PRIMEAPE. Gold nite: L13 MANKEY, L15 RATTATA,
|
||||
L15 RATICATE, L13 RATTATA, L15 RATICATE, L15 PRIMEAPE, L15 PRIMEAPE. (Silver swaps MANKEY /
|
||||
PRIMEAPE for RATTATA / RATICATE, which is exactly the "Gold only" note in the walkthrough.)
|
||||
Water: `data/wild/kanto_water.asm:19`, 4 percent: L15 GOLDEEN, L10 GOLDEEN, L15 SEAKING.
|
||||
Fishing group `FISHGROUP_LAKE`. No `treemon_maps.asm` row, so no headbutt table.
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_10_NORTH
|
||||
|
||||
- Script: `maps/Route10North.asm`
|
||||
- Blocks: `maps/Route10North.blk`
|
||||
- Header: `data/maps/maps.asm:218` -> `map Route10North, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_10, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_LAKE`
|
||||
- Dimensions: `constants/map_constants.asm:200` -> `map_const ROUTE_10_NORTH, 10, 9` (group 7, id 14)
|
||||
- Connections (`data/maps/attributes.asm:389`): north `ROUTE_9` (-20), south `ROUTE_10_SOUTH` (0)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 11 | 1 | `ROUTE_10_POKECENTER_1F` | 1 |
|
||||
| 2 | 3 | 9 | `POWER_PLANT` | 1 |
|
||||
|
||||
**Coord events** - none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 5 | 11 | `BGEVENT_READ` | `PowerPlantSign` ("KANTO POWER PLANT") |
|
||||
| 12 | 1 | `BGEVENT_READ` | `Route10PokecenterSign` (`jumpstd PokecenterSignScript`) |
|
||||
|
||||
**Object events** - `def_object_events` is empty. No trainers on this map.
|
||||
|
||||
**Wild encounters** - `data/wild/kanto_grass.asm:700` `def_grass_wildmons ROUTE_10_NORTH`,
|
||||
10/10/10 percent, no version split.
|
||||
morn: L16 SPEAROW, L17 VOLTORB, L16 RATICATE, L18 FEAROW, L16 QUAGSIRE, L15 ELECTABUZZ, L15 ELECTABUZZ.
|
||||
day: L16 SPEAROW, L17 VOLTORB, L17 RATICATE, L18 FEAROW, L15 ELECTABUZZ, L17 ELECTABUZZ, L17 ELECTABUZZ.
|
||||
nite: L16 QUAGSIRE, L17 VOLTORB, L16 RATICATE, L17 QUAGSIRE, L18 RATICATE, L15 ELECTABUZZ, L15 ELECTABUZZ.
|
||||
Water: `data/wild/kanto_water.asm:26`, 4 percent: L15 GOLDEEN, L10 GOLDEEN, L15 SEAKING.
|
||||
Fishing group `FISHGROUP_LAKE`.
|
||||
|
||||
---
|
||||
|
||||
### MAP_POWER_PLANT
|
||||
|
||||
- Script: `maps/PowerPlant.asm`
|
||||
- Blocks: `maps/PowerPlant.blk`
|
||||
- Header: `data/maps/maps.asm:214` -> `map PowerPlant, TILESET_FACILITY, INDOOR, LANDMARK_POWER_PLANT, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:196` -> `map_const POWER_PLANT, 10, 9` (group 7, id 10)
|
||||
- Scenes: `SCENE_POWERPLANT_NOOP` = 0, `SCENE_POWERPLANT_GUARD_GETS_PHONE_CALL` = 1
|
||||
(`maps/PowerPlant.asm:11-12`); scene var `data/maps/scenes.asm:12`
|
||||
`scene_var POWER_PLANT, wPowerPlantSceneID`
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 2 | 17 | `ROUTE_10_NORTH` | 2 |
|
||||
| 2 | 3 | 17 | `ROUTE_10_NORTH` | 2 |
|
||||
|
||||
**Coord events**
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_POWERPLANT_GUARD_GETS_PHONE_CALL` (1) | 5 | 12 | `PowerPlantGuardPhoneScript` | the "shady character in CERULEAN" cutscene; ends with `setscene SCENE_POWERPLANT_NOOP` |
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 0 | 1 | `BGEVENT_READ` | `PowerPlantBookshelf` (`jumpstd DifficultBookshelfScript`) |
|
||||
| 1 | 1 | `BGEVENT_READ` | `PowerPlantBookshelf` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `POWERPLANT_OFFICER1` | `SPRITE_OFFICER` | 4 | 14 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PowerPlantOfficerScript` | -1 |
|
||||
| `POWERPLANT_GYM_GUIDE1` | `SPRITE_GYM_GUIDE` | 2 | 9 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `PowerPlantGymGuide1Script` | -1 |
|
||||
| `POWERPLANT_GYM_GUIDE2` | `SPRITE_GYM_GUIDE` | 6 | 11 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `PowerPlantGymGuide2Script` | -1 |
|
||||
| `POWERPLANT_OFFICER2` | `SPRITE_OFFICER` | 9 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `PowerPlantOfficer2Script` | -1 |
|
||||
| `POWERPLANT_GYM_GUIDE3` | `SPRITE_GYM_GUIDE` | 7 | 2 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (1,0) | `OBJECTTYPE_SCRIPT` | `PowerPlantGymGuide4Script` | -1 |
|
||||
| `POWERPLANT_MANAGER` | `SPRITE_FISHER` | 14 | 10 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `PowerPlantManager` | -1 |
|
||||
|
||||
(The object const list names the fifth guide `POWERPLANT_GYM_GUIDE3` while its script is
|
||||
called `PowerPlantGymGuide4Script`; that mismatch is in the source, not a transcription slip.)
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `PowerPlantManager` (`pokegold.sym` `54:4dbd`) - the spine of the whole section:
|
||||
1. `checkevent EVENT_RETURNED_MACHINE_PART` -> `.ReturnedMachinePart`
|
||||
2. `checkitem MACHINE_PART` -> `.FoundMachinePart`
|
||||
3. `checkevent EVENT_MET_MANAGER_AT_POWER_PLANT` -> `.MetManager`
|
||||
4. first talk: `PowerPlantManagerWhoWouldRuinMyGeneratorText`, then
|
||||
`setevent EVENT_MET_MANAGER_AT_POWER_PLANT`,
|
||||
`clearevent EVENT_CERULEAN_GYM_ROCKET`,
|
||||
`clearevent EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM`,
|
||||
`setmapscene CERULEAN_GYM, SCENE_CERULEANGYM_GRUNT_RUNS_OUT`,
|
||||
`setscene SCENE_POWERPLANT_GUARD_GETS_PHONE_CALL`.
|
||||
5. `.FoundMachinePart` (out of scope for this walkthrough section, listed because it is the
|
||||
payoff): `takeitem MACHINE_PART`, `setevent EVENT_RETURNED_MACHINE_PART`,
|
||||
`clearevent EVENT_SAFFRON_TRAIN_STATION_POPULATION`,
|
||||
`setevent EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH`,
|
||||
`setevent EVENT_ROUTE_24_ROCKET`, `setevent EVENT_RESTORED_POWER_TO_KANTO`,
|
||||
`clearevent EVENT_GOLDENROD_TRAIN_STATION_GENTLEMAN`, then
|
||||
`verbosegiveitem TM_ZAP_CANNON` guarded by `EVENT_GOT_TM07_ZAP_CANNON`.
|
||||
- `PowerPlantGuardPhoneScript` - `playsound SFX_CALL`, `showemote EMOTE_SHOCK` on
|
||||
`POWERPLANT_OFFICER1`, `applymovement POWERPLANT_OFFICER1 PowerPlantOfficer1ApproachGymGuide2Movement`
|
||||
(RIGHT, RIGHT, UP, UP), two text boxes, then `PowerPlantOfficer1ReturnToPostMovement`
|
||||
(DOWN, DOWN, LEFT, LEFT, turn_head DOWN) and `setscene SCENE_POWERPLANT_NOOP`.
|
||||
- The four flavour NPCs all branch only on `EVENT_RETURNED_MACHINE_PART`
|
||||
(`PowerPlantOfficerScript` additionally on `EVENT_MET_MANAGER_AT_POWER_PLANT`).
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_MET_MANAGER_AT_POWER_PLANT` | `constants/event_flags.asm:201` | `PowerPlantManager` | gate for everything downstream |
|
||||
| `EVENT_CERULEAN_GYM_ROCKET` | `constants/event_flags.asm:1295` | cleared by `PowerPlantManager` | grunt object now exists in the gym |
|
||||
| `EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM` | `constants/event_flags.asm:251` | cleared by `PowerPlantManager` | the hidden item is now live |
|
||||
| `SCENE_POWERPLANT_GUARD_GETS_PHONE_CALL` (1) | `maps/PowerPlant.asm:12` | `setscene`, then the `5,12` coord event | forces the exit cutscene once |
|
||||
| `EVENT_RETURNED_MACHINE_PART` / `EVENT_RESTORED_POWER_TO_KANTO` | `constants/event_flags.asm:200` / `:204` | `.FoundMachinePart` | later-section state (Magnet Train, Underground Path, roaming beasts checks in `maps/Route26.asm` etc.) |
|
||||
| `EVENT_GOT_TM07_ZAP_CANNON` | `constants/event_flags.asm:222` | `.ReturnedMachinePart` | TM07 reward |
|
||||
|
||||
**Items** - `TM_ZAP_CANNON` from the MANAGER, but only on the *return* trip, which the
|
||||
walkthrough section does not cover.
|
||||
|
||||
**Trainers / wild encounters** - none.
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_24
|
||||
|
||||
- Script: `maps/Route24.asm`
|
||||
- Blocks: `maps/Route24.blk`
|
||||
- Header: `data/maps/maps.asm:219` -> `map Route24, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_24, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_LAKE`
|
||||
- Dimensions: `constants/map_constants.asm:201` -> `map_const ROUTE_24, 10, 9` (group 7, id 15)
|
||||
- Connections (`data/maps/attributes.asm:370`): north `ROUTE_25` (0), south `CERULEAN_CITY` (-6)
|
||||
|
||||
**Warps** - `def_warp_events` is empty.
|
||||
**Coord events** - empty.
|
||||
**BG events** - empty.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE24_ROCKET` | `SPRITE_ROCKET` | 8 | 7 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | `Route24RocketScript` | `EVENT_ROUTE_24_ROCKET` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route24RocketScript` (`pokegold.sym` `50:4407`) - `faceplayer`,
|
||||
`playmusic MUSIC_ROCKET_ENCOUNTER`, `Route24RocketSeenText`,
|
||||
`winlosstext Route24RocketBeatenText, -1`, `loadtrainer GRUNTM, GRUNTM_31`, `startbattle`,
|
||||
`dontrestartmapmusic`, `reloadmapafterbattle`, then `Route24RocketAfterBattleText`
|
||||
(the "MACHINE PART ... hide it I did in GYM of the CERULEAN ... Inside water put it I did"
|
||||
hint), `special FadeOutMusic`, `Route24RocketDisappearsText`, `special FadeOutToBlack`,
|
||||
`special ReloadSpritesNoPalettes`, `disappear ROUTE24_ROCKET`, `pause 25`,
|
||||
`special FadeInFromBlack`, `playmapmusic`.
|
||||
Note: he is *talked to*, not a sight-line trainer, and there is no `EVENT_BEAT_*` -
|
||||
`disappear` sets `EVENT_ROUTE_24_ROCKET` and that is the only record of the win.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_ROUTE_24_ROCKET` | `constants/event_flags.asm:1294` | set at `engine/events/std_scripts.asm:548`; cleared by `CeruleanGymGruntRunsOutScript`; set again by `disappear` and by `PowerPlantManager:165` | clear = grunt is standing on the bridge at `8,7` |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `GRUNTM, GRUNTM_31` | `GRUNTM` | 31 (`data/trainers/parties.asm:1340`) | L30 GOLBAT | `Route24RocketScript` | none |
|
||||
|
||||
**Wild encounters** - `data/wild/kanto_grass.asm:1061` `def_grass_wildmons ROUTE_24`, 10/10/10 percent.
|
||||
morn: L8 BELLSPROUT, L10 BELLSPROUT, L9 ABRA, L12 WEEPINBELL, L8 VENONAT, L14 WEEPINBELL, L14 WEEPINBELL.
|
||||
day: L8 BELLSPROUT, L10 SUNKERN, L9 ABRA, L12 WEEPINBELL, L10 BELLSPROUT, L14 WEEPINBELL, L14 WEEPINBELL.
|
||||
nite: L8 VENONAT, L10 ODDISH, L9 ABRA, L13 WEEPINBELL, L10 BELLSPROUT, L10 VENOMOTH, L10 VENOMOTH.
|
||||
Water: `data/wild/kanto_water.asm:75`, 4 percent: L10 GOLDEEN, L5 GOLDEEN, L10 SEAKING.
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_25
|
||||
|
||||
- Script: `maps/Route25.asm`
|
||||
- Blocks: `maps/Route25.blk`
|
||||
- Header: `data/maps/maps.asm:220` -> `map Route25, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_25, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_LAKE`
|
||||
- Dimensions: `constants/map_constants.asm:202` -> `map_const ROUTE_25, 30, 9` (group 7, id 16)
|
||||
- Connections (`data/maps/attributes.asm:374`): south `ROUTE_24` (0). No other connection - the
|
||||
route is a dead end at Bill's house.
|
||||
- Scenes: `SCENE_ROUTE25_NOOP` = 0, `SCENE_ROUTE25_MISTYS_DATE` = 1 (`maps/Route25.asm:16-17`);
|
||||
scene var `data/maps/scenes.asm:14`
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 47 | 5 | `BILLS_HOUSE` | 1 |
|
||||
|
||||
**Coord events**
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_ROUTE25_MISTYS_DATE` (1) | 42 | 6 | `Route25MistyDate1Script` (`pokegold.sym` `50:4781`) | the date cutscene, upper approach |
|
||||
| `SCENE_ROUTE25_MISTYS_DATE` (1) | 42 | 7 | `Route25MistyDate2Script` | same cutscene, lower approach |
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 45 | 5 | `BGEVENT_READ` | `BillsHouseSign` ("SEA COTTAGE / BILL'S HOUSE") |
|
||||
| 4 | 5 | `BGEVENT_ITEM` | `Route25HiddenPotion` -> `hiddenitem POTION, EVENT_ROUTE_25_HIDDEN_POTION` (`pokegold.sym` `50:48c6`) |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE25_MISTY` | `SPRITE_MISTY` | 46 | 9 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` (inert) | `EVENT_ROUTE_25_MISTY_BOYFRIEND` |
|
||||
| `ROUTE25_COOLTRAINER_M1` | `SPRITE_COOLTRAINER_M` | 46 | 10 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` (inert) | `EVENT_ROUTE_25_MISTY_BOYFRIEND` |
|
||||
| `ROUTE25_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 12 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerSchoolboyDudley` | -1 |
|
||||
| `ROUTE25_LASS1` | `SPRITE_LASS` | 16 | 11 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerLassEllen` | -1 |
|
||||
| `ROUTE25_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 21 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerSchoolboyJoe` | -1 |
|
||||
| `ROUTE25_LASS2` | `SPRITE_LASS` | 22 | 6 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerLassLaura` | -1 |
|
||||
| `ROUTE25_YOUNGSTER3` | `SPRITE_YOUNGSTER` | 25 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 2) | `TrainerCamperLloyd` | -1 |
|
||||
| `ROUTE25_LASS3` | `SPRITE_LASS` | 28 | 11 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerLassShannon` | -1 |
|
||||
| `ROUTE25_SUPER_NERD` | `SPRITE_SUPER_NERD` | 31 | 7 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER` (sight 1) | `TrainerSupernerdPat` | -1 |
|
||||
| `ROUTE25_COOLTRAINER_M2` | `SPRITE_COOLTRAINER_M` | 37 | 8 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `TrainerCooltrainermKevin` | -1 |
|
||||
| `ROUTE25_POKE_BALL` | `SPRITE_POKE_BALL` | 32 | 4 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route25Protein` (`itemball PROTEIN`, `pokegold.sym` `50:48c4`) | `EVENT_ROUTE_25_PROTEIN` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route25MistyDate1Script` / `Route25MistyDate2Script` - identical except for the approach
|
||||
movement. `showemote EMOTE_HEART` on Misty, `pause 30`, `showemote EMOTE_SHOCK` on the
|
||||
boyfriend, `applymovement ROUTE25_COOLTRAINER_M1` (one or two `big_step DOWN`),
|
||||
`disappear ROUTE25_COOLTRAINER_M1`, `playmusic MUSIC_BEAUTY_ENCOUNTER`, Misty walks over
|
||||
(`Route25MistyApproachesPlayerMovement1` = UP,UP,UP,LEFT,LEFT,LEFT; variant 2 drops one UP),
|
||||
`writetext Route25MistyDateText` (the "pest" speech that ends with "Come to CERULEAN GYM"),
|
||||
Misty leaves via `Route25MistyLeavesMovement` (LEFT x5), `disappear ROUTE25_MISTY`,
|
||||
**`clearevent EVENT_TRAINERS_IN_CERULEAN_GYM`**, `setscene SCENE_ROUTE25_NOOP`,
|
||||
`special RestartMapMusic`.
|
||||
- `TrainerCooltrainermKevin` - not a `trainer` macro; a hand-written script.
|
||||
`checkevent EVENT_BEAT_COOLTRAINERM_KEVIN` -> after-battle text;
|
||||
`checkevent EVENT_CLEARED_NUGGET_BRIDGE` -> skip the prize;
|
||||
otherwise `CooltrainermKevinNuggetText`, `verbosegiveitem NUGGET`, `iffalse .NoRoomForNugget`,
|
||||
`setevent EVENT_CLEARED_NUGGET_BRIDGE`, then `winlosstext CooltrainermKevinBeatenText, 0`,
|
||||
`loadtrainer COOLTRAINERM, KEVIN`, `startbattle`, `reloadmapafterbattle`,
|
||||
`setevent EVENT_BEAT_COOLTRAINERM_KEVIN`.
|
||||
A full bag at the Nugget step aborts before the battle.
|
||||
- The six "six-pack" trainers are plain `trainer` macros with `EVENT_BEAT_*` flags only.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_ROUTE_25_MISTY_BOYFRIEND` | `constants/event_flags.asm:1296` | set at `engine/events/std_scripts.asm:549`; cleared by `CeruleanGymGruntRunsOutScript` | clear = the pair are standing at `46,9` / `46,10` |
|
||||
| `SCENE_ROUTE25_MISTYS_DATE` (1) | `maps/Route25.asm:17` | `setmapscene` from `maps/CeruleanGym.asm:50` | arms the two coord events |
|
||||
| `EVENT_TRAINERS_IN_CERULEAN_GYM` | `constants/event_flags.asm:1297` | cleared here | the gym becomes populated |
|
||||
| `EVENT_CLEARED_NUGGET_BRIDGE` | `constants/event_flags.asm:215` | `TrainerCooltrainermKevin` | Nugget already handed over |
|
||||
| `EVENT_BEAT_COOLTRAINERM_KEVIN` | `constants/event_flags.asm:873` | `TrainerCooltrainermKevin` | Kevin battle done |
|
||||
| `EVENT_ROUTE_25_HIDDEN_POTION` | `constants/event_flags.asm:248` | `Route25HiddenPotion` | hidden Potion taken |
|
||||
| `EVENT_ROUTE_25_PROTEIN` | `constants/event_flags.asm:1326` | item ball object row | Protein ball taken |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `POTION` | hidden, face `4,5` | `bg_event 4, 5, BGEVENT_ITEM, Route25HiddenPotion` | `EVENT_ROUTE_25_HIDDEN_POTION` |
|
||||
| `NUGGET` | talk to Kevin at `37,8` | `TrainerCooltrainermKevin`, `verbosegiveitem NUGGET` | `EVENT_CLEARED_NUGGET_BRIDGE` |
|
||||
| `PROTEIN` | item ball at `32,4` | `object_event ... OBJECTTYPE_ITEMBALL, 0, Route25Protein` | `EVENT_ROUTE_25_PROTEIN` |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `SCHOOLBOY, DUDLEY` | `SCHOOLBOY` | 7 (`parties.asm:448`) | L35 ODDISH | `TrainerSchoolboyDudley` | none |
|
||||
| `LASS, ELLEN` | `LASS` | 11 (`parties.asm:703`) | L30 WIGGLYTUFF, L34 GRANBULL | `TrainerLassEllen` | none |
|
||||
| `SCHOOLBOY, JOE` | `SCHOOLBOY` | 8 (`parties.asm:453`) | L33 TANGELA, L33 VAPOREON | `TrainerSchoolboyJoe` | none |
|
||||
| `LASS, LAURA` | `LASS` | 7 (`parties.asm:676`) | L28 GLOOM, L31 PIDGEOTTO, L31 BELLOSSOM | `TrainerLassLaura` | none |
|
||||
| `CAMPER, LLOYD` | `CAMPER` | 6 (`parties.asm:2733`) | L34 NIDOKING | `TrainerCamperLloyd` | none |
|
||||
| `LASS, SHANNON` | `LASS` | 8 (`parties.asm:683`) | L29 PARAS, L29 PARAS, L32 PARASECT | `TrainerLassShannon` | none |
|
||||
| `SUPER_NERD, PAT` | `SUPER_NERD` | 8 (`parties.asm:2009`) | L36 PORYGON | `TrainerSupernerdPat` | none |
|
||||
| `COOLTRAINERM, KEVIN` | `COOLTRAINERM` | 17 (`parties.asm:847`) | L38 RHYHORN, L35 CHARMELEON, L35 WARTORTLE | `TrainerCooltrainermKevin` | none |
|
||||
|
||||
Base rewards: SCHOOLBOY 8, LASS 6, CAMPER 5, SUPER_NERD 8, COOLTRAINERM 12.
|
||||
|
||||
**Wild encounters** - `data/wild/kanto_grass.asm:1089` `def_grass_wildmons ROUTE_25`, 10/10/10 percent.
|
||||
morn: L8 PIDGEY, L10 BELLSPROUT, L8 VENONAT, L9 ABRA, L10 PIDGEOTTO, L14 WEEPINBELL, L14 WEEPINBELL.
|
||||
day: L8 PIDGEY, L10 BELLSPROUT, L10 PIDGEY, L9 ABRA, L12 PIDGEOTTO, L14 WEEPINBELL, L14 WEEPINBELL.
|
||||
nite: L8 VENONAT, L10 ODDISH, L10 VENOMOTH, L9 ABRA, L14 WEEPINBELL, L10 BELLSPROUT, L10 BELLSPROUT.
|
||||
Water: `data/wild/kanto_water.asm:82`, 4 percent: L10 GOLDEEN, L5 GOLDEEN, L10 SEAKING.
|
||||
|
||||
---
|
||||
|
||||
### MAP_BILLS_HOUSE
|
||||
|
||||
- Script: `maps/BillsHouse.asm`
|
||||
- Blocks: shared, `data/maps/blocks.asm:163` (`BillsHouse_Blocks`)
|
||||
- Header: `data/maps/maps.asm:215` -> `map BillsHouse, TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_25, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:197` -> `map_const BILLS_HOUSE, 4, 4` (group 7, id 11)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 2 | 7 | `ROUTE_25` | 1 |
|
||||
| 2 | 3 | 7 | `ROUTE_25` | 1 |
|
||||
|
||||
**Coord events** - none. **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `BILLSHOUSE_GRAMPS` | `SPRITE_GRAMPS` | 2 | 3 | `SPRITEMOVEDATA_STANDING_UP` (radius 0,2) | `OBJECTTYPE_SCRIPT` | `BillsGrandpa` (`pokegold.sym` `54:547c`) | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `BillsGrandpa` - a five-step chain, each step gated on the previous. Order in the asm:
|
||||
1. LICKITUNG -> `EVENT_SHOWED_LICKITUNG_TO_BILLS_GRANDPA` -> `verbosegiveitem EVERSTONE`
|
||||
(`EVENT_GOT_EVERSTONE_FROM_BILLS_GRANDPA`)
|
||||
2. ODDISH -> `EVENT_SHOWED_ODDISH_TO_BILLS_GRANDPA` -> `verbosegiveitem LEAF_STONE`
|
||||
(`EVENT_GOT_LEAF_STONE_FROM_BILLS_GRANDPA`)
|
||||
3. STARYU -> `EVENT_SHOWED_STARYU_TO_BILLS_GRANDPA` -> `verbosegiveitem WATER_STONE`
|
||||
(`EVENT_GOT_WATER_STONE_FROM_BILLS_GRANDPA`)
|
||||
4. GROWLITHE (Gold) / VULPIX (Silver, selected by `checkver` / `iftrue .AskVulpix`) ->
|
||||
`EVENT_SHOWED_GROWLITHE_VULPIX_TO_BILLS_GRANDPA` -> `verbosegiveitem FIRE_STONE`
|
||||
(`EVENT_GOT_FIRE_STONE_FROM_BILLS_GRANDPA`)
|
||||
5. PICHU -> `EVENT_SHOWED_PICHU_TO_BILLS_GRANDPA` -> `verbosegiveitem THUNDERSTONE`
|
||||
(`EVENT_GOT_THUNDERSTONE_FROM_BILLS_GRANDPA`, which also ends the chain)
|
||||
Mechanics: `writetext ... AskToSeeMonText`, `yesorno`, `scall .ExcitedToSee`,
|
||||
`special BillsGrandfather` (party pick; `iffalse` = cancelled), `ifnotequal <SPECIES>, .WrongPokemon`.
|
||||
`EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1` (`constants/event_flags.asm:5`) is set after each
|
||||
successful hand-over, so he only accepts **one mon per visit** - a bot must leave and
|
||||
re-enter the house between stones.
|
||||
- First talk sets `EVENT_MET_BILLS_GRANDPA` (`constants/event_flags.asm:457`).
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `EVERSTONE` | show LICKITUNG | `BillsGrandpa` | `EVENT_GOT_EVERSTONE_FROM_BILLS_GRANDPA` |
|
||||
| `LEAF_STONE` | show ODDISH | `BillsGrandpa` | `EVENT_GOT_LEAF_STONE_FROM_BILLS_GRANDPA` |
|
||||
| `WATER_STONE` | show STARYU | `BillsGrandpa` | `EVENT_GOT_WATER_STONE_FROM_BILLS_GRANDPA` |
|
||||
| `FIRE_STONE` | show GROWLITHE (Gold) / VULPIX (Silver) | `BillsGrandpa` | `EVENT_GOT_FIRE_STONE_FROM_BILLS_GRANDPA` |
|
||||
| `THUNDERSTONE` | show PICHU | `BillsGrandpa` | `EVENT_GOT_THUNDERSTONE_FROM_BILLS_GRANDPA` (`:467`) |
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Cut tree at the Route 9 west entrance (and the tree above Super Nerd Pat on Route 25) | `engine/events/overworld.asm:117` `CutFunction` -> `.CheckAble` (`ld de, ENGINE_HIVEBADGE` / `CheckBadge`, then `CheckMapForSomethingToCut` -> `engine/overworld/tile_events.asm:76` `CheckCutCollision`). The trees themselves are block data in `maps/Route9.blk` / `maps/Route25.blk`, not object events. | HIVEBADGE + a party mon with CUT | already held (Johto badge 2) |
|
||||
| Surfing the Route 9 / Route 10 river to reach the Power Plant door at `3,9` | `engine/events/overworld.asm:322` `SurfFunction` `.TrySurf` (`ld de, ENGINE_FOGBADGE` / `CheckBadge`, then `GetTilePermission` == `WATER_TILE`); the overworld A-press path is `engine/events/overworld.asm:469` `TrySurfOW` with the same `ENGINE_FOGBADGE` `CheckEngineFlag` | FOGBADGE + SURF | already held (Johto badge 6) |
|
||||
| "Fly back to Cerulean City" | `engine/events/overworld.asm:529` `FlyFunction` (`ld de, ENGINE_STORMBADGE` at `:545`), destination row `data/maps/flypoints.asm:21` | STORMBADGE + FLY + `ENGINE_FLYPOINT_CERULEAN` set by `CeruleanCityFlypointCallback` | flypoint flag set on first entry to Cerulean |
|
||||
| Cerulean Gym is empty on the first visit | `maps/CeruleanGym.asm:379-383` object rows all carry `EVENT_TRAINERS_IN_CERULEAN_GYM`, set at `engine/events/std_scripts.asm:550` | - | `clearevent` in `Route25MistyDate1Script` / `Route25MistyDate2Script` (`maps/Route25.asm:48` / `:74`) |
|
||||
| Rocket grunt does not exist in the gym | `maps/CeruleanGym.asm:378` object row `EVENT_CERULEAN_GYM_ROCKET`, set at `std_scripts.asm:547`; the scene is `SCENE_CERULEANGYM_NOOP` by default | - | first talk to `PowerPlantManager` (`clearevent` + `setmapscene CERULEAN_GYM, SCENE_CERULEANGYM_GRUNT_RUNS_OUT`, `maps/PowerPlant.asm:146-148`) |
|
||||
| Rocket grunt does not exist on Route 24 | `maps/Route24.asm:129` object row `EVENT_ROUTE_24_ROCKET`, set at `std_scripts.asm:548` | - | `clearevent` in `CeruleanGymGruntRunsOutScript` (`maps/CeruleanGym.asm:47`) |
|
||||
| Misty + date do not exist on Route 25, and the two coord events are inert | `maps/Route25.asm:445-446` object rows `EVENT_ROUTE_25_MISTY_BOYFRIEND`; coord rows require `SCENE_ROUTE25_MISTYS_DATE` | - | `clearevent` + `setmapscene ROUTE_25, SCENE_ROUTE25_MISTYS_DATE` in `CeruleanGymGruntRunsOutScript` (`maps/CeruleanGym.asm:48`, `:50`) |
|
||||
| Hidden MACHINE_PART at Cerulean Gym `3,8` yields nothing | `maps/CeruleanGym.asm:373` -> `CeruleanGymHiddenMachinePart`, flag set at `std_scripts.asm:546` | - | `clearevent EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM` in `PowerPlantManager` (`maps/PowerPlant.asm:147`) |
|
||||
| Route 5 Underground Path door at `17,15` physically blocked | `maps/Route5.asm:56` NPC standing on `17,16` | - | `setevent EVENT_ROUTE_5_6_POKEFAN_M_BLOCKS_UNDERGROUND_PATH` in `PowerPlantManager` `.FoundMachinePart` (`maps/PowerPlant.asm:164`) - i.e. **after** this section |
|
||||
| Bill's grandpa gives one stone per visit | `maps/BillsHouse.asm` `.JustShowedSomething` on `EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1` | - | leave and re-enter the map |
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
Ordered, machine-actionable. `(g,m)` is `(map group, map id)` from `constants/map_constants.asm`.
|
||||
|
||||
1. `ROUTE_5_SAFFRON_GATE (25,14)`: walk onto warp 1/2 (`4,0` / `5,0`) -> `ROUTE_5`. Pre: none. Post: none.
|
||||
2. `ROUTE_5 (25,1)`: optional - walk to warp 4 (`10,11`), enter `ROUTE_5_CLEANSE_TAG_HOUSE (25,15)`, talk to `ROUTE5CLEANSETAGHOUSE_GRANNY` at `2,5`. Pre: `EVENT_GOT_CLEANSE_TAG` clear + free bag slot. Post: `EVENT_GOT_CLEANSE_TAG` set, CLEANSE_TAG in bag. Exit via warp 1/2 (`2,7`/`3,7`).
|
||||
3. `ROUTE_5`: walk north across the connection into `CERULEAN_CITY (7,17)`. Post: `ENGINE_FLYPOINT_CERULEAN` set by `MAPCALLBACK_NEWMAP`.
|
||||
4. `CERULEAN_CITY`: optional - face `2,12` and press A for BERSERK_GENE. Pre: `EVENT_FOUND_BERSERK_GENE_IN_CERULEAN_CITY` clear.
|
||||
5. `CERULEAN_CITY`: heal at warp 4 (`19,21`, `CERULEAN_POKECENTER_1F`). Optional but the walkthrough does it twice.
|
||||
6. `CERULEAN_CITY`: warp 5 (`30,23`) -> `CERULEAN_GYM (7,6)`. Expect an empty room (`EVENT_TRAINERS_IN_CERULEAN_GYM` set). Read `2,13` / `6,13` for the two notes. Exit via `4,15`/`5,15`.
|
||||
7. `CERULEAN_CITY`: exit east into `ROUTE_9 (7,13)`. Pre: HIVEBADGE + CUT for the entrance tree.
|
||||
8. `ROUTE_9`: battle in walkthrough order - `TrainerPicnickerEdna` (`9,10`), `TrainerCamperSid` (`11,2`), `TrainerCamperDean` (`23,11`), `TrainerHikerSidney` (`33,15`), `TrainerHikerTim` (`32,3`), `TrainerPicnickerHeidi` (`35,8`). Post: `EVENT_BEAT_*` per trainer. Optional hidden ETHER at `10,5`.
|
||||
9. `ROUTE_9`: cross the south connection into `ROUTE_10_NORTH (7,14)`. Optional heal at warp 1 (`11,1`).
|
||||
10. `ROUTE_10_NORTH`: Surf (pre: FOGBADGE + SURF) down the river and step onto warp 2 (`3,9`) -> `POWER_PLANT (7,10)`.
|
||||
11. `POWER_PLANT`: walk to `POWERPLANT_MANAGER` at `14,10` and talk. Pre: `EVENT_MET_MANAGER_AT_POWER_PLANT` clear. Post: `EVENT_MET_MANAGER_AT_POWER_PLANT` set, `EVENT_CERULEAN_GYM_ROCKET` cleared, `EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM` cleared, `CERULEAN_GYM` scene = 1, `POWER_PLANT` scene = 1.
|
||||
12. `POWER_PLANT`: walk over `5,12` on the way to the exit. Post: `PowerPlantGuardPhoneScript` runs, `POWER_PLANT` scene back to 0. Then exit via warp 1/2 (`2,17`/`3,17`).
|
||||
13. FLY to `CERULEAN_CITY` (`SPAWN_CERULEAN` = `19,22`). Pre: STORMBADGE + FLY + `ENGINE_FLYPOINT_CERULEAN`.
|
||||
14. `CERULEAN_CITY`: warp 5 -> `CERULEAN_GYM`. On load the scene script fires; hold through the movement/text (four A presses: intro, big-mistake, bye, plus the emote pauses). Post: `EVENT_MET_ROCKET_GRUNT_AT_CERULEAN_GYM` set, `EVENT_ROUTE_24_ROCKET` cleared, `EVENT_ROUTE_25_MISTY_BOYFRIEND` cleared, `ROUTE_25` scene = 1, `CERULEAN_GYM` scene = 0.
|
||||
15. `CERULEAN_CITY`: exit north into `ROUTE_24 (7,15)`. Talk to `ROUTE24_ROCKET` at `8,7`. Battle `GRUNTM, GRUNTM_31` (L30 GOLBAT). Post: `disappear` sets `EVENT_ROUTE_24_ROCKET`.
|
||||
16. `ROUTE_24`: north connection into `ROUTE_25 (7,16)`.
|
||||
17. `ROUTE_25`: optional hidden POTION - face `4,5`, press A. Pre: `EVENT_ROUTE_25_HIDDEN_POTION` clear.
|
||||
18. `ROUTE_25`: battle `TrainerSchoolboyDudley` (`12,8`), `TrainerLassEllen` (`16,11`), `TrainerSchoolboyJoe` (`21,8`), `TrainerLassLaura` (`22,6`), `TrainerCamperLloyd` (`25,4`), `TrainerLassShannon` (`28,11`), `TrainerSupernerdPat` (`31,7`).
|
||||
19. `ROUTE_25`: talk to `ROUTE25_COOLTRAINER_M2` at `37,8`. Pre: free bag slot. Post: NUGGET, `EVENT_CLEARED_NUGGET_BRIDGE`, then battle `COOLTRAINERM, KEVIN` and `EVENT_BEAT_COOLTRAINERM_KEVIN`.
|
||||
20. `ROUTE_25`: CUT the tree, walk onto the item ball at `32,4` for PROTEIN. Post: `EVENT_ROUTE_25_PROTEIN`.
|
||||
21. `ROUTE_25`: walk east onto `42,6` (or `42,7`). Pre: `ROUTE_25` scene = `SCENE_ROUTE25_MISTYS_DATE`. Post: date cutscene, `EVENT_TRAINERS_IN_CERULEAN_GYM` cleared, `ROUTE_25` scene = 0.
|
||||
22. `ROUTE_25`: warp 1 (`47,5`) -> `BILLS_HOUSE (7,11)`. Talk to `BILLSHOUSE_GRAMPS` at `2,3`. One stone per map load; loop enter/talk/exit until the wanted stones are collected. Post: `EVENT_MET_BILLS_GRANDPA`, then the `EVENT_GOT_*_FROM_BILLS_GRANDPA` chain.
|
||||
23. FLY to `CERULEAN_CITY`, heal at warp 4, then warp 5 -> `CERULEAN_GYM`.
|
||||
24. `CERULEAN_GYM`: battle `TrainerSwimmerfBriana` (`1,9`), then face `3,8` and press A for MACHINE_PART. Pre: `EVENT_FOUND_MACHINE_PART_IN_CERULEAN_GYM` clear + free bag slot. Post: flag set, MACHINE_PART in bag.
|
||||
25. `CERULEAN_GYM`: battle `TrainerSwimmermParker` (`8,9`), `TrainerSwimmerfDiana` (`4,6`).
|
||||
26. `CERULEAN_GYM`: talk to `CERULEANGYM_MISTY` at `5,3`. Pre: `ENGINE_CASCADEBADGE` clear. Post: `EVENT_BEAT_MISTY`, `EVENT_BEAT_SWIMMERF_DIANA`, `EVENT_BEAT_SWIMMERF_BRIANA`, `EVENT_BEAT_SWIMMERM_PARKER`, `ENGINE_CASCADEBADGE` set, +4700 money.
|
||||
|
||||
Follow-up (next section): carry MACHINE_PART back to `POWERPLANT_MANAGER` at `14,10` to set
|
||||
`EVENT_RETURNED_MACHINE_PART` / `EVENT_RESTORED_POWER_TO_KANTO` and collect TM07 ZAP CANNON.
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Kanto map headers, warps, coord/bg/object event tables extracted from ROM | `src/import/RomExtractorGen2.lua` (`OBJECTTYPE_ITEMBALL, OBJECTTYPE_TRAINER = 1, 2` at :71; branch at :2960-2968) | implemented - generic, all map groups, nothing Kanto-specific needed |
|
||||
| Coord-event trip-wires (Power Plant `5,12`, Route 25 `42,6`/`42,7`) with the scene id filter | `src/world/gen2/World.lua:5006` `World:tryCoordScript` | implemented |
|
||||
| Scene scripts on map load + `sdefer` (`CeruleanGymGruntRunsOutScene`) | `src/world/gen2/World.lua` `World:trySceneScript`; `src/script/gen2/Vm.lua:92` | partial - `sdefer` is run immediately rather than queued until the map settles (comment at `Vm.lua:93` says so). Cosmetic ordering risk on the gym grunt scene. |
|
||||
| `setscene` / `setmapscene` / `checkmapscene` | `src/script/gen2/Vm.lua:274`, `:279`, `:284` | implemented, including the `0xff` "no scene var" sentinel |
|
||||
| `checkevent` / `setevent` / `clearevent` and `checkflag` / `setflag` (`ENGINE_CASCADEBADGE`, `ENGINE_FLYPOINT_CERULEAN`) | `src/script/gen2/Vm.lua:198-229`, `engineFlags` store at `:1661` | implemented - generic id-keyed stores, no per-flag work needed |
|
||||
| Hidden items (`BGEVENT_ITEM`): MACHINE_PART, ETHER, POTION, BERSERK_GENE | `src/world/gen2/HiddenItems.lua` | implemented (the header notes this was previously unreachable because `World:bgEventAt` only answered `BGEVENT_READ`) |
|
||||
| Item ball (`Route25Protein`) | `src/import/RomExtractorGen2.lua:2968` (`OBJECTTYPE_ITEMBALL`) | implemented |
|
||||
| Trainer sight lines / `OBJECTTYPE_TRAINER` sight radius | `src/world/gen2/Trainers.lua:98` `Trainers.sees` | implemented |
|
||||
| `loadtrainer` / `startbattle` / `winlosstext` / `reloadmapafterbattle` / `dontrestartmapmusic` / `endifjustbattled` | `src/script/gen2/Vm.lua` | implemented |
|
||||
| Prize money = base x level x 4 (Misty 4700G etc.) | `src/battle/gen2/Prize.lua:12` (documents the `ld c, 4` loop), `Prize.reward` at `:82` | implemented, incl. Amulet Coin and Mom's savings split |
|
||||
| CUT gate on HIVEBADGE + cut-tree collision | `src/world/gen2/FieldMoves.lua:440` `badgeGate` / `:452`; `src/world/gen2/Permissions.lua:130` `CUT_TREE`, `:152` `CheckCutCollision` set | implemented |
|
||||
| SURF gate on FOGBADGE | `src/world/gen2/FieldMoves.lua:482` | implemented |
|
||||
| FLY gate on STORMBADGE + flypoint table | `src/world/gen2/FieldMoves.lua:342` `FieldMoves.FLYPOINTS`, `:501`; `src/world/gen2/World.lua:4284` (`_FlyMap`) | implemented |
|
||||
| `special BillsGrandfather` (party pick for the stone chain) | `src/script/gen2/Specials.lua:1267` | implemented |
|
||||
| `special FadeOutMusic` / `FadeOutToBlack` / `FadeInFromBlack` / `ReloadSpritesNoPalettes` / `RestartMapMusic` (Route 24 grunt exit, gym grunt scene) | `src/script/gen2/Specials.lua:999`, `:1001`, `:1025`, `:1059`, `:1066` | implemented |
|
||||
| `showemote`, `applymovement` (incl. `big_step`, `jump_step`, `fix_facing`, `set_sliding`, `slow_step`), `turnobject`, `disappear`/`appear` | `src/script/gen2/Vm.lua`, `src/script/gen2/Movement.lua` | implemented |
|
||||
| Kanto Gen 2 wild tables (`kanto_grass`/`kanto_water`, Gold/Silver split on Route 9) | extracted by `src/import/RomExtractorGen2.lua`; consumed by `src/battle/gen2/Encounter.lua` | implemented - version split comes from the ROM, so it is correct by construction |
|
||||
| End-to-end driver coverage for this stretch | `tests/drivers/gold_*.lua` (25 drivers: boot, walk, warp, trainer, battle, roamers, ice path, etc.) | missing - no Kanto / Cerulean / Power Plant driver exists |
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
- **Cut tree positions.** The walkthrough says "Cut the tree at the entrance" (Route 9) and
|
||||
"Cut the tree above Pat" (Route 25). Cut trees are block data in `maps/Route9.blk` /
|
||||
`maps/Route25.blk`, not events, so no coordinate for them appears anywhere in the asm text.
|
||||
A bot needs the decoded block map (or the port's `Permissions.isCutTree` over the loaded
|
||||
collision) to find them. The Protein ball at `32,4` sitting two cells above Pat at `31,7`
|
||||
is consistent with the walkthrough's claim, but the tree cell itself is unverified here.
|
||||
- **Kevin's battle is omitted from the walkthrough.** The text says only "that guy will give
|
||||
you a Nugget". `TrainerCooltrainermKevin` gives the NUGGET and then immediately battles you
|
||||
with L38 RHYHORN / L35 CHARMELEON / L35 WARTORTLE. Treat the walkthrough as incomplete here,
|
||||
not the asm.
|
||||
- **Wild lists are abridged in the walkthrough.** Route 24 also has VENONAT / SUNKERN /
|
||||
ODDISH / VENOMOTH, and Route 25 also has PIDGEY / PIDGEOTTO / VENONAT / WEEPINBELL /
|
||||
ODDISH / VENOMOTH, none of which the FAQ lists. Route 5's own table (PIDGEY / BELLSPROUT /
|
||||
ABRA / ODDISH / GLOOM) is not listed at all.
|
||||
- **"Surf down the river ... and you'll be at the Power Plant."** The Power Plant door is
|
||||
`warp_event 3, 9` on `ROUTE_10_NORTH`, and the water table for both `ROUTE_9` and
|
||||
`ROUTE_10_NORTH` exists, so surfing is clearly the route - but the exact water path across
|
||||
the Route 9 / Route 10 North connection boundary is block data and was not traced.
|
||||
- **EXP values.** Every per-mon EXP number in the walkthrough (e.g. "Level 42 Golduck
|
||||
(1566 EXP)") depends on the player's own level and participation; nothing in
|
||||
`data/trainers/parties.asm` carries them. Money values were all reproduced from
|
||||
`data/trainers/attributes.asm` base reward x last-mon level x 4 and check out.
|
||||
- **"the 11th gym badge".** The asm has no badge counter tied to this; `ENGINE_CASCADEBADGE`
|
||||
is just one bit in `constants/engine_flags.asm:48`. The ordinal is walkthrough prose.
|
||||
- **Route 5 "Day Care Center from Red, Blue and Yellow".** The building is
|
||||
`ROUTE_5_CLEANSE_TAG_HOUSE` and its sign at `10,11` reads "House for Sale... Nobody lives
|
||||
here." Nothing in Gen 2 calls it a Day Care; that identification is the FAQ's own history
|
||||
note. Also note the sign and the door share the tile `10,11`, so an A-press there may hit
|
||||
either depending on facing - worth verifying against the port's bg-event-vs-warp priority.
|
||||
- **`POWERPLANT_GYM_GUIDE3` / `PowerPlantGymGuide4Script` naming mismatch** in
|
||||
`maps/PowerPlant.asm` (there is no `PowerPlantGymGuide3Script`). Harmless, but a
|
||||
label-driven bot should not assume the const and script numbers line up.
|
||||
@@ -0,0 +1,919 @@
|
||||
# Section 24 - Snorlax and Pewter City Gym
|
||||
|
||||
Source: `../section-24-snorlax-and-pewter-city-gym.txt`
|
||||
(the FAQ numbers this chapter "30 > Snorlax and Pewter City Gym"; the file index
|
||||
is 24)
|
||||
|
||||
Maps covered: `MAP_ROUTE_19`, `MAP_ROUTE_19_FUCHSIA_GATE`, `MAP_VERMILION_CITY`,
|
||||
`MAP_VERMILION_POKECENTER_1F`, `MAP_DIGLETTS_CAVE`, `MAP_ROUTE_2`,
|
||||
`MAP_ROUTE_2_NUGGET_HOUSE`, `MAP_ROUTE_2_GATE`, `MAP_PEWTER_CITY`,
|
||||
`MAP_PEWTER_POKECENTER_1F`, `MAP_PEWTER_GYM`
|
||||
|
||||
Badges / key milestones in this section:
|
||||
|
||||
- Level 50 **SNORLAX** caught in Vermilion City, holding LEFTOVERS
|
||||
(`BATTLETYPE_FORCEITEM`); clearing it opens the DIGLETT'S CAVE door
|
||||
- **SILVER_WING** (Gold) / **RAINBOW_WING** (Silver) from the Pewter City gramps
|
||||
- **NUGGET** from the Route 2 house
|
||||
- CARBOS, DIRE_HIT, ELIXER, MAX_POTION item balls on Route 2
|
||||
- **BOULDERBADGE** from BROCK (`ENGINE_BOULDERBADGE`)
|
||||
|
||||
---
|
||||
|
||||
## 1. Route order
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 0 | `FUCHSIA_CITY` | - | previous section | south connection, or `ROUTE_19_FUCHSIA_GATE` warps 1/2 at (4,0)/(5,0) | **Belongs to the previous section**; only the hop south is listed |
|
||||
| 1 | `ROUTE_19_FUCHSIA_GATE` | `maps/Route19FuchsiaGate.asm` | warps 1/2 at (4,0)/(5,0) from `FUCHSIA_CITY` 10/11 | warps 3/4 at (4,7)/(5,7) -> `ROUTE_19` 1 | Officer explains why Route 19 is shut |
|
||||
| 2 | `ROUTE_19` | `maps/Route19.asm` | warp 1 at (7,3) from the gate | dead end - back through warp 1 | **Blocked.** `Route19ClearRocksCallback` paints six boulders while `EVENT_CINNABAR_ROCKS_CLEARED` is clear |
|
||||
| 3 | `VERMILION_CITY` | `maps/VermilionCity.asm` | FLY (`ENGINE_FLYPOINT_VERMILION`, set by `VermilionCityFlypointCallback`) | warp 10 at (34,7) -> `DIGLETTS_CAVE` 1 | Wake and battle the SNORLAX at (34,8) with the POKe FLUTE radio channel |
|
||||
| 4 | `VERMILION_POKECENTER_1F` | `maps/VermilionPokecenter1F.asm` | city warp 2 at (9,5) | warps 1/2 at (3,7)/(4,7) back | Heal and withdraw the caught SNORLAX |
|
||||
| 5 | `DIGLETTS_CAVE` | `maps/DiglettsCave.asm` | warp 1 at (3,33) from `VERMILION_CITY` 10 | warp 3 at (15,5) -> `ROUTE_2` 5 | Ladder chain 2->5, long cave, ladder 6->4, then the door |
|
||||
| 6 | `ROUTE_2` | `maps/Route2.asm` | warp 5 at (12,7) from `DIGLETTS_CAVE` 3 | north connection to `PEWTER_CITY` | Carbos, Nugget house, Elixer, three Bug Catchers, two cut trees |
|
||||
| 7 | `ROUTE_2_NUGGET_HOUSE` | `maps/Route2NuggetHouse.asm` | Route 2 warp 1 at (15,15) | warps 1/2 at (2,7)/(3,7) back | `Route2NuggetHouseFisherScript` -> `verbosegiveitem NUGGET` |
|
||||
| 8 | `ROUTE_2_GATE` | `maps/Route2Gate.asm` | Route 2 warps 3/4 at (16,27)/(17,27) | warps 3/4 at (4,7)/(5,7) -> `ROUTE_2` 2 at (15,31) | The "route-changing house" that drops you further south on the same route |
|
||||
| 9 | `PEWTER_CITY` | `maps/PewterCity.asm` | south connection from `ROUTE_2` (`connection north, PewterCity, PEWTER_CITY, -5`) | five warps, see below | Sets `ENGINE_FLYPOINT_PEWTER`; gramps hands over the wing |
|
||||
| 10 | `PEWTER_POKECENTER_1F` | `maps/PewterPokecenter1F.asm` | city warp 4 at (13,25) | warps 1/2 at (3,7)/(4,7) back | Heal; `Chris` runs `trade NPC_TRADE_CHRIS` (GLOOM -> RAPIDASH) |
|
||||
| 11 | `PEWTER_GYM` | `maps/PewterGym.asm` | city warp 2 at (16,17) | warps 1/2 at (4,13)/(5,13) back | Camper Jerry, then **BROCK** for the BOULDERBADGE |
|
||||
|
||||
Spillover: the FAQ's video-link header also names Tin Tower, Ho-Oh, Routes 1-4
|
||||
and Pallet Town. Nothing in the prose of this section enters those maps - the
|
||||
Routes 3/4 and Pallet beats belong to the following section and are not covered
|
||||
here.
|
||||
|
||||
---
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_ROUTE_19
|
||||
|
||||
- Script: `maps/Route19.asm`
|
||||
- Blocks: `maps/Route19.blk`
|
||||
- Header: `data/maps/maps.asm:196` -> `TILESET_KANTO, ROUTE, LANDMARK_ROUTE_19, MUSIC_ROUTE_3, FALSE (phone allowed), PALETTE_AUTO, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:180` `map_const ROUTE_19, 10, 18` (group `CINNABAR`, id 5) = 20 x 36 cells
|
||||
- Attributes / border: `data/maps/attributes.asm:287` `map_attributes Route19, ROUTE_19, $43`
|
||||
- Connections: `connection north, FuchsiaCity, FUCHSIA_CITY, 0`; `connection west, Route20, ROUTE_20, 9`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 7 | 3 | `ROUTE_19_FUCHSIA_GATE` | 3 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
none.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 11 | 13 | `BGEVENT_READ` | `Route19Sign` |
|
||||
| 11 | 1 | `BGEVENT_READ` | `CarefulSwimmingSign` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE19_SWIMMER_GIRL` | `SPRITE_SWIMMER_GIRL` | 9 | 23 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 0) | `TrainerSwimmerfDawn` | -1 |
|
||||
| `ROUTE19_SWIMMER_GUY1` | `SPRITE_SWIMMER_GUY` | 13 | 28 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerSwimmermHarold` | -1 |
|
||||
| `ROUTE19_SWIMMER_GUY2` | `SPRITE_SWIMMER_GUY` | 11 | 17 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerSwimmermJerome` | -1 |
|
||||
| `ROUTE19_SWIMMER_GUY3` | `SPRITE_SWIMMER_GUY` | 8 | 23 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` (sight 0) | `TrainerSwimmermTucker` | -1 |
|
||||
| `ROUTE19_FISHER1` | `SPRITE_FISHER` | 9 | 5 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` (sight 1) | `Route19Fisher1Script` | -1 |
|
||||
| `ROUTE19_FISHER2` | `SPRITE_FISHER` | 11 | 5 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT` (sight 1) | `Route19Fisher2Script` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route19ClearRocksCallback` (`callback MAPCALLBACK_TILES`). `checkevent
|
||||
EVENT_CINNABAR_ROCKS_CLEARED` / `iftrue .Done`; otherwise it runs six
|
||||
`changeblock` writes that lay a rock block (`$7a`) over block coordinates
|
||||
`(6,6) (8,6) (10,6) (12,8) (4,8) (10,10)`. This is the wall the FAQ hits.
|
||||
Block coordinates, so the blocked cells are `x = 2*bx .. 2*bx+1`,
|
||||
`y = 2*by .. 2*by+1`.
|
||||
- `Route19Fisher1Script` / `Route19Fisher2Script` both branch on
|
||||
`EVENT_CINNABAR_ROCKS_CLEARED` for flavour text only. Neither one clears the
|
||||
rocks - see "Blockers and gates".
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_CINNABAR_ROCKS_CLEARED` | `constants/event_flags.asm:214` | read by `Route19ClearRocksCallback`, `Route19Fisher1Script`, `Route19Fisher2Script`, `Route19FuchsiaGateOfficerScript`; **set** by `Route20ClearRocksCallback` (`maps/Route20.asm:13`, `MAPCALLBACK_NEWMAP`) | Route 19 stays blocked until the player has stood on Route 20, which is reached from the Cinnabar side (Pallet -> Route 21 -> Cinnabar -> Route 20) |
|
||||
|
||||
**Items** - none on this map.
|
||||
|
||||
**Trainers** - four Swimmers, all reachable only by Surf and all beyond the
|
||||
boulders in the FAQ's play order; not part of this section's critical path.
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_19_FUCHSIA_GATE
|
||||
|
||||
- Script: `maps/Route19FuchsiaGate.asm`
|
||||
- Header: `data/maps/maps.asm:194` -> `TILESET_GATE, GATE, LANDMARK_ROUTE_19, MUSIC_ROUTE_3, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:178` `map_const ROUTE_19_FUCHSIA_GATE, 5, 4` (group `CINNABAR`, id 3)
|
||||
- Attributes: `data/maps/attributes.asm:512` `map_attributes Route19FuchsiaGate, ROUTE_19_FUCHSIA_GATE, $00`; no connections
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 0 | `FUCHSIA_CITY` | 10 |
|
||||
| 2 | 5 | 0 | `FUCHSIA_CITY` | 11 |
|
||||
| 3 | 4 | 7 | `ROUTE_19` | 1 |
|
||||
| 4 | 5 | 7 | `ROUTE_19` | 1 |
|
||||
|
||||
**Coord events** - none. **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE19FUCHSIAGATE_OFFICER` | `SPRITE_OFFICER` | 0 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `Route19FuchsiaGateOfficerScript` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route19FuchsiaGateOfficerScript`: `checkevent EVENT_CINNABAR_ROCKS_CLEARED`,
|
||||
two text branches. Pure flavour; the officer does not gate the warp.
|
||||
|
||||
---
|
||||
|
||||
### MAP_VERMILION_CITY
|
||||
|
||||
- Script: `maps/VermilionCity.asm`
|
||||
- Blocks: `maps/VermilionCity.blk`
|
||||
- Header: `data/maps/maps.asm:296` -> `TILESET_KANTO, TOWN, LANDMARK_VERMILION_CITY, MUSIC_VERMILION_CITY, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN`
|
||||
- Dimensions: `constants/map_constants.asm:273` `map_const VERMILION_CITY, 20, 18` (group `VERMILION`, id 3) = 40 x 36 cells
|
||||
- Attributes: `data/maps/attributes.asm:342` `map_attributes VermilionCity, VERMILION_CITY, $43`
|
||||
- Connections: `connection north, Route6, ROUTE_6, 5`; `connection east, Route11, ROUTE_11, 0`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 5 | 5 | `VERMILION_FISHING_SPEECH_HOUSE` | 1 |
|
||||
| 2 | 9 | 5 | `VERMILION_POKECENTER_1F` | 1 |
|
||||
| 3 | 7 | 13 | `POKEMON_FAN_CLUB` | 1 |
|
||||
| 4 | 13 | 13 | `VERMILION_MAGNET_TRAIN_SPEECH_HOUSE` | 1 |
|
||||
| 5 | 21 | 13 | `VERMILION_MART` | 2 |
|
||||
| 6 | 21 | 17 | `VERMILION_DIGLETTS_CAVE_SPEECH_HOUSE` | 1 |
|
||||
| 7 | 10 | 19 | `VERMILION_GYM` | 1 |
|
||||
| 8 | 19 | 31 | `VERMILION_PORT_PASSAGE` | 1 |
|
||||
| 9 | 20 | 31 | `VERMILION_PORT_PASSAGE` | 2 |
|
||||
| 10 | 34 | 7 | `DIGLETTS_CAVE` | 1 |
|
||||
|
||||
**Coord events** (`def_coord_events`) - none.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 25 | 3 | `BGEVENT_READ` | `VermilionCitySign` |
|
||||
| 5 | 19 | `BGEVENT_READ` | `VermilionGymSign` |
|
||||
| 5 | 13 | `BGEVENT_READ` | `PokemonFanClubSign` |
|
||||
| 33 | 9 | `BGEVENT_READ` | `VermilionCityDiglettsCaveSign` |
|
||||
| 27 | 15 | `BGEVENT_READ` | `VermilionCityPortSign` |
|
||||
| 10 | 5 | `BGEVENT_READ` | `VermilionCityPokecenterSign` (`jumpstd PokecenterSignScript`) |
|
||||
| 22 | 13 | `BGEVENT_READ` | `VermilionCityMartSign` (`jumpstd MartSignScript`) |
|
||||
| 12 | 19 | `BGEVENT_ITEM` | `VermilionCityHiddenFullHeal` -> `hiddenitem FULL_HEAL, EVENT_VERMILION_CITY_HIDDEN_FULL_HEAL` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `VERMILIONCITY_TEACHER` | `SPRITE_TEACHER` | 18 | 9 | `SPRITEMOVEDATA_WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `VermilionCityTeacherScript` | -1 |
|
||||
| `VERMILIONCITY_GRAMPS` | `SPRITE_GRAMPS` | 23 | 6 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `VermilionMachopOwner` | -1 |
|
||||
| `VERMILIONCITY_MACHOP` | `SPRITE_MACHOP` | 26 | 7 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT` | `VermilionMachop` | -1 |
|
||||
| `VERMILIONCITY_SUPER_NERD` | `SPRITE_SUPER_NERD` | 14 | 16 | `SPRITEMOVEDATA_WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `VermilionCitySuperNerdScript` | -1 |
|
||||
| `VERMILIONCITY_BIG_SNORLAX` | `SPRITE_BIG_SNORLAX` | 34 | 8 | `SPRITEMOVEDATA_BIGDOLLSYM` | `OBJECTTYPE_SCRIPT` | `VermilionSnorlax` | `EVENT_VERMILION_CITY_SNORLAX` |
|
||||
| `VERMILIONCITY_POKEFAN_M` | `SPRITE_POKEFAN_M` | 31 | 12 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `VermilionGymBadgeGuy` | -1 |
|
||||
|
||||
Note the object-flag polarity (`CheckObjectFlag`,
|
||||
`engine/overworld/map_objects_2.asm:32`): an object with an `EVENT_*` flag is
|
||||
**masked when the flag is set**. So the SNORLAX is present only while
|
||||
`EVENT_VERMILION_CITY_SNORLAX` is clear, and `disappear` is what sets it.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `VermilionSnorlax` (`4f:5291`). `opentext` -> `special SnorlaxAwake` ->
|
||||
`iftrue .Awake`. Asleep branch writes `VermilionCitySnorlaxSleepingText` and
|
||||
ends. `.Awake` writes `VermilionCityRadioNearSnorlaxText`, `pause 15`,
|
||||
`cry SNORLAX`, then
|
||||
`loadvar VAR_BATTLETYPE, BATTLETYPE_FORCEITEM` /
|
||||
`loadwildmon SNORLAX, 50` / `startbattle` /
|
||||
`disappear VERMILIONCITY_BIG_SNORLAX` / `setevent EVENT_FOUGHT_SNORLAX` /
|
||||
`reloadmapafterbattle`.
|
||||
The `disappear` and `setevent` run **unconditionally after the battle**, so a
|
||||
fled-from or fainted Snorlax is still gone: catching it is a one-shot.
|
||||
- `SnorlaxAwake` (`engine/events/specials.asm:358`, symbol `03:45d6`). Returns
|
||||
`TRUE` only when *both* hold:
|
||||
1. `[wMapMusic] == MUSIC_POKE_FLUTE_CHANNEL`;
|
||||
2. the player stands on one of `.ProximityCoords`
|
||||
(`engine/events/specials.asm:399`): `(33,8) (34,10) (35,10) (36,8) (36,9)`.
|
||||
The POKe FLUTE channel is the Pokegear radio at knob 78 / "20.0"
|
||||
(`engine/pokegear/pokegear.asm:1447`, `.PokeFluteRadio` at 1487): it needs
|
||||
**not-Johto** and `POKEGEAR_EXPN_CARD_F` in `wPokegearFlags`. Tuning the radio
|
||||
and closing the Pokegear leaves that song as `wMapMusic`.
|
||||
- `VermilionGymBadgeGuy`: `checkevent EVENT_GOT_HP_UP_FROM_VERMILION_GUY`, then
|
||||
`readvar VAR_BADGES` with `ifequal NUM_BADGES` / `ifgreater 13` /
|
||||
`ifgreater 9`. `verbosegiveitem HP_UP` only at all 16 badges. Not this
|
||||
section's business, but a bot walking Vermilion will trip the text.
|
||||
- `VermilionCityFlypointCallback` (`MAPCALLBACK_NEWMAP`):
|
||||
`setflag ENGINE_FLYPOINT_VERMILION`. This is what makes the FAQ's "fly back to
|
||||
Vermilion City" legal - it must already have been visited on foot.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_VERMILION_CITY_SNORLAX` | `constants/event_flags.asm:1298` | object flag on `VERMILIONCITY_BIG_SNORLAX`; set by `disappear` in `VermilionSnorlax` | clear = Snorlax on the map; set = gone, cave door walkable |
|
||||
| `EVENT_FOUGHT_SNORLAX` | `constants/event_flags.asm:1266` | set by `VermilionSnorlax`; read by `VermilionPokecenter1FFishingGuruScript` (`maps/VermilionPokecenter1F.asm:18`) and used as the object flag of the right Black Belt in `maps/VictoryRoadGate.asm:119` | "the Snorlax encounter has happened" |
|
||||
| `ENGINE_FLYPOINT_VERMILION` | `constants/engine_flags.asm:72` | `VermilionCityFlypointCallback` | Fly destination unlocked |
|
||||
| `POKEGEAR_EXPN_CARD_F` | `constants/ram_constants.asm:280` | read by `.PokeFluteRadio` | without the EXPN CARD the flute channel is static and `SnorlaxAwake` can never be true |
|
||||
| `EVENT_VERMILION_CITY_HIDDEN_FULL_HEAL` | `constants/event_flags.asm:252` | `VermilionCityHiddenFullHeal` | hidden FULL_HEAL at (12,19) |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `LEFTOVERS` | attached to the wild SNORLAX | `data/pokemon/base_stats/snorlax.asm:9` `db LEFTOVERS, LEFTOVERS` forced by `BATTLETYPE_FORCEITEM` (`engine/battle/core.asm:5771`) | - |
|
||||
| `FULL_HEAL` | hidden | `bg_event 12, 19, BGEVENT_ITEM, VermilionCityHiddenFullHeal` | `EVENT_VERMILION_CITY_HIDDEN_FULL_HEAL` |
|
||||
|
||||
**Wild encounters** - the scripted SNORLAX is not a table entry; it is
|
||||
`loadwildmon SNORLAX, 50`. Level 50 learnset from
|
||||
`data/pokemon/evos_attacks.asm:1932`: the four most recent are SNORE (36),
|
||||
REST (36), BODY_SLAM (43), ROLLOUT (50) - which is exactly the FAQ's "Rest" and
|
||||
"Rollout". Catch rate 25, base exp 154 (`data/pokemon/base_stats/snorlax.asm`).
|
||||
|
||||
---
|
||||
|
||||
### MAP_VERMILION_POKECENTER_1F
|
||||
|
||||
- Script: `maps/VermilionPokecenter1F.asm`
|
||||
- Header: `data/maps/maps.asm` (`VERMILION` group); dimensions
|
||||
`constants/map_constants.asm:275` `map_const VERMILION_POKECENTER_1F, 5, 4`
|
||||
|
||||
Only the one script matters here:
|
||||
|
||||
- `VermilionPokecenter1FFishingGuruScript`: `checkevent EVENT_FOUGHT_SNORLAX`,
|
||||
two text branches ("A sleeping #MON is lying in front of DIGLETT'S CAVE" ->
|
||||
after-text). It is a read-only confirmation that the Snorlax beat is done; the
|
||||
PC withdraw the FAQ asks for is the standard `POKECENTER_2F` / PC menu, not a
|
||||
map script.
|
||||
|
||||
---
|
||||
|
||||
### MAP_DIGLETTS_CAVE
|
||||
|
||||
- Script: `maps/DiglettsCave.asm`
|
||||
- Blocks: `maps/DiglettsCave.blk` (180 bytes = 10 x 18 blocks)
|
||||
- Header: `data/maps/maps.asm:153` -> `TILESET_CAVE, CAVE, LANDMARK_DIGLETTS_CAVE, MUSIC_MT_MOON, TRUE (no phone), PALETTE_NITE, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:140` `map_const DIGLETTS_CAVE, 10, 18` (group `DUNGEONS`, id 75) = 20 x 36 cells
|
||||
- Attributes: `data/maps/attributes.asm:487` `map_attributes DiglettsCave, DIGLETTS_CAVE, $09`; no connections
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 33 | `VERMILION_CITY` | 10 |
|
||||
| 2 | 5 | 31 | `DIGLETTS_CAVE` | 5 |
|
||||
| 3 | 15 | 5 | `ROUTE_2` | 5 |
|
||||
| 4 | 17 | 3 | `DIGLETTS_CAVE` | 6 |
|
||||
| 5 | 17 | 33 | `DIGLETTS_CAVE` | 2 |
|
||||
| 6 | 3 | 3 | `DIGLETTS_CAVE` | 4 |
|
||||
|
||||
Warps 2<->5 and 4<->6 are the two internal ladders. Reading `DiglettsCave.blk`
|
||||
against `data/tilesets/kanto_collision.asm` (block `$1f`/`$1b` = ladder, `$24` =
|
||||
door) the map is three disconnected pockets:
|
||||
|
||||
- pocket A (bottom-left, blocks (1,15)-(2,17)): warp 1 (VERMILION door) + warp 2
|
||||
(ladder);
|
||||
- pocket B (the long cave): warp 5 at (17,33) up to warp 6 at (3,3);
|
||||
- pocket C (top-right, blocks (7,1)-(8,3)): warp 4 (ladder) + warp 3 (ROUTE_2
|
||||
door).
|
||||
|
||||
So the traversal is: `(3,33)` -> ladder `(5,31)` -> emerge `(17,33)` -> walk the
|
||||
long cave to `(3,3)` -> ladder -> emerge `(17,3)` -> door `(15,5)` -> Route 2
|
||||
`(12,7)`. That is exactly the FAQ's "down the ladder, ride up, up that ladder,
|
||||
exit the door".
|
||||
|
||||
**Coord events** - none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 6 | 11 | `BGEVENT_ITEM` | `DiglettsCaveHiddenMaxRevive` -> `hiddenitem MAX_REVIVE, EVENT_DIGLETTS_CAVE_HIDDEN_MAX_REVIVE` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `DIGLETTSCAVE_POKEFAN_M` | `SPRITE_POKEFAN_M` | 3 | 31 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `DiglettsCavePokefanMScript` | -1 |
|
||||
|
||||
**Trainers** - none, matching the FAQ.
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `MAX_REVIVE` | hidden (ITEMFINDER / bump) | `bg_event 6, 11, BGEVENT_ITEM` | `EVENT_DIGLETTS_CAVE_HIDDEN_MAX_REVIVE` (`constants/event_flags.asm:228`) |
|
||||
|
||||
**Wild encounters** - `data/wild/kanto_grass.asm:5` `def_grass_wildmons
|
||||
DIGLETTS_CAVE`, rates `4 / 2 / 8` percent (morn/day/nite). All three time slots
|
||||
carry the same seven rows:
|
||||
|
||||
| slot | level | species |
|
||||
|---|---|---|
|
||||
| 1 | 15 | DIGLETT |
|
||||
| 2 | 17 | DIGLETT |
|
||||
| 3 | 19 | DIGLETT |
|
||||
| 4 | 13 | DIGLETT |
|
||||
| 5 | 19 | DUGTRIO |
|
||||
| 6 | 24 | DUGTRIO |
|
||||
| 7 | 29 | DUGTRIO |
|
||||
|
||||
The FAQ's "capture both Diglett and Dugtrio here" is correct, and night is the
|
||||
best time (8% vs 2% by day).
|
||||
|
||||
The FAQ's "ride your bike" is legal here: `BikeFunction`'s `.CheckEnvironment`
|
||||
(`engine/events/overworld.asm:1665`) allows outdoor maps, `CAVE` and `GATE`.
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_2
|
||||
|
||||
- Script: `maps/Route2.asm`
|
||||
- Blocks: `maps/Route2.blk` (270 bytes = 10 x 27 blocks)
|
||||
- Header: `data/maps/maps.asm:455` -> `TILESET_KANTO, ROUTE, LANDMARK_ROUTE_2, MUSIC_ROUTE_2, FALSE, PALETTE_AUTO, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:421` `map_const ROUTE_2, 10, 27` (group `VIRIDIAN`, id 1) = 20 x 54 cells
|
||||
- Attributes: `data/maps/attributes.asm:255` `map_attributes Route2, ROUTE_2, $0f`
|
||||
- Connections: `connection north, PewterCity, PEWTER_CITY, -5`;
|
||||
`connection south, ViridianCity, VIRIDIAN_CITY, -5`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 15 | 15 | `ROUTE_2_NUGGET_HOUSE` | 1 |
|
||||
| 2 | 15 | 31 | `ROUTE_2_GATE` | 3 |
|
||||
| 3 | 16 | 27 | `ROUTE_2_GATE` | 1 |
|
||||
| 4 | 17 | 27 | `ROUTE_2_GATE` | 2 |
|
||||
| 5 | 12 | 7 | `DIGLETTS_CAVE` | 3 |
|
||||
|
||||
**Coord events** - none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 7 | 51 | `BGEVENT_READ` | `Route2Sign` |
|
||||
| 11 | 9 | `BGEVENT_READ` | `Route2DiglettsCaveSign` |
|
||||
| 7 | 23 | `BGEVENT_ITEM` | `Route2HiddenMaxEther` -> `hiddenitem MAX_ETHER, EVENT_ROUTE_2_HIDDEN_MAX_ETHER` |
|
||||
| 4 | 14 | `BGEVENT_ITEM` | `Route2HiddenFullHeal` -> `hiddenitem FULL_HEAL, EVENT_ROUTE_2_HIDDEN_FULL_HEAL` |
|
||||
| 4 | 27 | `BGEVENT_ITEM` | `Route2HiddenFullRestore` -> `hiddenitem FULL_RESTORE, EVENT_ROUTE_2_HIDDEN_FULL_RESTORE` |
|
||||
| 11 | 30 | `BGEVENT_ITEM` | `Route2HiddenRevive` -> `hiddenitem REVIVE, EVENT_ROUTE_2_HIDDEN_REVIVE` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE2_BUG_CATCHER1` | `SPRITE_BUG_CATCHER` | 10 | 45 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER` (sight 5) | `TrainerBugCatcherRob` | -1 |
|
||||
| `ROUTE2_BUG_CATCHER2` | `SPRITE_BUG_CATCHER` | 5 | 5 | `SPRITEMOVEDATA_STANDING_RIGHT` (radius 1,0) | `OBJECTTYPE_TRAINER` (sight 4) | `TrainerBugCatcherEd` | -1 |
|
||||
| `ROUTE2_BUG_CATCHER3` | `SPRITE_BUG_CATCHER` | 4 | 43 | `SPRITEMOVEDATA_STANDING_UP` (radius 1,0) | `OBJECTTYPE_TRAINER` (sight 5) | `TrainerBugCatcherDoug` | -1 |
|
||||
| `ROUTE2_POKE_BALL1` | `SPRITE_POKE_BALL` | 0 | 29 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route2DireHit` (`itemball DIRE_HIT`) | `EVENT_ROUTE_2_DIRE_HIT` |
|
||||
| `ROUTE2_POKE_BALL2` | `SPRITE_POKE_BALL` | 2 | 23 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route2MaxPotion` (`itemball MAX_POTION`) | `EVENT_ROUTE_2_MAX_POTION` |
|
||||
| `ROUTE2_POKE_BALL3` | `SPRITE_POKE_BALL` | 19 | 2 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route2Carbos` (`itemball CARBOS`) | `EVENT_ROUTE_2_CARBOS` |
|
||||
| `ROUTE2_POKE_BALL4` | `SPRITE_POKE_BALL` | 14 | 50 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route2Elixer` (`itemball ELIXER`) | `EVENT_ROUTE_2_ELIXER` |
|
||||
| `ROUTE2_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 10 | 14 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `Route2FruitTree` (`fruittree FRUITTREE_ROUTE_2`) | -1 |
|
||||
|
||||
**Cut trees** (not asm rows - decoded from `maps/Route2.blk` against the
|
||||
`CUT_TREE` entries of `data/tilesets/kanto_collision.asm`, which are blocks
|
||||
`$00 $32 $33 $34 $35 $60`)
|
||||
|
||||
| block (bx,by) | block id | covered cells (x, y) | which FAQ beat |
|
||||
|---|---|---|---|
|
||||
| (2, 4) | `$32` | 4-5, 8-9 | near the Diglett's Cave door, not used by the FAQ |
|
||||
| (7, 9) | `$32` | 14-15, 18-19 | "cut the tree to the south" (just below the Nugget house at (15,15)) |
|
||||
| (5, 20) | `$34` | 10-11, 40-41 | not named by the FAQ |
|
||||
| (6, 23) | `$34` | 12-13, 46-47 | not named by the FAQ |
|
||||
| (6, 25) | `$34` | 12-13, 50-51 | "cut the tree just left of it" (immediately left of the Elixer at (14,50)) |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `TrainerBugCatcherRob` / `TrainerBugCatcherEd` / `TrainerBugCatcherDoug`
|
||||
(`4d:5bc2`, `4d:5bd6`, `4d:5bea`): each is the plain `trainer CLASS, MEMBER,
|
||||
EVENT_*, seen, beaten, 0, .Script` header with an `endifjustbattled` +
|
||||
after-battle text body. No items, no flags beyond the beaten event.
|
||||
- `Route2FruitTree`: `fruittree FRUITTREE_ROUTE_2` ->
|
||||
`data/items/fruit_trees.asm:28` `db PSNCUREBERRY`. This is the FAQ's
|
||||
"PSNCure Berry".
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_ROUTE_2_DIRE_HIT` | `constants/event_flags.asm:1318` | item ball object flag | clear = ball present |
|
||||
| `EVENT_ROUTE_2_MAX_POTION` | `constants/event_flags.asm:1319` | item ball object flag | clear = ball present |
|
||||
| `EVENT_ROUTE_2_CARBOS` | `constants/event_flags.asm:1320` | item ball object flag | clear = ball present |
|
||||
| `EVENT_ROUTE_2_ELIXER` | `constants/event_flags.asm:1321` | item ball object flag | clear = ball present |
|
||||
| `EVENT_ROUTE_2_HIDDEN_MAX_ETHER` | `constants/event_flags.asm:237` | `Route2HiddenMaxEther` | hidden pickup |
|
||||
| `EVENT_ROUTE_2_HIDDEN_FULL_HEAL` | `constants/event_flags.asm:238` | `Route2HiddenFullHeal` | hidden pickup |
|
||||
| `EVENT_ROUTE_2_HIDDEN_FULL_RESTORE` | `constants/event_flags.asm:239` | `Route2HiddenFullRestore` | hidden pickup |
|
||||
| `EVENT_ROUTE_2_HIDDEN_REVIVE` | `constants/event_flags.asm:240` | `Route2HiddenRevive` | hidden pickup |
|
||||
| `EVENT_BEAT_BUG_CATCHER_ROB` | `constants/event_flags.asm:840` | `TrainerBugCatcherRob` | trainer beaten |
|
||||
| `EVENT_BEAT_BUG_CATCHER_ED` | `constants/event_flags.asm:841` | `TrainerBugCatcherEd` | trainer beaten |
|
||||
| `EVENT_BEAT_BUG_CATCHER_DOUG` | `constants/event_flags.asm:850` | `TrainerBugCatcherDoug` | trainer beaten |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `CARBOS` | item ball at (19,2) | `Route2Carbos` | `EVENT_ROUTE_2_CARBOS` |
|
||||
| `MAX_POTION` | item ball at (2,23) | `Route2MaxPotion` | `EVENT_ROUTE_2_MAX_POTION` |
|
||||
| `DIRE_HIT` | item ball at (0,29) | `Route2DireHit` | `EVENT_ROUTE_2_DIRE_HIT` |
|
||||
| `ELIXER` | item ball at (14,50) | `Route2Elixer` | `EVENT_ROUTE_2_ELIXER` |
|
||||
| `PSNCUREBERRY` | fruit tree object at (10,14) | `FRUITTREE_ROUTE_2`, `data/items/fruit_trees.asm:28` | daily reset, not an event flag |
|
||||
| `MAX_ETHER` | hidden at (7,23) | bg_event | `EVENT_ROUTE_2_HIDDEN_MAX_ETHER` |
|
||||
| `FULL_HEAL` | hidden at (4,14) | bg_event | `EVENT_ROUTE_2_HIDDEN_FULL_HEAL` |
|
||||
| `FULL_RESTORE` | hidden at (4,27) | bg_event | `EVENT_ROUTE_2_HIDDEN_FULL_RESTORE` |
|
||||
| `REVIVE` | hidden at (11,30) | bg_event | `EVENT_ROUTE_2_HIDDEN_REVIVE` |
|
||||
| `NUGGET` | Route 2 Nugget House NPC | `Route2NuggetHouseFisherScript` | `EVENT_GOT_NUGGET_FROM_GUY` |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `ROB` (`constants/trainer_constants.asm:314`) | `BUG_CATCHER` | 2 | `BugCatcherGroup` "ROB" (`data/trainers/parties.asm:1416`): L32 BEEDRILL, L32 BUTTERFREE, `TRAINERTYPE_NORMAL` | `TrainerBugCatcherRob` | no |
|
||||
| `DOUG` (`constants/trainer_constants.asm:324`) | `BUG_CATCHER` | 12 | `BugCatcherGroup` "DOUG" (`data/trainers/parties.asm:1482`): L34 ARIADOS | `TrainerBugCatcherDoug` | no |
|
||||
| `ED` (`constants/trainer_constants.asm:315`) | `BUG_CATCHER` | 3 | `BugCatcherGroup` "ED" (`data/trainers/parties.asm:1422`): L30 BEEDRILL x3 | `TrainerBugCatcherEd` | no |
|
||||
|
||||
`BUG_CATCHER` base reward 4 (`data/trainers/attributes.asm:215`); prize =
|
||||
base x last mon level (`ComputeTrainerReward`,
|
||||
`engine/battle/read_trainer_party.asm:300`).
|
||||
|
||||
**Wild encounters** - `data/wild/kanto_grass.asm:259` `def_grass_wildmons
|
||||
ROUTE_2`, rates `10 / 10 / 10`. Gold (`IF DEF(_GOLD)`):
|
||||
|
||||
| slot | morn | day | nite |
|
||||
|---|---|---|---|
|
||||
| 1 | L3 CATERPIE | L3 CATERPIE | L3 HOOTHOOT |
|
||||
| 2 | L3 PIDGEY | L3 PIDGEY | L3 SPINARAK |
|
||||
| 3 | L5 METAPOD | L5 METAPOD | L5 HOOTHOOT |
|
||||
| 4 | L7 BUTTERFREE | L7 PIDGEY | L7 NOCTOWL |
|
||||
| 5 | L7 PIDGEOTTO | L7 PIDGEOTTO | L7 ARIADOS |
|
||||
| 6 | L4 PIKACHU | L4 PIKACHU | L4 PIKACHU |
|
||||
| 7 | L4 PIKACHU | L4 PIKACHU | L4 PIKACHU |
|
||||
|
||||
(The Silver table under `ELIF DEF(_SILVER)` swaps the Caterpie line for
|
||||
Weedle/Kakuna/Beedrill and Spinarak for Ledyba.)
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_2_NUGGET_HOUSE
|
||||
|
||||
- Script: `maps/Route2NuggetHouse.asm`
|
||||
- Header: `data/maps/maps.asm:465` -> `TILESET_HOUSE, INDOOR, LANDMARK_ROUTE_2, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:431` `map_const ROUTE_2_NUGGET_HOUSE, 4, 4`
|
||||
- Attributes: `data/maps/attributes.asm:664`; no connections
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 2 | 7 | `ROUTE_2` | 1 |
|
||||
| 2 | 3 | 7 | `ROUTE_2` | 1 |
|
||||
|
||||
**Coord events** - none. **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE2NUGGETHOUSE_FISHER` | `SPRITE_FISHER` | 2 | 4 | `SPRITEMOVEDATA_WALK_UP_DOWN` (radius 0,2) | `OBJECTTYPE_SCRIPT` | `Route2NuggetHouseFisherScript` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route2NuggetHouseFisherScript` (`5f:4e26`): `faceplayer` / `opentext` /
|
||||
`checkevent EVENT_GOT_NUGGET_FROM_GUY` -> `.GotNugget`; otherwise text,
|
||||
`promptbutton`, `verbosegiveitem NUGGET`, `iffalse .NoRoom`,
|
||||
`setevent EVENT_GOT_NUGGET_FROM_GUY`. A full bag skips the flag, so the gift
|
||||
is re-offerable.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_GOT_NUGGET_FROM_GUY` | `constants/event_flags.asm:199` | `Route2NuggetHouseFisherScript` | one-time NUGGET |
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_2_GATE
|
||||
|
||||
- Script: `maps/Route2Gate.asm`
|
||||
- Header: `data/maps/maps.asm:466` -> `TILESET_GATE, GATE, LANDMARK_ROUTE_2, MUSIC_ROUTE_2, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:432` `map_const ROUTE_2_GATE, 5, 4`
|
||||
- Attributes: `data/maps/attributes.asm:665`; no connections
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 0 | `ROUTE_2` | 3 |
|
||||
| 2 | 5 | 0 | `ROUTE_2` | 4 |
|
||||
| 3 | 4 | 7 | `ROUTE_2` | 2 |
|
||||
| 4 | 5 | 7 | `ROUTE_2` | 2 |
|
||||
|
||||
Both sides land back on Route 2 - this is the FAQ's "route-changing house...
|
||||
You'll still be in Route 2". North side (y=0) <-> Route 2 (16,27)/(17,27);
|
||||
south side (y=7) <-> Route 2 (15,31).
|
||||
|
||||
**Coord events** - none. **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE2GATE_SCIENTIST` | `SPRITE_SCIENTIST` | 6 | 4 | `SPRITEMOVEDATA_WALK_UP_DOWN` (radius 0,2) | `OBJECTTYPE_SCRIPT` | `Route2GateScientistScript` | -1 |
|
||||
|
||||
`Route2GateScientistScript` is `jumptextfaceplayer` only (Oak's aide flavour).
|
||||
|
||||
---
|
||||
|
||||
### MAP_PEWTER_CITY
|
||||
|
||||
- Script: `maps/PewterCity.asm`
|
||||
- Blocks: `maps/PewterCity.blk`
|
||||
- Header: `data/maps/maps.asm:322` -> `TILESET_KANTO, TOWN, LANDMARK_PEWTER_CITY, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_AUTO, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:297` `map_const PEWTER_CITY, 20, 18` (group `PEWTER`, id 2) = 40 x 36 cells
|
||||
- Attributes: `data/maps/attributes.asm:251` `map_attributes PewterCity, PEWTER_CITY, $0f`
|
||||
- Connections: `connection south, Route2, ROUTE_2, 5`; `connection east, Route3, ROUTE_3, 5`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 29 | 13 | `PEWTER_NIDORAN_SPEECH_HOUSE` | 1 |
|
||||
| 2 | 16 | 17 | `PEWTER_GYM` | 1 |
|
||||
| 3 | 23 | 17 | `PEWTER_MART` | 2 |
|
||||
| 4 | 13 | 25 | `PEWTER_POKECENTER_1F` | 1 |
|
||||
| 5 | 7 | 29 | `PEWTER_SNOOZE_SPEECH_HOUSE` | 1 |
|
||||
|
||||
There is **no museum warp**: the FAQ's "they even filled the doorway in" is
|
||||
literally true in the asm - the museum exists only as a sign.
|
||||
|
||||
**Coord events** - none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 25 | 23 | `BGEVENT_READ` | `PewterCitySign` |
|
||||
| 11 | 17 | `BGEVENT_READ` | `PewterGymSign` |
|
||||
| 15 | 9 | `BGEVENT_READ` | `PewterMuseumSign` ("closed for renovations") |
|
||||
| 33 | 19 | `BGEVENT_READ` | `PewterCityMtMoonGiftShopSign` |
|
||||
| 19 | 29 | `BGEVENT_READ` | `PewterCityWelcomeSign` |
|
||||
| 14 | 25 | `BGEVENT_READ` | `PewterCityPokecenterSign` (`jumpstd PokecenterSignScript`) |
|
||||
| 24 | 17 | `BGEVENT_READ` | `PewterCityMartSign` (`jumpstd MartSignScript`) |
|
||||
|
||||
No hidden items in Pewter City.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `PEWTERCITY_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 19 | 11 | `SPRITEMOVEDATA_WANDER` (2,2) | `OBJECTTYPE_SCRIPT` | `PewterCityCooltrainerFScript` | -1 |
|
||||
| `PEWTERCITY_BUG_CATCHER` | `SPRITE_BUG_CATCHER` | 14 | 29 | `SPRITEMOVEDATA_WANDER` (2,2) | `OBJECTTYPE_SCRIPT` | `PewterCityBugCatcherScript` | -1 |
|
||||
| `PEWTERCITY_GRAMPS` | `SPRITE_GRAMPS` | 29 | 17 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 2,0) | `OBJECTTYPE_SCRIPT` | `PewterCityGrampsScript` | -1 |
|
||||
| `PEWTERCITY_FRUIT_TREE1` | `SPRITE_FRUIT_TREE` | 32 | 3 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `PewterCityFruitTree1` (`fruittree FRUITTREE_PEWTER_CITY_1`) | -1 |
|
||||
| `PEWTERCITY_FRUIT_TREE2` | `SPRITE_FRUIT_TREE` | 30 | 3 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `PewterCityFruitTree2` (`fruittree FRUITTREE_PEWTER_CITY_2`) | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `PewterCityGrampsScript` (`4d:583e`). `faceplayer` / `opentext` / `checkver`
|
||||
/ `iftrue .RainbowWing`. `checkver` leaves `GS_VERSION` in `wScriptVar`
|
||||
(0 = Gold, 1 = Silver), so:
|
||||
- **Gold**: `checkevent EVENT_GOT_SILVER_WING` -> `.GotSilverWing`; otherwise
|
||||
text, `promptbutton`, `verbosegiveitem SILVER_WING`,
|
||||
`setevent EVENT_GOT_SILVER_WING`. Note there is **no `iffalse` guard** on
|
||||
this `verbosegiveitem`: the flag is set whether or not the bag had room.
|
||||
- **Silver**: `checkevent EVENT_GOT_RAINBOW_WING` -> `.GotSilverWing`;
|
||||
otherwise `verbosegiveitem RAINBOW_WING`, `setevent EVENT_GOT_RAINBOW_WING`,
|
||||
`setevent EVENT_TEAM_ROCKET_DISBANDED`.
|
||||
This gramps is the *second* source for the wing pair: the Radio Tower director
|
||||
(`maps/RadioTower5F.asm:120-140`) gives the opposite one on the same
|
||||
`checkver` split.
|
||||
- `PewterCityFlypointCallback` (`MAPCALLBACK_NEWMAP`):
|
||||
`setflag ENGINE_FLYPOINT_PEWTER`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_GOT_SILVER_WING` | `constants/event_flags.asm:130` | Gold branch of `PewterCityGrampsScript`; also `maps/RadioTower5F.asm:139` (Silver) | one-time |
|
||||
| `EVENT_GOT_RAINBOW_WING` | `constants/event_flags.asm:129` | Silver branch of `PewterCityGrampsScript`; also `maps/RadioTower5F.asm:129` (Gold); read by `maps/EcruteakTinTowerEntrance.asm:62` | one-time; gates Tin Tower entry |
|
||||
| `EVENT_TEAM_ROCKET_DISBANDED` | `constants/event_flags.asm:1283` | set by the Silver branch here | side effect of the Rainbow Wing gift |
|
||||
| `ENGINE_FLYPOINT_PEWTER` | `constants/engine_flags.asm:69` | `PewterCityFlypointCallback` | Fly destination |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `SILVER_WING` (Gold) / `RAINBOW_WING` (Silver) | talk to `PEWTERCITY_GRAMPS` at (29,17) | `PewterCityGrampsScript` | `EVENT_GOT_SILVER_WING` / `EVENT_GOT_RAINBOW_WING` |
|
||||
| `ICE_BERRY` | fruit tree at (32,3) | `FRUITTREE_PEWTER_CITY_1`, `data/items/fruit_trees.asm:31` | daily |
|
||||
| `MINT_BERRY` | fruit tree at (30,3) | `FRUITTREE_PEWTER_CITY_2`, `data/items/fruit_trees.asm:32` | daily |
|
||||
|
||||
**Wild encounters** - none (TOWN, no grass table).
|
||||
|
||||
---
|
||||
|
||||
### MAP_PEWTER_POKECENTER_1F
|
||||
|
||||
- Script: `maps/PewterPokecenter1F.asm`
|
||||
- Header: `data/maps/maps.asm:326` -> `TILESET_POKECENTER, INDOOR, LANDMARK_PEWTER_CITY, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:301` `map_const PEWTER_POKECENTER_1F, 5, 4`
|
||||
- Attributes: `data/maps/attributes.asm:583`
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 7 | `PEWTER_CITY` | 4 |
|
||||
| 2 | 4 | 7 | `PEWTER_CITY` | 4 |
|
||||
| 3 | 0 | 7 | `POKECENTER_2F` | 1 |
|
||||
|
||||
**Coord events** - none. **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `PEWTERPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PewterPokecenter1FNurseScript` (`jumpstd PokecenterNurseScript`) | -1 |
|
||||
| `PEWTERPOKECENTER1F_TEACHER` | `SPRITE_TEACHER` | 8 | 6 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PewterPokecenter1FTeacherScript` | -1 |
|
||||
| `PEWTERPOKECENTER1F_JIGGLYPUFF` | `SPRITE_JIGGLYPUFF` | 1 | 3 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT` | `PewterJigglypuff` | -1 |
|
||||
| `PEWTERPOKECENTER1F_BUG_CATCHER` | `SPRITE_BUG_CATCHER` | 2 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PewterPokecenter1FBugCatcherScript` | -1 |
|
||||
| `PEWTERPOKECENTER1F_CHRIS` | `SPRITE_POKEFAN_M` | 7 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Chris` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Chris` (`5a:46f5`): `faceplayer` / `opentext` / `trade NPC_TRADE_CHRIS` /
|
||||
`waitbutton` / `closetext` / `end`. The trade row is
|
||||
`data/events/npc_trades.asm:18`:
|
||||
`npctrade TRADE_DIALOGSET_HAPPY, GLOOM, RAPIDASH, "RUNNY", $96, $66,
|
||||
BURNT_BERRY, 15616, "CHRIS", TRADE_GENDER_EITHER` - the player gives GLOOM and
|
||||
receives a RAPIDASH nicknamed RUNNY, DVs `$96/$66`, holding a BURNT_BERRY, OT
|
||||
"CHRIS" ID 15616. The FAQ's "trade a Gloom for a Rapidash" is exact. Note the
|
||||
NPC sprite is `SPRITE_POKEFAN_M`, not a lady.
|
||||
|
||||
---
|
||||
|
||||
### MAP_PEWTER_GYM
|
||||
|
||||
- Script: `maps/PewterGym.asm`
|
||||
- Blocks: `maps/PewterGym.blk`
|
||||
- Header: `data/maps/maps.asm:324` -> `TILESET_TOWER, INDOOR, LANDMARK_PEWTER_CITY, MUSIC_GYM, TRUE (no phone), PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:299` `map_const PEWTER_GYM, 5, 7` (group `PEWTER`, id 4) = 10 x 14 cells
|
||||
- Attributes: `data/maps/attributes.asm:581` `map_attributes PewterGym, PEWTER_GYM, $00`; no connections
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 13 | `PEWTER_CITY` | 2 |
|
||||
| 2 | 5 | 13 | `PEWTER_CITY` | 2 |
|
||||
|
||||
**Coord events** - none. There is no lock-in trip-wire in this gym.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 2 | 11 | `BGEVENT_READ` | `PewterGymStatue` |
|
||||
| 7 | 11 | `BGEVENT_READ` | `PewterGymStatue` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `PEWTERGYM_BROCK` | `SPRITE_BROCK` | 5 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `PewterGymBrockScript` | -1 |
|
||||
| `PEWTERGYM_YOUNGSTER` | `SPRITE_YOUNGSTER` | 2 | 5 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerCamperJerry` | -1 |
|
||||
| `PEWTERGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 6 | 11 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` (sight 1) | `PewterGymGuideScript` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `PewterGymBrockScript` (`5a:405f`). `faceplayer` / `opentext` /
|
||||
`checkflag ENGINE_BOULDERBADGE` / `iftrue .FightDone`. Otherwise:
|
||||
`writetext BrockIntroText`, `waitbutton`, `closetext`,
|
||||
`winlosstext BrockWinLossText, 0`, `loadtrainer BROCK, BROCK1`,
|
||||
`startbattle`, `reloadmapafterbattle`, `setevent EVENT_BEAT_BROCK`,
|
||||
**`setevent EVENT_BEAT_CAMPER_JERRY`**, then `opentext` /
|
||||
`writetext ReceivedBoulderBadgeText` / `playsound SFX_GET_BADGE` / `waitsfx` /
|
||||
`setflag ENGINE_BOULDERBADGE` / `writetext BrockBoulderBadgeText` /
|
||||
`waitbutton` / `closetext` / `end`.
|
||||
Two consequences for a bot: (1) Camper Jerry is optional - beating Brock marks
|
||||
him beaten; (2) the badge is a `setflag` on the ENGINE namespace, not an
|
||||
`EVENT_*`.
|
||||
- `TrainerCamperJerry` (`5a:4093`): standard `trainer CAMPER, JERRY,
|
||||
EVENT_BEAT_CAMPER_JERRY, ...` header, sight range 3 facing right from (2,5).
|
||||
- `PewterGymGuideScript`: `checkevent EVENT_BEAT_BROCK`, two text branches.
|
||||
- `PewterGymStatue`: `checkflag ENGINE_BOULDERBADGE`; unbeaten -> `jumpstd
|
||||
GymStatue1Script`, beaten -> `gettrainername STRING_BUFFER_4, BROCK, BROCK1`
|
||||
then `jumpstd GymStatue2Script`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_BOULDERBADGE` | `constants/engine_flags.asm:47` (row `data/events/engine_flags.asm`, backed by `wKantoBadges`) | checked and set by `PewterGymBrockScript`; checked by `PewterGymStatue` | the gym's own re-entry guard, and the ROCK-type damage boost via `BadgeTypeBoosts` |
|
||||
| `EVENT_BEAT_BROCK` | `constants/event_flags.asm:715` | set by `PewterGymBrockScript`; read by `PewterGymGuideScript` | gym cleared |
|
||||
| `EVENT_BEAT_CAMPER_JERRY` | `constants/event_flags.asm:545` | `TrainerCamperJerry`, and also set by `PewterGymBrockScript` | trainer beaten / skipped |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `JERRY` (`constants/trainer_constants.asm:566`) | `CAMPER` | 18 | `CamperGroup` "JERRY" (`data/trainers/parties.asm:2811`): L37 SANDSLASH, `TRAINERTYPE_NORMAL` | `TrainerCamperJerry` | no |
|
||||
| `BROCK1` (`constants/trainer_constants.asm:91`, class `BROCK` = 11 at line 90) | `BROCK` | 1 | `BrockGroup` (`data/trainers/parties.asm:270`, symbol `0e:5cda`), `TRAINERTYPE_MOVES` | `PewterGymBrockScript` | no phone |
|
||||
|
||||
`BrockGroup` party, in asm order:
|
||||
|
||||
| # | level | species | moves |
|
||||
|---|---|---|---|
|
||||
| 1 | 41 | GRAVELER | DEFENSE_CURL, ROCK_SLIDE, ROLLOUT, EARTHQUAKE |
|
||||
| 2 | 41 | RHYHORN | FURY_ATTACK, SCARY_FACE, EARTHQUAKE, HORN_DRILL |
|
||||
| 3 | 42 | OMASTAR | BITE, SURF, PROTECT, SPIKE_CANNON |
|
||||
| 4 | 44 | ONIX | BIND, ROCK_SLIDE, BIDE, SANDSTORM |
|
||||
| 5 | 42 | KABUTOPS | SLASH, SURF, ENDURE, GIGA_DRAIN |
|
||||
|
||||
Class attributes (`data/trainers/attributes.asm:101`): item1 `HYPER_POTION`,
|
||||
base reward 25, AI `AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE |
|
||||
AI_CAUTIOUS | AI_STATUS | AI_RISKY`, `CONTEXT_USE | SWITCH_SOMETIMES`. Camper
|
||||
base reward 5 (`data/trainers/attributes.asm:323`).
|
||||
|
||||
**Wild encounters** - none.
|
||||
|
||||
---
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Route 19 boulders | `maps/Route19.asm:Route19ClearRocksCallback` (`4e:4f09`), six `changeblock ... $7a` writes under `callback MAPCALLBACK_TILES` | `EVENT_CINNABAR_ROCKS_CLEARED` | Set only by `maps/Route20.asm:Route20ClearRocksCallback` (`MAPCALLBACK_NEWMAP`) - i.e. by reaching Route 20 from the Cinnabar side. Nothing in this section can clear it; the FAQ is right that you must turn around. |
|
||||
| SNORLAX blocking the DIGLETT'S CAVE door | `maps/VermilionCity.asm` object at (34,8) with a `SPRITEMOVEDATA_BIGDOLLSYM` 2x2 footprint sitting directly under warp 10 at (34,7) | `EVENT_VERMILION_CITY_SNORLAX` clear = present | `disappear VERMILIONCITY_BIG_SNORLAX` at the end of `VermilionSnorlax`, which only runs after `startbattle` |
|
||||
| SNORLAX will not wake | `engine/events/specials.asm:358 SnorlaxAwake` | `wMapMusic == MUSIC_POKE_FLUTE_CHANNEL` **and** player on one of (33,8), (34,10), (35,10), (36,8), (36,9) | Pokegear radio tuned to knob 78 / "20.0" |
|
||||
| POKe FLUTE radio channel is static | `engine/pokegear/pokegear.asm:1487 .PokeFluteRadio` | not in Johto (`.InJohto`, landmark >= `KANTO_LANDMARK`) **and** `POKEGEAR_EXPN_CARD_F` set in `wPokegearFlags` | EXPN CARD, obtained earlier (Lavender radio director) |
|
||||
| Route 2 cut trees | tileset collision `COLL_CUT_TREE` (`constants/collision_constants.asm:16`), tested by `CheckCutCollision` (`engine/overworld/tile_events.asm:76`) | HM01 CUT + `ENGINE_HIVEBADGE` (`engine/events/overworld.asm:133 CutFunction.CheckAble`) | already held long before this section |
|
||||
| Diglett's Cave traversal | `maps/DiglettsCave.blk` geometry - the three pockets are joined only by warps 2<->5 and 6<->4 | none beyond walking | - |
|
||||
| Pewter Gym re-fight | `maps/PewterGym.asm:PewterGymBrockScript` `checkflag ENGINE_BOULDERBADGE / iftrue .FightDone` | badge not yet held | one-shot |
|
||||
| Pewter museum | no warp exists in `PewterCity_MapEvents`; only `bg_event 15, 9 PewterMuseumSign` | - | permanently closed in GS |
|
||||
|
||||
---
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
Coordinates are asm cell coordinates. "clear" / "set" refer to event flags.
|
||||
|
||||
1. `ROUTE_19_FUCHSIA_GATE` -> `ROUTE_19`: walk warp 3/4 at (4,7)/(5,7).
|
||||
Precondition: none. Expect to be stopped; `EVENT_CINNABAR_ROCKS_CLEARED` is
|
||||
clear, so blocks `(6,6) (8,6) (10,6) (12,8) (4,8) (10,10)` are rock. Turn
|
||||
around through warp 1 at (7,3).
|
||||
2. FLY to `VERMILION_CITY`. Precondition: `ENGINE_FLYPOINT_VERMILION` set.
|
||||
3. Open the Pokegear, RADIO card, tune the knob to 78 ("20.0"). Precondition:
|
||||
`POKEGEAR_EXPN_CARD_F` set and the landmark is Kanto. Postcondition:
|
||||
`wMapMusic == MUSIC_POKE_FLUTE_CHANNEL`.
|
||||
4. Close the Pokegear and walk to one of (33,8) / (34,10) / (35,10) / (36,8) /
|
||||
(36,9). Face `VERMILIONCITY_BIG_SNORLAX` at (34,8) and press A.
|
||||
Precondition: `EVENT_VERMILION_CITY_SNORLAX` clear.
|
||||
Effect: `SnorlaxAwake` returns TRUE -> wild L50 SNORLAX with LEFTOVERS forced.
|
||||
5. Battle: catch or defeat. Either way, postcondition
|
||||
`EVENT_VERMILION_CITY_SNORLAX` set (object removed) and
|
||||
`EVENT_FOUGHT_SNORLAX` set. **Save before step 4** - a failed catch is not
|
||||
repeatable.
|
||||
6. Optional: `VERMILION_CITY` warp 2 at (9,5) -> `VERMILION_POKECENTER_1F`, heal
|
||||
and withdraw SNORLAX; back out warps 1/2 at (3,7)/(4,7).
|
||||
7. `VERMILION_CITY` warp 10 at (34,7) -> `DIGLETTS_CAVE` warp 1 at (3,33).
|
||||
8. `DIGLETTS_CAVE`: walk (3,33) -> (5,31) (warp 2, ladder). Land at (17,33).
|
||||
9. Walk the long cave (17,33) -> (3,3) (warp 6, ladder). Land at (17,3).
|
||||
Optional en route: hidden `MAX_REVIVE` at (6,11),
|
||||
flag `EVENT_DIGLETTS_CAVE_HIDDEN_MAX_REVIVE`.
|
||||
10. Walk (17,3) -> (15,5) (warp 3, door) -> `ROUTE_2` warp 5 at (12,7).
|
||||
11. `ROUTE_2`: item ball `ROUTE2_POKE_BALL3` at (19,2) = CARBOS.
|
||||
Precondition `EVENT_ROUTE_2_CARBOS` clear; postcondition set.
|
||||
12. Warp 1 at (15,15) -> `ROUTE_2_NUGGET_HOUSE`. Talk to
|
||||
`ROUTE2NUGGETHOUSE_FISHER` at (2,4). Precondition
|
||||
`EVENT_GOT_NUGGET_FROM_GUY` clear and >=1 bag slot; postcondition set,
|
||||
NUGGET in bag. Exit warps 1/2 at (2,7)/(3,7).
|
||||
13. Optional: fruit tree `ROUTE2_FRUIT_TREE` at (10,14) = PSNCUREBERRY (daily).
|
||||
14. CUT the tree covering cells (14-15, 18-19). Precondition: HM01 in bag and
|
||||
`ENGINE_HIVEBADGE` set.
|
||||
15. Warp 3/4 at (16,27)/(17,27) -> `ROUTE_2_GATE` -> warps 3/4 at (4,7)/(5,7) ->
|
||||
`ROUTE_2` warp 2 at (15,31). Still Route 2.
|
||||
16. Item ball `ROUTE2_POKE_BALL4` at (14,50) = ELIXER.
|
||||
Flag `EVENT_ROUTE_2_ELIXER`.
|
||||
17. CUT the tree covering cells (12-13, 50-51), then head north.
|
||||
18. Battle `TrainerBugCatcherRob` - object at (10,45), facing left, sight 5.
|
||||
Party L32 BEEDRILL / L32 BUTTERFREE. Postcondition
|
||||
`EVENT_BEAT_BUG_CATCHER_ROB`.
|
||||
19. Battle `TrainerBugCatcherDoug` - object at (4,43), facing up, sight 5.
|
||||
Party L34 ARIADOS. Postcondition `EVENT_BEAT_BUG_CATCHER_DOUG`.
|
||||
20. Optional pickups on the way north: item ball at (0,29) = DIRE_HIT
|
||||
(`EVENT_ROUTE_2_DIRE_HIT`); item ball at (2,23) = MAX_POTION
|
||||
(`EVENT_ROUTE_2_MAX_POTION`); hidden MAX_ETHER at (7,23), hidden FULL_HEAL
|
||||
at (4,14), hidden FULL_RESTORE at (4,27), hidden REVIVE at (11,30).
|
||||
21. Battle `TrainerBugCatcherEd` - object at (5,5), facing right, sight 4.
|
||||
Party L30 BEEDRILL x3. Postcondition `EVENT_BEAT_BUG_CATCHER_ED`.
|
||||
22. Walk off the north edge of `ROUTE_2` into `PEWTER_CITY`
|
||||
(`connection north, PewterCity, PEWTER_CITY, -5`). Postcondition
|
||||
`ENGINE_FLYPOINT_PEWTER` set by `PewterCityFlypointCallback`.
|
||||
23. Talk to `PEWTERCITY_GRAMPS` at (29,17) (he paces x 27..31 on y=17).
|
||||
Precondition `EVENT_GOT_SILVER_WING` clear (Gold).
|
||||
Postcondition: SILVER_WING in bag, `EVENT_GOT_SILVER_WING` set.
|
||||
On Silver: RAINBOW_WING, `EVENT_GOT_RAINBOW_WING` and
|
||||
`EVENT_TEAM_ROCKET_DISBANDED` set.
|
||||
24. Optional: warp 4 at (13,25) -> `PEWTER_POKECENTER_1F`. Heal at
|
||||
`PEWTERPOKECENTER1F_NURSE` (3,1). Talk to `Chris` at (7,2) to trade a GLOOM
|
||||
for RAPIDASH "RUNNY". Optional: fruit trees at (32,3) = ICE_BERRY and
|
||||
(30,3) = MINT_BERRY.
|
||||
25. `PEWTER_CITY` warp 2 at (16,17) -> `PEWTER_GYM` (arrive at warp 1, (4,13)).
|
||||
26. Optional: battle `TrainerCamperJerry`, object at (2,5), facing right,
|
||||
sight 3. L37 SANDSLASH.
|
||||
27. Talk to `PEWTERGYM_BROCK` at (5,1). Precondition `ENGINE_BOULDERBADGE`
|
||||
clear. Battle `loadtrainer BROCK, BROCK1` (5 mon, L41-44).
|
||||
Postconditions: `EVENT_BEAT_BROCK`, `EVENT_BEAT_CAMPER_JERRY`,
|
||||
`ENGINE_BOULDERBADGE`.
|
||||
28. Exit warps 1/2 at (4,13)/(5,13) -> `PEWTER_CITY` warp 2; heal.
|
||||
|
||||
---
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| All maps / warps / objects / bg events for this section | `src/import/RomExtractorGen2.lua` (`MAP_GROUP_COUNT = 26`, line 47; the group walk at line 527) | implemented - every Kanto group is extracted, so these maps are data, not hand-ported code |
|
||||
| `MAPCALLBACK_TILES` + `changeblock` (Route 19 boulders) | `src/script/gen2/Opcodes.lua:127` (`changeblock`), driver `tests/drivers/gold_map_callbacks.lua` | implemented |
|
||||
| `special SnorlaxAwake` | `src/script/gen2/Specials.lua:1562` `H.SnorlaxAwake`, with `Specials.SNORLAX_PROXIMITY` transcribing `.ProximityCoords` and `Specials.POKE_FLUTE_SONG = "Music_PokeFluteChannel"`; hooks `currentMusic` / `playerCell` at `src/world/gen2/World.lua:2712` and `:2723` | implemented |
|
||||
| POKe FLUTE radio channel (knob 78 / 20.0) + EXPN CARD gate | `src/ui/gen2/Pokegear.lua:812-815` (the `knob = 78` row, `ctx.inJohto` / `ctx.expnCard`) | implemented |
|
||||
| `BATTLETYPE_FORCEITEM` -> Snorlax holds LEFTOVERS | `src/world/gen2/World.lua:111` and `:4576` | implemented |
|
||||
| `loadwildmon` / `startbattle` / `reloadmapafterbattle` / `disappear` | `src/script/gen2/Vm.lua:806-841` | implemented |
|
||||
| Catching a wild mon (Ultra Balls on Snorlax) | `src/battle/gen2/Catching.lua` | implemented |
|
||||
| Trainer battles from `OBJECTTYPE_TRAINER` rows (Rob / Doug / Ed / Jerry) | `src/world/gen2/Trainers.lua`, `src/script/gen2/Vm.lua:806` (`loadtrainer`) | implemented |
|
||||
| Gym leader script shape (`checkflag`/`winlosstext`/`setflag ENGINE_BOULDERBADGE`) | `src/script/gen2/Vm.lua:208` (`setflag`/`clearflag`), `src/world/gen2/World.lua:1296-1340` (`engineFlags`) | implemented |
|
||||
| `BadgeTypeBoosts` - BOULDERBADGE boosting ROCK moves (`data/types/badge_type_boosts.asm`, `engine/battle/misc.asm:146 DoBadgeTypeBoosts`) | no match anywhere in `src/battle/gen2/` | **missing** (the Gen 1 port has its own badge table in `src/battle/Damage.lua:28`, but nothing in the Gen 2 battle path reads badges) |
|
||||
| Item ball pickup (Route 2 CARBOS / DIRE_HIT / ELIXER / MAX_POTION) | extracted into `obj.itemball` at `src/import/RomExtractorGen2.lua:2874` and `:2968`, but no consumer exists in `src/world/gen2/`; `src/script/gen2/CallAsm.lua:550` stubs `TryReceiveItem` with "wItemBallItemID / wItemBallQuantity are set by a script path this port does not run" | **missing** |
|
||||
| Hidden items (Diglett's Cave MAX_REVIVE, the four Route 2 hidden items, Vermilion FULL_HEAL) | `src/world/gen2/HiddenItems.lua`, wired at `src/world/gen2/World.lua:5291` and the ITEMFINDER path at `:3423` | implemented |
|
||||
| `verbosegiveitem` (NUGGET, SILVER_WING/RAINBOW_WING) | `src/script/gen2/Opcodes.lua:163`, `src/script/gen2/Vm.lua:501` / `:1201` | implemented |
|
||||
| `fruittree` (PSNCUREBERRY, ICE_BERRY, MINT_BERRY) | `src/script/gen2/Opcodes.lua:160`, `src/world/gen2/World.lua:1058` (`fruitTreeItem`) | implemented |
|
||||
| `trade NPC_TRADE_CHRIS` (GLOOM -> RAPIDASH) | `src/core/gen2/NpcTrade.lua`, `src/script/gen2/Opcodes.lua:155` | implemented |
|
||||
| `checkver` Gold/Silver split in `PewterCityGrampsScript` | `src/script/gen2/Vm.lua:774`, `src/world/gen2/World.lua:1287` (`gsVersion`) | implemented |
|
||||
| `setflag ENGINE_FLYPOINT_PEWTER` / `_VERMILION` and Fly | `src/world/gen2/World.lua:5628` | implemented |
|
||||
| CUT field move + HIVEBADGE check | `src/world/gen2/FieldMoves.lua` (badge table at lines 100-101) | implemented |
|
||||
| Bike allowed in `CAVE` (Diglett's Cave) | `src/world/gen2/Bike.lua` | partial - the file exists and comments the `hiddenitem`/BICYCLE interaction, but the `.CheckEnvironment` outdoor/CAVE/GATE test was not located; verify before relying on it |
|
||||
| `gettrainername` for the beaten-gym statue | `src/script/gen2/Vm.lua:407` | implemented |
|
||||
|
||||
---
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
- **Brock's party order.** The FAQ lists Graveler, Omastar, Rhyhorn, Onix,
|
||||
Kabutops. `data/trainers/parties.asm:270` `BrockGroup` is Graveler (41),
|
||||
**Rhyhorn (41), Omastar (42)**, Onix (44), Kabutops (42) - slots 2 and 3 are
|
||||
swapped relative to the FAQ. Levels and species set match.
|
||||
- **All the FAQ's prize money figures are exactly 4x the asm value.**
|
||||
`ComputeTrainerReward` (`engine/battle/read_trainer_party.asm:300`) is
|
||||
`base reward x last mon level`, with no multiplier. Brock 25 x 42 = 1050 (FAQ
|
||||
says 4,200); Rob 4 x 32 = 128 (FAQ 512); Doug 4 x 34 = 136 (FAQ 544); Ed
|
||||
4 x 30 = 120 (FAQ 480); Jerry 5 x 37 = 185 (FAQ 740). The FAQ's EXP figures
|
||||
likewise do not come from GS. Treat the money/EXP numbers in this section as
|
||||
belonging to a different game (most likely HeartGold/SoulSilver, which the
|
||||
text references) and ignore them for a GS bot.
|
||||
- **"Viridian Forest".** The FAQ says the Max Potion is "in Viridian Forest".
|
||||
There is no Viridian Forest map in `constants/map_constants.asm`; the
|
||||
MAX_POTION is `ROUTE2_POKE_BALL2` at (2,23) on `ROUTE_2`, in the area the
|
||||
forest used to occupy.
|
||||
- **"the man just to the right of the Pokemart".** `PEWTERCITY_GRAMPS` spawns at
|
||||
(29,17) with a `WALK_LEFT_RIGHT` radius of 2 (so x 27..31); the mart door is
|
||||
warp 3 at (23,17). He is to the right of the mart but not adjacent.
|
||||
- **"The Pokemon Center has a lady".** The trade NPC is `Chris`, sprite
|
||||
`SPRITE_POKEFAN_M` at (7,2). Male sprite, name CHRIS.
|
||||
- **Which cut tree the FAQ means.** The two trees named in step 14 and step 17
|
||||
above are inferred from the block dump of `maps/Route2.blk` plus the item/warp
|
||||
positions; they are not labelled anywhere in asm. Three other `CUT_TREE`
|
||||
blocks exist on Route 2 (see the cut-tree table) and one of those may be the
|
||||
intended route instead. Verify with a real walk.
|
||||
- **Diglett's Cave pocket connectivity.** The three-pocket reading is derived by
|
||||
hand from `maps/DiglettsCave.blk` block ids against
|
||||
`data/tilesets/kanto_collision.asm`; the per-tile collision of the "long cave"
|
||||
interior was not fully traced, only that every warp block is reachable within
|
||||
its pocket. The FAQ's described route is consistent with it.
|
||||
- **"the route is cleared to Diglett's Cave and through Route 8".** Nothing in
|
||||
`maps/VermilionCity.asm` or `maps/Route11.asm` gates Route 11 / Route 8 on the
|
||||
Snorlax; the only thing `EVENT_FOUGHT_SNORLAX` unlocks outside Vermilion is the
|
||||
right-hand Black Belt object in `maps/VictoryRoadGate.asm:119`. The Snorlax
|
||||
blocks only the Diglett's Cave door tile at (34,7).
|
||||
- **"Keep around those four Ultra Balls"** and the Rest/Thunder Wave catch
|
||||
advice are strategy, not asm; nothing enforces a ball count.
|
||||
- **`src/world/gen2/Bike.lua` CAVE permission.** Marked partial in section 5 -
|
||||
the port file was read but the equivalent of `BikeFunction.CheckEnvironment`
|
||||
(`engine/events/overworld.asm:1665`) was not located, so "you can bike through
|
||||
Diglett's Cave in the port" is unverified.
|
||||
@@ -0,0 +1,987 @@
|
||||
# Section 25 - Lugia (Gold) and Ho-Oh (Silver)
|
||||
|
||||
Source: `../section-25-lugia-gold-and-ho-oh-silver.txt`
|
||||
Maps covered: `ROUTE_41`, `WHIRL_ISLAND_NE`, `WHIRL_ISLAND_NW`, `WHIRL_ISLAND_SW`,
|
||||
`WHIRL_ISLAND_SE`, `WHIRL_ISLAND_CAVE`, `WHIRL_ISLAND_B1F`, `WHIRL_ISLAND_B2F`,
|
||||
`WHIRL_ISLAND_LUGIA_CHAMBER`, `ECRUTEAK_CITY`, `ECRUTEAK_TIN_TOWER_ENTRANCE`,
|
||||
`ECRUTEAK_TIN_TOWER_BACK_ENTRANCE`, `TIN_TOWER_1F` .. `TIN_TOWER_9F`,
|
||||
`TIN_TOWER_ROOF`
|
||||
Badges / key milestones in this section: no badges. The milestone is catching the
|
||||
*other* version mascot post-Elite-Four - `EVENT_FOUGHT_LUGIA` /
|
||||
`EVENT_FOUGHT_HO_OH`, armed by the wing the Pewter City gramps hands over
|
||||
(`maps/PewterCity.asm`, `PewterCityGrampsScript`).
|
||||
|
||||
This section is two mutually exclusive branches. The walkthrough labels them
|
||||
"Whirl Islands - Gold Version" and "Tin Tower - Silver Version", and the asm
|
||||
agrees: `Script_checkver` (`engine/overworld/scripting.asm:1471`) returns
|
||||
`GS_VERSION` (`constants/misc_constants.asm:21-23`: 0 = Gold, 1 = Silver), so on
|
||||
each mascot the `checkver / iftrue .Silver` pair picks the level. In Gold, Lugia
|
||||
is **level 70** and Ho-Oh was already caught at level 40; in Silver it is the
|
||||
mirror image.
|
||||
|
||||
---
|
||||
|
||||
## 1. Route order
|
||||
|
||||
The walkthrough opens on the Magnet Train back to Johto - that hop belongs to the
|
||||
neighbouring Kanto section, and the only thing this section needs from it is that
|
||||
the player already holds the version-opposite wing.
|
||||
|
||||
**Gold branch (Lugia, Whirl Islands)**
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `OLIVINE_CITY` | (fly target) | Fly | west connection to `ROUTE_40` (`data/maps/attributes.asm:145`) | "Fly to Olivine City" |
|
||||
| 2 | `ROUTE_40` | `data/maps/attributes.asm:223` | east connection from Olivine | south connection to `ROUTE_41` (`attributes.asm:224`) | surf southwest |
|
||||
| 3 | `ROUTE_41` | `maps/Route41.asm` | north connection from Route 40 | `warp_event 36, 19` (warp 2) | "surf southwest and west until you reach the northeast cave", clearing whirlpools |
|
||||
| 4 | `WHIRL_ISLAND_NE` | `maps/WhirlIslandNE.asm` | warp 1 `(3, 13)` | warp 2 `(17, 3)` or warp 3 `(13, 11)` | Flash, ledges, `ULTRA_BALL` at `(11, 11)` |
|
||||
| 5 | `WHIRL_ISLAND_B1F` | `maps/WhirlIslandB1F.asm` | warp 2 `(35, 3)` / warp 3 `(29, 9)` | warp 7 `(25, 21)` or warp 8 `(13, 27)` | ledge chain, `ESCAPE_ROPE` at `(19, 26)`, `CALCIUM` at `(33, 23)` |
|
||||
| 6 | `WHIRL_ISLAND_CAVE` | `maps/WhirlIslandCave.asm` | B1F warp 9 `(17, 21)` -> Cave warp 1 `(7, 5)` | Cave warp 2 `(3, 13)` -> `WHIRL_ISLAND_NW` | "head out the door to breathe, but that's pointless" |
|
||||
| 7 | `WHIRL_ISLAND_SW` | `maps/WhirlIslandSW.asm` | B1F warp 4 `(9, 31)` / warp 5 `(23, 31)` | warp 5 `(17, 15)` -> B2F warp 4 | `GUARD_SPEC` at `(15, 2)` |
|
||||
| 8 | `WHIRL_ISLAND_B2F` | `maps/WhirlIslandB2F.asm` | warp 1 `(11, 5)` from B1F warp 7 | warp 3 `(7, 25)` | `MAX_REVIVE` `(6, 4)`, waterfall descent, "head left and through the door" |
|
||||
| 9 | `WHIRL_ISLAND_LUGIA_CHAMBER` | `maps/WhirlIslandLugiaChamber.asm` | warp 1 `(9, 13)` | Escape Rope (environment is `CAVE`) | surf north to Lugia at `(9, 5)` |
|
||||
|
||||
**Silver branch (Ho-Oh, Tin Tower)**
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `ECRUTEAK_CITY` | `maps/EcruteakCity.asm` | Fly | warp 3 `(18, 11)` | "Go back to Ecruteak City" |
|
||||
| 2 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | `maps/EcruteakTinTowerEntrance.asm` | warp 1 `(4, 17)` / warp 2 `(5, 17)` | warp 3 `(5, 3)` -> warp 4 `(17, 15)`, then warp 5 `(17, 3)` | "the Bell Tower entry house. Talk to the man" |
|
||||
| 3 | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | `maps/EcruteakTinTowerBackEntrance.asm` | warp 3 `(2, 4)` | warp 1 `(7, 4)` / warp 2 `(7, 5)` -> `ECRUTEAK_CITY` warps 4/5 `(20, 2)` `(20, 3)` | "follow up the trail to the tower itself" |
|
||||
| 4 | `ECRUTEAK_CITY` (north strip) | `maps/EcruteakCity.asm` | warps 4/5 | warp 12 `(37, 7)` | walk east along the fenced strip to Tin Tower |
|
||||
| 5 | `TIN_TOWER_1F` | `maps/TinTower1F.asm` | warp 1 `(9, 15)` / warp 2 `(10, 15)` | warp 3 `(10, 2)` | "another bald man will let you through" - the sage occupies the stair tile |
|
||||
| 6 | `TIN_TOWER_2F` | `maps/TinTower2F.asm` | warp 2 `(10, 2)` | warp 1 `(10, 14)` | "wild Rattatas will start converging on you" |
|
||||
| 7 | `TIN_TOWER_3F` | `maps/TinTower3F.asm` | warp 1 `(10, 14)` | warp 2 `(16, 2)` | `FULL_HEAL` at `(3, 14)`, jump-ledge puzzle |
|
||||
| 8 | `TIN_TOWER_4F` | `maps/TinTower4F.asm` | warp 2 `(16, 2)` | warps 1 `(2, 4)`, 3 `(2, 14)`, 4 `(17, 15)` | `ULTRA_BALL`, `SUPER_POTION`, `ESCAPE_ROPE` |
|
||||
| 9 | `TIN_TOWER_5F` | `maps/TinTower5F.asm` | warps 2/3/4 | warp 1 `(11, 15)` | `RARE_CANDY` at `(9, 9)` |
|
||||
| 10 | `TIN_TOWER_6F` | `maps/TinTower6F.asm` | warp 2 `(11, 15)` | warp 1 `(3, 9)` | "go up and you'll get a Max Potion" (see Unresolved) |
|
||||
| 11 | `TIN_TOWER_7F` | `maps/TinTower7F.asm` | warp 1 `(3, 9)` | warp 2 `(10, 15)`, plus in-floor warp pair 3/4 and warp 5 to 9F | `MAX_REVIVE` at `(16, 1)`; "go through the next two warps" |
|
||||
| 12 | `TIN_TOWER_8F` | `maps/TinTower8F.asm` | warp 1 `(2, 5)` | warps 2-6 into 9F | `NUGGET`, `MAX_ELIXER`, `FULL_RESTORE` |
|
||||
| 13 | `TIN_TOWER_9F` | `maps/TinTower9F.asm` | warps 1/2/3/5/6/7 from 7F+8F | warp 4 `(7, 9)` | "cross the planks down, then go up the ladder" |
|
||||
| 14 | `TIN_TOWER_ROOF` | `maps/TinTowerRoof.asm` | warp 1 `(9, 13)` | back down warp 1 then Escape Rope | level-70 Ho-Oh at `(9, 5)` |
|
||||
|
||||
The walkthrough's closing line ("ride the Magnet Train back to Kanto and fly back
|
||||
to Pewter City") hands off to the next section; nothing on that hop is resolved
|
||||
here.
|
||||
|
||||
---
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### ROUTE_41
|
||||
|
||||
- Script: `maps/Route41.asm`
|
||||
- Blocks: `maps/Route41.blk`
|
||||
- Header: `data/maps/maps.asm:443` -> `TILESET_JOHTO`, `ROUTE`, `LANDMARK_ROUTE_41`, `MUSIC_ROUTE_36`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_OCEAN`
|
||||
- Dimensions: `constants/map_constants.asm:410` -> `map_const ROUTE_41, 25, 27` (group 22 `CIANWOOD`, map 2)
|
||||
- Connections (`data/maps/attributes.asm:227-229`): north `Route40`, west `CianwoodCity`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 12 | 17 | `WHIRL_ISLAND_NW` | 1 |
|
||||
| 2 | 36 | 19 | `WHIRL_ISLAND_NE` | 1 |
|
||||
| 3 | 12 | 37 | `WHIRL_ISLAND_SW` | 1 |
|
||||
| 4 | 36 | 45 | `WHIRL_ISLAND_SE` | 1 |
|
||||
|
||||
**Coord events**: none.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 9 | 35 | `BGEVENT_ITEM` | `Route41HiddenMaxEther` -> `hiddenitem MAX_ETHER, EVENT_ROUTE_41_HIDDEN_MAX_ETHER` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE41_OLIVINE_RIVAL1` | `SPRITE_OLIVINE_RIVAL` | 32 | 6 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (sight 3) | `TrainerSwimmermCharlie` | -1 |
|
||||
| `ROUTE41_OLIVINE_RIVAL2` | `SPRITE_OLIVINE_RIVAL` | 46 | 8 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (3) | `TrainerSwimmermGeorge` | -1 |
|
||||
| `ROUTE41_OLIVINE_RIVAL3` | `SPRITE_OLIVINE_RIVAL` | 20 | 26 | `SPINCOUNTERCLOCKWISE` | `OBJECTTYPE_TRAINER` (3) | `TrainerSwimmermBerke` | -1 |
|
||||
| `ROUTE41_OLIVINE_RIVAL4` | `SPRITE_OLIVINE_RIVAL` | 32 | 30 | `SPINCLOCKWISE` | `OBJECTTYPE_TRAINER` (3) | `TrainerSwimmermKirk` | -1 |
|
||||
| `ROUTE41_OLIVINE_RIVAL5` | `SPRITE_OLIVINE_RIVAL` | 19 | 46 | `SPINCOUNTERCLOCKWISE` | `OBJECTTYPE_TRAINER` (3) | `TrainerSwimmermMathew` | -1 |
|
||||
| `ROUTE41_SWIMMER_GIRL1` | `SPRITE_SWIMMER_GIRL` | 17 | 4 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (3) | `TrainerSwimmerfKaylee` | -1 |
|
||||
| `ROUTE41_SWIMMER_GIRL2` | `SPRITE_SWIMMER_GIRL` | 23 | 19 | `STANDING_UP` | `OBJECTTYPE_TRAINER` (3) | `TrainerSwimmerfSusie` | -1 |
|
||||
| `ROUTE41_SWIMMER_GIRL3` | `SPRITE_SWIMMER_GIRL` | 27 | 34 | `STANDING_LEFT` | `OBJECTTYPE_TRAINER` (3) | `TrainerSwimmerfDenise` | -1 |
|
||||
| `ROUTE41_SWIMMER_GIRL4` | `SPRITE_SWIMMER_GIRL` | 44 | 28 | `STANDING_RIGHT` | `OBJECTTYPE_TRAINER` (4) | `TrainerSwimmerfKara` | -1 |
|
||||
| `ROUTE41_SWIMMER_GIRL5` | `SPRITE_SWIMMER_GIRL` | 9 | 50 | `SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` (2) | `TrainerSwimmerfWendy` | -1 |
|
||||
|
||||
Note the sight ranges: Kara is `4`, the widest on the route, which is why the
|
||||
walkthrough uses her as the landmark for the northeast whirlpool ("the whirlpool
|
||||
is just northwest of where you fought Swimmer Kara"). Her object sits at
|
||||
`(44, 28)`; the trainer flag is `EVENT_BEAT_SWIMMERF_KARA`, so once beaten she is
|
||||
still standing there (`event flag` on the row is `-1`, i.e. never masked) and
|
||||
still walks into your path - the walkthrough's joke about swimmer stamina is
|
||||
literally the object table.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `TrainerSwimmerfKara` - `trainer SWIMMERF, KARA, EVENT_BEAT_SWIMMERF_KARA, ...`.
|
||||
Post-battle it only prints `SwimmerfKaraAfterBattleText` ("I heard roars from
|
||||
deep inside the ISLANDS"). No flag beyond the beat flag, no gate.
|
||||
|
||||
**Whirlpools**: they are *map blocks*, not events. `data/collision/field_move_blocks.asm:48-55`
|
||||
gives `WhirlpoolBlockPointers` -> `TILESET_JOHTO` -> `db $07, $36, 0` (facing
|
||||
block `$07`, replacement `$36`, animation type 0). A bot that wants the whirlpool
|
||||
coordinates has to scan `maps/Route41.blk` for block id `$07`; the collision
|
||||
value under it is `COLL_WHIRLPOOL` `$24` (`constants/collision_constants.asm:27`,
|
||||
tested by `CheckWhirlpoolTile`, `home/map_objects.asm:176-183`).
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
- Surfing, `data/wild/johto_water.asm:170-181`, `def_water_wildmons ROUTE_41`,
|
||||
encounter rate `6 percent`. Gold: `20 TENTACOOL`, `20 TENTACRUEL`, `20 MANTINE`.
|
||||
Silver: `20 TENTACOOL`, `20 TENTACRUEL`, `15 TENTACOOL`.
|
||||
- Fishing: header fish group is `FISHGROUP_OCEAN` (`data/wild/fish.asm:13`,
|
||||
`.Ocean_Old/.Ocean_Good/.Ocean_Super`).
|
||||
- The walkthrough's Max Repel advice is aimed squarely at this 6% table.
|
||||
|
||||
---
|
||||
|
||||
### WHIRL_ISLAND_NE
|
||||
|
||||
- Script: `maps/WhirlIslandNE.asm`
|
||||
- Blocks: `maps/WhirlIslandNE.blk`
|
||||
- Header: `data/maps/maps.asm:137` -> `TILESET_DARK_CAVE`, `CAVE`, `LANDMARK_WHIRL_ISLANDS`, `MUSIC_UNION_CAVE`, phone `TRUE`, `PALETTE_DARK`, `FISHGROUP_WHIRL_ISLANDS`
|
||||
- Dimensions: `constants/map_constants.asm:124` -> `map_const WHIRL_ISLAND_NE, 10, 9` (group 3 `DUNGEONS`, map 59)
|
||||
- Connections: none (island interior).
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 13 | `ROUTE_41` | 2 |
|
||||
| 2 | 17 | 3 | `WHIRL_ISLAND_B1F` | 2 |
|
||||
| 3 | 13 | 11 | `WHIRL_ISLAND_B1F` | 3 |
|
||||
|
||||
**Coord events**: none. **BG events**: none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `WHIRLISLANDNE_POKE_BALL` | `SPRITE_POKE_BALL` | 11 | 11 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandNEUltraBall` (`47:401e`) | `EVENT_WHIRL_ISLAND_NE_ULTRA_BALL` |
|
||||
|
||||
`WhirlIslandNEUltraBall` is a bare `itemball ULTRA_BALL`.
|
||||
|
||||
---
|
||||
|
||||
### WHIRL_ISLAND_NW
|
||||
|
||||
- Script: `maps/WhirlIslandNW.asm` - no scripts at all, warps only
|
||||
- Blocks: `maps/WhirlIslandNW.blk`
|
||||
- Header: `data/maps/maps.asm:136`, same row shape as NE
|
||||
- Dimensions: `constants/map_constants.asm:123` -> `5, 9` (group 3, map 58)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 5 | 7 | `ROUTE_41` | 1 |
|
||||
| 2 | 5 | 3 | `WHIRL_ISLAND_B1F` | 1 |
|
||||
| 3 | 3 | 15 | `WHIRL_ISLAND_SW` | 4 |
|
||||
| 4 | 7 | 15 | `WHIRL_ISLAND_CAVE` | 2 |
|
||||
|
||||
No coord events, bg events or objects.
|
||||
|
||||
---
|
||||
|
||||
### WHIRL_ISLAND_SW
|
||||
|
||||
- Script: `maps/WhirlIslandSW.asm`
|
||||
- Blocks: `maps/WhirlIslandSW.blk`
|
||||
- Header: `data/maps/maps.asm:138`
|
||||
- Dimensions: `constants/map_constants.asm:125` -> `10, 9` (group 3, map 60)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 5 | 7 | `ROUTE_41` | 3 |
|
||||
| 2 | 17 | 3 | `WHIRL_ISLAND_B1F` | 5 |
|
||||
| 3 | 3 | 3 | `WHIRL_ISLAND_B1F` | 4 |
|
||||
| 4 | 3 | 15 | `WHIRL_ISLAND_NW` | 3 |
|
||||
| 5 | 17 | 15 | `WHIRL_ISLAND_B2F` | 4 |
|
||||
|
||||
**Coord events**: none. **BG events**: none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `WHIRLISLANDSW_POKE_BALL` | `SPRITE_POKE_BALL` | 15 | 2 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandSWGuardSpec` (`47:4044`) | `EVENT_WHIRL_ISLAND_SW_GUARD_SPEC` |
|
||||
|
||||
`itemball GUARD_SPEC` - the walkthrough's "Guard Specs".
|
||||
|
||||
**Wild encounters**: surfing, `data/wild/johto_water.asm:79-84`,
|
||||
`def_water_wildmons WHIRL_ISLAND_SW`, rate `4 percent`: `20 TENTACOOL`,
|
||||
`15 HORSEA`, `20 TENTACRUEL`.
|
||||
|
||||
---
|
||||
|
||||
### WHIRL_ISLAND_SE
|
||||
|
||||
- Script: `maps/WhirlIslandSE.asm` - warps only
|
||||
- Blocks: `maps/WhirlIslandSE.blk`
|
||||
- Header: `data/maps/maps.asm:140`
|
||||
- Dimensions: `constants/map_constants.asm:127` -> `5, 9` (group 3, map 62)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 5 | 13 | `ROUTE_41` | 4 |
|
||||
| 2 | 5 | 3 | `WHIRL_ISLAND_B1F` | 6 |
|
||||
|
||||
No coord events, bg events or objects. No water encounter table.
|
||||
|
||||
---
|
||||
|
||||
### WHIRL_ISLAND_CAVE
|
||||
|
||||
- Script: `maps/WhirlIslandCave.asm` - warps only
|
||||
- Blocks: `maps/WhirlIslandCave.blk`
|
||||
- Header: `data/maps/maps.asm:139`
|
||||
- Dimensions: `constants/map_constants.asm:126` -> `5, 9` (group 3, map 61)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 7 | 5 | `WHIRL_ISLAND_B1F` | 9 |
|
||||
| 2 | 3 | 13 | `WHIRL_ISLAND_NW` | 4 |
|
||||
|
||||
This is the "you can now head out the door to breathe, but that's pointless" room:
|
||||
it is a pure corridor between B1F and the NW island.
|
||||
|
||||
---
|
||||
|
||||
### WHIRL_ISLAND_B1F
|
||||
|
||||
- Script: `maps/WhirlIslandB1F.asm` (`47:4096` `WhirlIslandB1F_MapScripts`)
|
||||
- Blocks: `maps/WhirlIslandB1F.blk` (`2b:6f61 WhirlIslandB1F_Blocks`)
|
||||
- Header: `data/maps/maps.asm:141`
|
||||
- Dimensions: `constants/map_constants.asm:128` -> `20, 18` (group 3, map 63)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 5 | 5 | `WHIRL_ISLAND_NW` | 2 |
|
||||
| 2 | 35 | 3 | `WHIRL_ISLAND_NE` | 2 |
|
||||
| 3 | 29 | 9 | `WHIRL_ISLAND_NE` | 3 |
|
||||
| 4 | 9 | 31 | `WHIRL_ISLAND_SW` | 3 |
|
||||
| 5 | 23 | 31 | `WHIRL_ISLAND_SW` | 2 |
|
||||
| 6 | 31 | 29 | `WHIRL_ISLAND_SE` | 2 |
|
||||
| 7 | 25 | 21 | `WHIRL_ISLAND_B2F` | 1 |
|
||||
| 8 | 13 | 27 | `WHIRL_ISLAND_B2F` | 2 |
|
||||
| 9 | 17 | 21 | `WHIRL_ISLAND_CAVE` | 1 |
|
||||
|
||||
**Coord events**: none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 30 | 4 | `BGEVENT_ITEM` | `WhirlIslandB1FHiddenRareCandy` -> `hiddenitem RARE_CANDY, EVENT_WHIRL_ISLAND_B1F_HIDDEN_RARE_CANDY` |
|
||||
| 36 | 18 | `BGEVENT_ITEM` | `WhirlIslandB1FHiddenUltraBall` -> `hiddenitem ULTRA_BALL, EVENT_WHIRL_ISLAND_B1F_HIDDEN_ULTRA_BALL` |
|
||||
| 2 | 23 | `BGEVENT_ITEM` | `WhirlIslandB1FHiddenFullRestore` -> `hiddenitem FULL_RESTORE, EVENT_WHIRL_ISLAND_B1F_HIDDEN_FULL_RESTORE` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `WHIRLISLANDB1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 7 | 13 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB1FFullRestore` | `EVENT_WHIRL_ISLAND_B1F_FULL_RESTORE` |
|
||||
| `WHIRLISLANDB1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 2 | 18 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB1FCarbos` | `EVENT_WHIRL_ISLAND_B1F_CARBOS` |
|
||||
| `WHIRLISLANDB1F_POKE_BALL3` | `SPRITE_POKE_BALL` | 33 | 23 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB1FCalcium` | `EVENT_WHIRL_ISLAND_B1F_CALCIUM` |
|
||||
| `WHIRLISLANDB1F_POKE_BALL4` | `SPRITE_POKE_BALL` | 17 | 8 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB1FNugget` | `EVENT_WHIRL_ISLAND_B1F_NUGGET` |
|
||||
| `WHIRLISLANDB1F_POKE_BALL5` | `SPRITE_POKE_BALL` | 19 | 26 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB1FEscapeRope` | `EVENT_WHIRL_ISLAND_B1F_ESCAPE_ROPE` |
|
||||
| `WHIRLISLANDB1F_BOULDER` | `SPRITE_BOULDER` | 23 | 26 | `SPRITEMOVEDATA_STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | `WhirlIslandB1FBoulder` (`jumpstd StrengthBoulderScript`) | -1 |
|
||||
|
||||
The boulder at `(23, 26)` sits two tiles east of the Escape Rope ball and is the
|
||||
only Strength object in the dungeon. The walkthrough never mentions it.
|
||||
|
||||
**Wild encounters**: grass/cave-floor, `data/wild/johto_grass.asm:1213-1239`,
|
||||
`def_grass_wildmons WHIRL_ISLAND_B1F`, rates `6 percent` for all three times of
|
||||
day, and the morn/day/nite slot lists are identical:
|
||||
`23 KRABBY`, `24 ZUBAT`, `25 KRABBY`, `23 SEEL`, `24 GOLBAT`, `25 SEEL`, `25 SEEL`.
|
||||
No water table for B1F.
|
||||
|
||||
---
|
||||
|
||||
### WHIRL_ISLAND_B2F
|
||||
|
||||
- Script: `maps/WhirlIslandB2F.asm`
|
||||
- Blocks: `maps/WhirlIslandB2F.blk`
|
||||
- Header: `data/maps/maps.asm:142`
|
||||
- Dimensions: `constants/map_constants.asm:129` -> `10, 18` (group 3, map 64)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 11 | 5 | `WHIRL_ISLAND_B1F` | 7 |
|
||||
| 2 | 7 | 11 | `WHIRL_ISLAND_B1F` | 8 |
|
||||
| 3 | 7 | 25 | `WHIRL_ISLAND_LUGIA_CHAMBER` | 1 |
|
||||
| 4 | 13 | 31 | `WHIRL_ISLAND_SW` | 5 |
|
||||
|
||||
**Coord events**: none. **BG events**: none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `WHIRLISLANDB2F_POKE_BALL1` | `SPRITE_POKE_BALL` | 10 | 11 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB2FFullRestore` | `EVENT_WHIRL_ISLAND_B2F_FULL_RESTORE` |
|
||||
| `WHIRLISLANDB2F_POKE_BALL2` | `SPRITE_POKE_BALL` | 6 | 4 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB2FMaxRevive` | `EVENT_WHIRL_ISLAND_B2F_MAX_REVIVE` |
|
||||
| `WHIRLISLANDB2F_POKE_BALL3` | `SPRITE_POKE_BALL` | 5 | 12 | `STILL` | `OBJECTTYPE_ITEMBALL` | `WhirlIslandB2FMaxElixer` | `EVENT_WHIRL_ISLAND_B2F_MAX_ELIXER` |
|
||||
|
||||
`(6, 4)` is the single `MAX_REVIVE` in the whole Whirl Islands (see Unresolved
|
||||
for the walkthrough's "Max Revive x2").
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
- Floor: `data/wild/johto_grass.asm:1241-1267`, rate `6 percent`, identical to B1F
|
||||
(`23 KRABBY / 24 ZUBAT / 25 KRABBY / 23 SEEL / 24 GOLBAT / 25 SEEL / 25 SEEL`).
|
||||
- Water: `data/wild/johto_water.asm:86-91`, rate `4 percent`: `15 HORSEA`,
|
||||
`20 HORSEA`, `20 TENTACRUEL`.
|
||||
|
||||
The "two items on the cliff but you can't reach those" line matches the two balls
|
||||
on the west shelf (`(5, 12)` Max Elixer and `(10, 11)` Full Restore are reachable
|
||||
from the other side of the floor; exactly which two the FAQ means is a `.blk`
|
||||
question, see Unresolved).
|
||||
|
||||
---
|
||||
|
||||
### WHIRL_ISLAND_LUGIA_CHAMBER
|
||||
|
||||
- Script: `maps/WhirlIslandLugiaChamber.asm` (`47:418c` callback, `47:41a0 Lugia`)
|
||||
- Blocks: `maps/WhirlIslandLugiaChamber.blk` (`2b:717d`)
|
||||
- Header: `data/maps/maps.asm:143` -> `TILESET_DARK_CAVE`, `CAVE`, `LANDMARK_WHIRL_ISLANDS`, `MUSIC_UNION_CAVE`, phone `TRUE`, `PALETTE_DARK`, `FISHGROUP_WHIRL_ISLANDS`
|
||||
- Dimensions: `constants/map_constants.asm:130` -> `10, 9` (group 3, map 65)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 9 | 13 | `WHIRL_ISLAND_B2F` | 3 |
|
||||
|
||||
**Coord events**: none. **BG events**: none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `WHIRLISLANDLUGIACHAMBER_LUGIA` | `SPRITE_LUGIA` | 9 | 5 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT`, palette `PAL_NPC_BLUE` | `Lugia` | `EVENT_WHIRL_ISLAND_LUGIA_CHAMBER_LUGIA` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `WhirlIslandLugiaChamberLugiaCallback` - `callback MAPCALLBACK_OBJECTS`.
|
||||
`checkevent EVENT_FOUGHT_LUGIA / iftrue .NoAppear`, then
|
||||
`checkitem SILVER_WING / iftrue .Appear`, else `.NoAppear`. So Lugia is on the
|
||||
map **only while the SILVER_WING is in the bag and the fight has not happened**.
|
||||
The wing is a `checkitem`, not an event - selling or storing it makes Lugia
|
||||
vanish again.
|
||||
- `Lugia` (`47:41a0`) - `faceplayer`, `opentext`, `writetext LugiaText`
|
||||
("Gyaaas!"), `cry LUGIA`, `pause 15`, `closetext`,
|
||||
**`setevent EVENT_FOUGHT_LUGIA`**, `checkver`, `iftrue .Silver`.
|
||||
- Gold arm: `loadvar VAR_BATTLETYPE, BATTLETYPE_FORCEITEM`,
|
||||
`loadwildmon LUGIA, 70`, `startbattle`,
|
||||
`disappear WHIRLISLANDLUGIACHAMBER_LUGIA`, `reloadmapafterbattle`, `end`.
|
||||
- `.Silver`: identical but `loadwildmon LUGIA, 40`.
|
||||
- `EVENT_FOUGHT_LUGIA` is set **before** the battle, so fleeing, fainting or
|
||||
knocking it out all consume the encounter. This is exactly why the
|
||||
walkthrough insists on saving first: there is no re-arm path in the asm.
|
||||
- `BATTLETYPE_FORCEITEM` (`constants/battle_constants.asm:101`) makes
|
||||
`InitEnemyMon`'s `.WildItem` arm hand over `wBaseItem1` unconditionally
|
||||
(`engine/battle/core.asm:5765-5773`). Lugia's `data/pokemon/base_stats/lugia.asm`
|
||||
row is `db NO_ITEM, NO_ITEM`, so it holds nothing - the forced-item type is
|
||||
inherited boilerplate here.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_FOUGHT_LUGIA` | `constants/event_flags.asm:455` | read by the callback, set by `Lugia` before `startbattle` | one-shot. Once set, Lugia never reappears |
|
||||
| `EVENT_WHIRL_ISLAND_LUGIA_CHAMBER_LUGIA` | `constants/event_flags.asm:1247` | the object row's mask flag, driven by `appear`/`disappear` | object visibility only |
|
||||
| `EVENT_GOT_SILVER_WING` | `constants/event_flags.asm:130` | set by `PewterCityGrampsScript` (Gold) / `RadioTower5F` (Silver) | prerequisite for holding `SILVER_WING` |
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
- Floor: `data/wild/johto_grass.asm:1269-1295`, rate `6 percent`:
|
||||
`24 KRABBY`, `25 ZUBAT`, `26 KRABBY`, `24 SEEL`, `25 GOLBAT`, `26 SEEL`, `26 SEEL`.
|
||||
- Water: `data/wild/johto_water.asm:93-98`, rate `4 percent`: `20 HORSEA`,
|
||||
`20 TENTACRUEL`, `20 SEADRA`. This is the only Seadra slot in the dungeon, and
|
||||
it is on the water directly in front of Lugia.
|
||||
|
||||
**Catch math** (`data/pokemon/base_stats/lugia.asm`): `db 3 ; catch rate`.
|
||||
`UltraBallMultiplier` doubles it to 6; `HeavyBallMultiplier`
|
||||
(`engine/items/item_effects.asm:752`) adds a flat offset by dex weight, and
|
||||
Lugia's dex weight is `4760` tenths of a pound = 216 kg
|
||||
(`data/pokemon/dex_entries/gold/lugia.asm`), which lands in the `+20` bracket ->
|
||||
effective 23. Thard_Verad's tip in the FAQ is correct and is worth ~4x the Ultra
|
||||
Ball rate.
|
||||
|
||||
---
|
||||
|
||||
### ECRUTEAK_CITY (transit only)
|
||||
|
||||
- Script: `maps/EcruteakCity.asm`
|
||||
- Header: `data/maps/maps.asm:173` -> `TILESET_JOHTO`, `TOWN`, `LANDMARK_ECRUTEAK_CITY`, `MUSIC_ECRUTEAK_CITY`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_POND`
|
||||
- Dimensions: `constants/map_constants.asm:159` -> `20, 18` (group 4, map 9)
|
||||
|
||||
**Warps** (only the rows this section uses)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 3 | 18 | 11 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | 1 |
|
||||
| 4 | 20 | 2 | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | 1 |
|
||||
| 5 | 20 | 3 | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | 2 |
|
||||
| 12 | 37 | 7 | `TIN_TOWER_1F` | 1 |
|
||||
| 13 | 5 | 5 | `BURNED_TOWER_1F` | 1 |
|
||||
|
||||
There is a `bg_event 38, 10, BGEVENT_READ, TinTowerSign` next to the tower door.
|
||||
|
||||
---
|
||||
|
||||
### ECRUTEAK_TIN_TOWER_ENTRANCE
|
||||
|
||||
- Script: `maps/EcruteakTinTowerEntrance.asm`
|
||||
- Blocks: `maps/EcruteakTinTowerEntrance.blk`
|
||||
- Header: `data/maps/maps.asm:165` -> `TILESET_TOWER`, `INDOOR`, `LANDMARK_ECRUTEAK_CITY`, `MUSIC_ECRUTEAK_CITY`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:151` -> `10, 9` (group 4, map 1)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 17 | `ECRUTEAK_CITY` | 3 |
|
||||
| 2 | 5 | 17 | `ECRUTEAK_CITY` | 3 |
|
||||
| 3 | 5 | 3 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | 4 |
|
||||
| 4 | 17 | 15 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | 3 |
|
||||
| 5 | 17 | 3 | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | 3 |
|
||||
|
||||
Warps 3 and 4 are a self-referential pair: the map holds two disconnected
|
||||
corridors and the "walk up the stairs" is a same-map teleport from `(5, 3)` to
|
||||
`(17, 15)`.
|
||||
|
||||
**Coord events**
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_ECRUTEAKTINTOWERENTRANCE_SAGE_BLOCKS` (0) | 4 | 7 | `EcruteakTinTowerEntranceSageBlocksLeft` (`52:400c`) | sage 2 side-steps LEFT, sage 1 is `moveobject`d to `(4, 6)` and `appear`ed, sage 2 `disappear`s |
|
||||
| `SCENE_ECRUTEAKTINTOWERENTRANCE_SAGE_BLOCKS` (0) | 5 | 7 | `EcruteakTinTowerEntranceSageBlocksRight` (`52:4021`) | mirror image, sage 2 ends at `(5, 6)` |
|
||||
|
||||
Scene ids come from the `scene_script` macro's inline `const` block
|
||||
(`macros/scripts/maps.asm:12-33`): `SAGE_BLOCKS` = 0, `NOOP` = 1. Both scene
|
||||
bodies are `end`; the scene id exists only to arm/disarm the two coord events.
|
||||
|
||||
**BG events**: none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ECRUTEAKTINTOWERENTRANCE_SAGE1` | `SPRITE_SAGE` | 4 | 6 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `EcruteakTinTowerEntranceSageScript` (`52:4037`) | `EVENT_ECRUTEAK_TIN_TOWER_ENTRANCE_SAGE_LEFT` |
|
||||
| `ECRUTEAKTINTOWERENTRANCE_SAGE2` | `SPRITE_SAGE` | 5 | 6 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `EcruteakTinTowerEntranceSageScript` | `EVENT_ECRUTEAK_TIN_TOWER_ENTRANCE_SAGE_RIGHT` |
|
||||
| `ECRUTEAKTINTOWERENTRANCE_SAGE3` | `SPRITE_SAGE` | 6 | 9 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `EcruteakTinTowerEntranceWanderingSageScript` (`52:404b`) | -1 |
|
||||
| `ECRUTEAKTINTOWERENTRANCE_GRAMPS` | `SPRITE_GRAMPS` | 3 | 11 | `WANDER` (1,1) | `OBJECTTYPE_SCRIPT` | `EcruteakTinTowerEntranceGrampsScript` | -1 |
|
||||
|
||||
Object rows are masked when their flag is **set** (`CheckObjectFlag`,
|
||||
`engine/overworld/map_objects_2.asm:32-61`: `EventFlagAction`/`CHECK_FLAG`
|
||||
non-zero -> `.masked`). `InitializeEventsScript`
|
||||
(`engine/events/std_scripts.asm:438`, line 533) does
|
||||
`setevent EVENT_ECRUTEAK_TIN_TOWER_ENTRANCE_SAGE_LEFT` at new game, so on a fresh
|
||||
save only SAGE2 at `(5, 6)` is standing there.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `EcruteakTinTowerEntranceSageScript` - `faceplayer`, `opentext`,
|
||||
`checkflag ENGINE_FOGBADGE`, `iftrue .BlockPassage_GotFogBadge`. Both arms are
|
||||
pure text (`EcruteakTinTowerEntranceSageText` vs `..._GotFogBadge`); **the
|
||||
script never opens the path**.
|
||||
- The actual unblock is `maps/EcruteakGym.asm:34`:
|
||||
`setmapscene ECRUTEAK_TIN_TOWER_ENTRANCE, SCENE_ECRUTEAKTINTOWERENTRANCE_NOOP`,
|
||||
run in the same block as `setflag ENGINE_FOGBADGE` after beating Morty. With
|
||||
the scene at `NOOP` the two coord events no longer match and the sage never
|
||||
steps into the doorway. By section 25 the player has all 16 badges, so this is
|
||||
already open - the walkthrough's "talk to the man and he'll let you through" is
|
||||
flavour text, not a required action.
|
||||
- `EcruteakTinTowerEntranceWanderingSageScript` - `checkevent EVENT_GOT_RAINBOW_WING`;
|
||||
the post-wing line is "The TIN TOWER shook! A #MON must have returned to the
|
||||
top!". This is the only in-map confirmation that Ho-Oh is now spawnable.
|
||||
|
||||
---
|
||||
|
||||
### ECRUTEAK_TIN_TOWER_BACK_ENTRANCE
|
||||
|
||||
- Script: `maps/EcruteakTinTowerBackEntrance.asm` - warps only, no scripts
|
||||
- Blocks: `maps/EcruteakTinTowerBackEntrance.blk`
|
||||
- Header: `data/maps/maps.asm:166` -> `TILESET_TRADITIONAL_HOUSE`, `INDOOR`, `LANDMARK_ECRUTEAK_CITY`, `MUSIC_ECRUTEAK_CITY`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:152` -> `4, 4` (group 4, map 2)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 7 | 4 | `ECRUTEAK_CITY` | 4 |
|
||||
| 2 | 7 | 5 | `ECRUTEAK_CITY` | 5 |
|
||||
| 3 | 2 | 4 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | 5 |
|
||||
|
||||
---
|
||||
|
||||
### TIN_TOWER_1F
|
||||
|
||||
- Script: `maps/TinTower1F.asm` (`42:4b1d TinTowerSageScript`)
|
||||
- Blocks: `maps/TinTower1F.blk`
|
||||
- Header: `data/maps/maps.asm:82` -> `TILESET_TOWER`, `DUNGEON`, `LANDMARK_TIN_TOWER`, `MUSIC_TIN_TOWER`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:69` -> `10, 9` (group 3, map 4)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 9 | 15 | `ECRUTEAK_CITY` | 12 |
|
||||
| 2 | 10 | 15 | `ECRUTEAK_CITY` | 12 |
|
||||
| 3 | 10 | 2 | `TIN_TOWER_2F` | 2 |
|
||||
|
||||
**Coord events**: none. **BG events**: none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `TINTOWER1F_SAGE` | `SPRITE_SAGE` | 10 | 2 | `STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `TinTowerSageScript` | `EVENT_TEAM_ROCKET_DISBANDED` |
|
||||
|
||||
**This is the real gate for the whole Silver branch.** The sage's coordinates are
|
||||
`(10, 2)`, byte-for-byte the coordinates of warp 3. While the object is visible
|
||||
the stair tile is occupied and 2F is unreachable. The object is masked - i.e. the
|
||||
sage disappears - when `EVENT_TEAM_ROCKET_DISBANDED`
|
||||
(`constants/event_flags.asm:1283`) is **set**, and that flag is only ever set by
|
||||
the two Rainbow Wing hand-offs:
|
||||
|
||||
- `maps/RadioTower5F.asm:124-130` (Gold): `verbosegiveitem RAINBOW_WING` (124),
|
||||
`setevent EVENT_GOT_RAINBOW_WING` (129), `setevent EVENT_TEAM_ROCKET_DISBANDED` (130).
|
||||
- `maps/PewterCity.asm:42-51` `.RainbowWing` (Silver): the gramps gives
|
||||
`RAINBOW_WING` (49), `setevent EVENT_GOT_RAINBOW_WING` (50),
|
||||
`setevent EVENT_TEAM_ROCKET_DISBANDED` (51).
|
||||
|
||||
So the walkthrough's "another bald man will let you through because you have the
|
||||
beautiful Rainbow Wing" is literally true, but the mechanism is a masked object
|
||||
keyed on `EVENT_TEAM_ROCKET_DISBANDED`, not a `checkitem RAINBOW_WING`.
|
||||
|
||||
**Wild encounters**: none - `TIN_TOWER_1F` has no `def_grass_wildmons` row
|
||||
(`data/wild/johto_grass.asm` starts the tower at `TIN_TOWER_2F`, line 61).
|
||||
|
||||
---
|
||||
|
||||
### TIN_TOWER_2F
|
||||
|
||||
- Script: `maps/TinTower2F.asm` - warps only
|
||||
- Blocks: `maps/TinTower2F.blk`
|
||||
- Header: `data/maps/maps.asm:83`; Dimensions `constants/map_constants.asm:70` -> `10, 9` (group 3, map 5)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 10 | 14 | `TIN_TOWER_3F` | 1 |
|
||||
| 2 | 10 | 2 | `TIN_TOWER_1F` | 3 |
|
||||
|
||||
**Wild encounters**: `data/wild/johto_grass.asm:61-87`,
|
||||
`def_grass_wildmons TIN_TOWER_2F`, rates `2 percent` morn/day/nite.
|
||||
|
||||
- morn/day: `20 RATTATA`, `21 RATTATA`, `22 RATTATA`, `22 RATTATA`, `23 RATTATA`, `24 RATTATA`, `24 RATTATA`
|
||||
- nite: `20 GASTLY`, `21 GASTLY`, `22 GASTLY`, `22 RATTATA`, `23 RATTATA`, `24 RATTATA`, `24 RATTATA`
|
||||
|
||||
Every floor 2F..9F carries the identical table (checked 2F at line 61 and 9F at
|
||||
line 257). The walkthrough only lists Rattata; Gastly at night is real.
|
||||
|
||||
---
|
||||
|
||||
### TIN_TOWER_3F
|
||||
|
||||
- Script: `maps/TinTower3F.asm`; Blocks `maps/TinTower3F.blk`
|
||||
- Header: `data/maps/maps.asm:84`; Dimensions `constants/map_constants.asm:71` -> `10, 9` (group 3, map 6)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 10 | 14 | `TIN_TOWER_2F` | 1 |
|
||||
| 2 | 16 | 2 | `TIN_TOWER_4F` | 2 |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `TINTOWER3F_POKE_BALL` | `SPRITE_POKE_BALL` | 3 | 14 | `STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower3FFullHeal` | `EVENT_TIN_TOWER_3F_FULL_HEAL` |
|
||||
|
||||
Matches the walkthrough's "hop left for a Full Heal".
|
||||
|
||||
---
|
||||
|
||||
### TIN_TOWER_4F
|
||||
|
||||
- Script: `maps/TinTower4F.asm`; Blocks `maps/TinTower4F.blk`
|
||||
- Header: `data/maps/maps.asm:85`; Dimensions `constants/map_constants.asm:72` -> `10, 9` (group 3, map 7)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 2 | 4 | `TIN_TOWER_5F` | 2 |
|
||||
| 2 | 16 | 2 | `TIN_TOWER_3F` | 2 |
|
||||
| 3 | 2 | 14 | `TIN_TOWER_5F` | 3 |
|
||||
| 4 | 17 | 15 | `TIN_TOWER_5F` | 4 |
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 11 | 6 | `BGEVENT_ITEM` | `TinTower4FHiddenMaxPotion` -> `hiddenitem MAX_POTION, EVENT_TIN_TOWER_4F_HIDDEN_MAX_POTION` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `TINTOWER4F_POKE_BALL1` | `SPRITE_POKE_BALL` | 14 | 10 | `STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower4FUltraBall` (`42:4cc1`) | `EVENT_TIN_TOWER_4F_ULTRA_BALL` |
|
||||
| `TINTOWER4F_POKE_BALL2` | `SPRITE_POKE_BALL` | 17 | 14 | `STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower4FSuperPotion` | `EVENT_TIN_TOWER_4F_SUPER_POTION` |
|
||||
| `TINTOWER4F_POKE_BALL3` | `SPRITE_POKE_BALL` | 2 | 12 | `STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower4FEscapeRope` | `EVENT_TIN_TOWER_4F_ESCAPE_ROPE` |
|
||||
|
||||
---
|
||||
|
||||
### TIN_TOWER_5F
|
||||
|
||||
- Script: `maps/TinTower5F.asm`; Blocks `maps/TinTower5F.blk`
|
||||
- Header: `data/maps/maps.asm:86`; Dimensions `constants/map_constants.asm:73` -> `10, 9` (group 3, map 8)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 11 | 15 | `TIN_TOWER_6F` | 2 |
|
||||
| 2 | 2 | 4 | `TIN_TOWER_4F` | 1 |
|
||||
| 3 | 2 | 14 | `TIN_TOWER_4F` | 3 |
|
||||
| 4 | 17 | 15 | `TIN_TOWER_4F` | 4 |
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 16 | 14 | `BGEVENT_ITEM` | `TinTower5FHiddenFullRestore` -> `hiddenitem FULL_RESTORE, EVENT_TIN_TOWER_5F_HIDDEN_FULL_RESTORE` |
|
||||
| 3 | 15 | `BGEVENT_ITEM` | `TinTower5FHiddenCarbos` -> `hiddenitem CARBOS, EVENT_TIN_TOWER_5F_HIDDEN_CARBOS` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `TINTOWER5F_POKE_BALL` | `SPRITE_POKE_BALL` | 9 | 9 | `STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower5FRareCandy` (`42:4d12`) | `EVENT_TIN_TOWER_5F_RARE_CANDY` |
|
||||
|
||||
---
|
||||
|
||||
### TIN_TOWER_6F
|
||||
|
||||
- Script: `maps/TinTower6F.asm` - warps only, no items, no objects
|
||||
- Blocks: `maps/TinTower6F.blk`
|
||||
- Header: `data/maps/maps.asm:87`; Dimensions `constants/map_constants.asm:74` -> `10, 9` (group 3, map 9)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 9 | `TIN_TOWER_7F` | 1 |
|
||||
| 2 | 11 | 15 | `TIN_TOWER_5F` | 1 |
|
||||
|
||||
The walkthrough's "go up and you'll get a Max Potion ... left across the upper
|
||||
bridge to get a Full Heal" floor has **no item balls and no bg events at all** in
|
||||
pokegold. See Unresolved.
|
||||
|
||||
---
|
||||
|
||||
### TIN_TOWER_7F
|
||||
|
||||
- Script: `maps/TinTower7F.asm`; Blocks `maps/TinTower7F.blk`
|
||||
- Header: `data/maps/maps.asm:88`; Dimensions `constants/map_constants.asm:75` -> `10, 9` (group 3, map 10)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 9 | `TIN_TOWER_6F` | 1 |
|
||||
| 2 | 10 | 15 | `TIN_TOWER_8F` | 1 |
|
||||
| 3 | 12 | 7 | `TIN_TOWER_7F` | 4 |
|
||||
| 4 | 8 | 3 | `TIN_TOWER_7F` | 3 |
|
||||
| 5 | 6 | 9 | `TIN_TOWER_9F` | 5 |
|
||||
|
||||
Warps 3/4 are the in-floor pair the walkthrough calls "go through the next two
|
||||
warps".
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `TINTOWER7F_POKE_BALL` | `SPRITE_POKE_BALL` | 16 | 1 | `STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower7FMaxRevive` | `EVENT_TIN_TOWER_7F_MAX_REVIVE` |
|
||||
|
||||
---
|
||||
|
||||
### TIN_TOWER_8F
|
||||
|
||||
- Script: `maps/TinTower8F.asm`; Blocks `maps/TinTower8F.blk`
|
||||
- Header: `data/maps/maps.asm:89`; Dimensions `constants/map_constants.asm:76` -> `10, 9` (group 3, map 11)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 2 | 5 | `TIN_TOWER_7F` | 2 |
|
||||
| 2 | 2 | 11 | `TIN_TOWER_9F` | 1 |
|
||||
| 3 | 16 | 7 | `TIN_TOWER_9F` | 2 |
|
||||
| 4 | 10 | 3 | `TIN_TOWER_9F` | 3 |
|
||||
| 5 | 14 | 15 | `TIN_TOWER_9F` | 6 |
|
||||
| 6 | 6 | 9 | `TIN_TOWER_9F` | 7 |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `TINTOWER8F_POKE_BALL1` | `SPRITE_POKE_BALL` | 7 | 13 | `STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower8FNugget` | `EVENT_TIN_TOWER_8F_NUGGET` |
|
||||
| `TINTOWER8F_POKE_BALL2` | `SPRITE_POKE_BALL` | 11 | 6 | `STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower8FMaxElixer` | `EVENT_TIN_TOWER_8F_MAX_ELIXER` |
|
||||
| `TINTOWER8F_POKE_BALL3` | `SPRITE_POKE_BALL` | 3 | 1 | `STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower8FFullRestore` | `EVENT_TIN_TOWER_8F_FULL_RESTORE` |
|
||||
|
||||
---
|
||||
|
||||
### TIN_TOWER_9F
|
||||
|
||||
- Script: `maps/TinTower9F.asm` (`42:4de0 TinTower9F_MapScripts`) - no callbacks,
|
||||
no objects; carries two unreferenced texts `TinTower9FUnusedHoOhText` /
|
||||
`TinTower9FUnusedLugiaText`
|
||||
- Blocks: `maps/TinTower9F.blk` (`2b:625f`)
|
||||
- Header: `data/maps/maps.asm:90`; Dimensions `constants/map_constants.asm:77` -> `10, 9` (group 3, map 12)
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 12 | 3 | `TIN_TOWER_8F` | 2 |
|
||||
| 2 | 2 | 5 | `TIN_TOWER_8F` | 3 |
|
||||
| 3 | 12 | 7 | `TIN_TOWER_8F` | 4 |
|
||||
| 4 | 7 | 9 | `TIN_TOWER_ROOF` | 1 |
|
||||
| 5 | 16 | 7 | `TIN_TOWER_7F` | 5 |
|
||||
| 6 | 6 | 13 | `TIN_TOWER_8F` | 5 |
|
||||
| 7 | 8 | 13 | `TIN_TOWER_8F` | 6 |
|
||||
|
||||
Warp 4 at `(7, 9)` is the only exit to the roof.
|
||||
|
||||
---
|
||||
|
||||
### TIN_TOWER_ROOF
|
||||
|
||||
- Script: `maps/TinTowerRoof.asm` (`5b:68ff` callback, `5b:6913 TinTowerHoOh`)
|
||||
- Blocks: `maps/TinTowerRoof.blk` (`2b:62b9`)
|
||||
- Header: `data/maps/maps.asm:344` -> `TILESET_TOWER`, **`ROUTE`**, `LANDMARK_TIN_TOWER`, `MUSIC_TIN_TOWER`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:318` -> `10, 9`, and note the group:
|
||||
`TIN_TOWER_ROOF` is map 12 of **group 15 (`FAST_SHIP`)**, not group 3 with the
|
||||
rest of the tower.
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 9 | 13 | `TIN_TOWER_9F` | 4 |
|
||||
|
||||
**Coord events**: none. **BG events**: none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `TINTOWERROOF_HO_OH` | `SPRITE_HO_OH` | 9 | 5 | `SPRITEMOVEDATA_POKEMON` | `OBJECTTYPE_SCRIPT`, palette `PAL_NPC_RED` | `TinTowerHoOh` | `EVENT_TIN_TOWER_ROOF_HO_OH` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `TinTowerRoofHoOhCallback` - `callback MAPCALLBACK_OBJECTS`:
|
||||
`checkevent EVENT_FOUGHT_HO_OH / iftrue .NoAppear`, then
|
||||
`checkitem RAINBOW_WING / iftrue .Appear`. Same shape as Lugia's: the wing must
|
||||
be **in the bag**, not merely once-received.
|
||||
- `TinTowerHoOh` (`5b:6913`) - text "Shaoooh!", `cry HO_OH`, `pause 15`,
|
||||
`setevent EVENT_FOUGHT_HO_OH` **before** the fight, `checkver / iftrue .Silver`.
|
||||
- Gold arm: `loadwildmon HO_OH, 40`.
|
||||
- `.Silver`: `loadwildmon HO_OH, 70` - the level the walkthrough quotes.
|
||||
- Both arms run `loadvar VAR_BATTLETYPE, BATTLETYPE_FORCEITEM` first, and
|
||||
Ho-Oh's base row is `db SACRED_ASH, SACRED_ASH`
|
||||
(`data/pokemon/base_stats/ho_oh.asm:9`), so **Ho-Oh always holds a SACRED_ASH**.
|
||||
That is a guaranteed item a bot should strip before/after the catch.
|
||||
|
||||
**Escape Rope caveat**: `EscapeRopeFunction` -> `.CheckCanDig`
|
||||
(`engine/events/overworld.asm:724-754`) only succeeds when
|
||||
`GetMapEnvironment` is `CAVE` or `DUNGEON`. `TIN_TOWER_ROOF` is `ROUTE`, so the
|
||||
walkthrough is right that you must go back down to 9F first. Every Whirl Island
|
||||
map including `WHIRL_ISLAND_LUGIA_CHAMBER` is `CAVE`, so the rope works on the
|
||||
spot there.
|
||||
|
||||
**Catch math**: Ho-Oh's catch rate is also `3`
|
||||
(`data/pokemon/base_stats/ho_oh.asm:7`). Its dex weight is `4390`
|
||||
(`data/pokemon/dex_entries/gold/ho_oh.asm`) = 199 kg, which falls in the
|
||||
`HeavyBallMultiplier` "add 0" bracket - so unlike Lugia, a Heavy Ball is *worse*
|
||||
than an Ultra Ball on Ho-Oh.
|
||||
|
||||
---
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Reaching Route 41 at all | `SurfFunction.TrySurf`, `engine/events/overworld.asm:322` (badge test at `:340`) | `ENGINE_FOGBADGE` + a party member that knows `SURF` | Fog Badge (Morty) |
|
||||
| Whirlpools guarding the Whirl Islands | `WhirlpoolFunction` `engine/events/overworld.asm:1061` (badge test at `:1077`) and `TryWhirlpoolOW` `:1167` (badge test at `:1171`) | `ENGINE_GLACIERBADGE` + `WHIRLPOOL` in the party | Glacier Badge (Pryce, badge 7) - **not** eight badges, see Unresolved |
|
||||
| Darkness inside every Whirl Island map | `FlashFunction.CheckUseFlash`, `engine/events/overworld.asm:271` | `ENGINE_ZEPHYRBADGE` + `FLASH`, and `wTimeOfDayPalset == DARKNESS_PALSET` (the map header's `PALETTE_DARK`) | Zephyr Badge. Flash is *refused* on any non-dark map, so a bot must only try it inside |
|
||||
| Climbing back up the B2F waterfall | `WaterfallFunction` `engine/events/overworld.asm:611` (badge test at `:618`) and `TryWaterfallOW` `:683` (badge test at `:687`); direction check `CheckMapCanWaterfall` (must face UP, `wTileUp` is a waterfall tile) | `ENGINE_RISINGBADGE` + `WATERFALL` | Rising Badge (Clair). Descending needs nothing - only the UP direction is gated |
|
||||
| B1F boulder at `(23, 26)` | `WhirlIslandB1FBoulder` -> `jumpstd StrengthBoulderScript` | `STRENGTH` + `ENGINE_PLAINBADGE` | optional; not on the Lugia path |
|
||||
| Lugia not on the map | `WhirlIslandLugiaChamberLugiaCallback` | `checkitem SILVER_WING` true **and** `EVENT_FOUGHT_LUGIA` clear | `EVENT_GOT_SILVER_WING` via `PewterCityGrampsScript` (Gold) or `RadioTower5F` (Silver) - and keep the wing in the bag |
|
||||
| Ho-Oh not on the map | `TinTowerRoofHoOhCallback` | `checkitem RAINBOW_WING` true **and** `EVENT_FOUGHT_HO_OH` clear | `EVENT_GOT_RAINBOW_WING` via `RadioTower5F` (Gold) or `PewterCityGrampsScript` `.RainbowWing` (Silver) |
|
||||
| Tin Tower stairs to 2F | `TinTower1F` object row: sage stands on `(10, 2)`, masked by `EVENT_TEAM_ROCKET_DISBANDED` | flag must be **set** | `setevent EVENT_TEAM_ROCKET_DISBANDED` in `maps/RadioTower5F.asm:130` or `maps/PewterCity.asm:51` |
|
||||
| Tin Tower entrance corridor | `coord_event 4, 7` / `5, 7` under `SCENE_ECRUTEAKTINTOWERENTRANCE_SAGE_BLOCKS` | scene must be `NOOP` | `maps/EcruteakGym.asm:34` `setmapscene ... SCENE_ECRUTEAKTINTOWERENTRANCE_NOOP` on the Fog Badge |
|
||||
| One-shot mascot | `Lugia` / `TinTowerHoOh` set `EVENT_FOUGHT_LUGIA` / `EVENT_FOUGHT_HO_OH` **before** `startbattle` | n/a | none. Save-before-fight is the only recovery |
|
||||
|
||||
---
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
**Preconditions for either branch**: 8 Johto badges (Zephyr, Fog, Plain, Glacier,
|
||||
Rising in particular), party moves `SURF`, `WHIRLPOOL`, `FLASH`, `WATERFALL`
|
||||
(`STRENGTH` optional), the version-opposite wing in the bag, a stack of
|
||||
`ULTRA_BALL` (`HEAVY_BALL` for Lugia), and a save immediately before the mascot.
|
||||
|
||||
**Gold branch - Lugia**
|
||||
|
||||
| # | Map | Target | Intent | Precondition | Postcondition |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | any | Olivine City | fly | `ENGINE_FLYPOINT_OLIVINE` | on `OLIVINE_CITY` |
|
||||
| 2 | `OLIVINE_CITY` | west edge | walk, then surf | `ENGINE_FOGBADGE` + `SURF` | on `ROUTE_40` |
|
||||
| 3 | `ROUTE_40` | south edge | surf | - | on `ROUTE_41` |
|
||||
| 4 | `ROUTE_41` | any block id `$07` (`TILESET_JOHTO` whirlpool) | face it, press A -> `TryWhirlpoolOW` | `ENGINE_GLACIERBADGE` + `WHIRLPOOL` | block replaced with `$36` for this map session only (`DisappearWhirlpool` writes the map buffer, not the save) |
|
||||
| 5 | `ROUTE_41` | `(36, 19)` warp 2 | walk onto | whirlpools cleared | on `WHIRL_ISLAND_NE` at `(3, 13)` |
|
||||
| 6 | `WHIRL_ISLAND_NE` | menu | use `FLASH` | `ENGINE_ZEPHYRBADGE`, map palette `PALETTE_DARK` | cave lit |
|
||||
| 7 | `WHIRL_ISLAND_NE` | object `WHIRLISLANDNE_POKE_BALL` `(11, 11)` | walk onto / A | `EVENT_WHIRL_ISLAND_NE_ULTRA_BALL` clear | `ULTRA_BALL` in bag, flag set |
|
||||
| 8 | `WHIRL_ISLAND_NE` | warp 2 `(17, 3)` (or warp 3 `(13, 11)`) | walk onto | - | `WHIRL_ISLAND_B1F` `(35, 3)` / `(29, 9)` |
|
||||
| 9 | `WHIRL_ISLAND_B1F` | `(19, 26)` | pick up | `EVENT_WHIRL_ISLAND_B1F_ESCAPE_ROPE` clear | `ESCAPE_ROPE` in bag |
|
||||
| 10 | `WHIRL_ISLAND_B1F` | `(33, 23)` | pick up | `EVENT_WHIRL_ISLAND_B1F_CALCIUM` clear | `CALCIUM` in bag |
|
||||
| 11 | `WHIRL_ISLAND_B1F` | optional `(30, 4)`, `(36, 18)`, `(2, 23)` | face + A (hidden) | matching `EVENT_..._HIDDEN_*` clear | Rare Candy / Ultra Ball / Full Restore |
|
||||
| 12 | `WHIRL_ISLAND_B1F` | warp 4 `(9, 31)` or warp 5 `(23, 31)` | walk onto | - | `WHIRL_ISLAND_SW` |
|
||||
| 13 | `WHIRL_ISLAND_SW` | `(15, 2)` | pick up | `EVENT_WHIRL_ISLAND_SW_GUARD_SPEC` clear | `GUARD_SPEC` in bag |
|
||||
| 14 | `WHIRL_ISLAND_B1F` | warp 7 `(25, 21)` | walk onto | - | `WHIRL_ISLAND_B2F` `(11, 5)` |
|
||||
| 15 | `WHIRL_ISLAND_B2F` | `(6, 4)` | pick up | `EVENT_WHIRL_ISLAND_B2F_MAX_REVIVE` clear | `MAX_REVIVE` in bag |
|
||||
| 16 | `WHIRL_ISLAND_B2F` | waterfall block, moving DOWN | surf south over it | `SURF` only (no badge for descent) | lower B2F pool |
|
||||
| 17 | `WHIRL_ISLAND_B2F` | warp 3 `(7, 25)` | walk onto | - | `WHIRL_ISLAND_LUGIA_CHAMBER` `(9, 13)` |
|
||||
| 18 | `WHIRL_ISLAND_LUGIA_CHAMBER` | - | **SAVE** | - | recoverable |
|
||||
| 19 | `WHIRL_ISLAND_LUGIA_CHAMBER` | object at `(9, 5)` | surf north, A | `SILVER_WING` in bag, `EVENT_FOUGHT_LUGIA` clear | `EVENT_FOUGHT_LUGIA` set, wild Lugia Lv70 (Gold) / Lv40 (Silver) |
|
||||
| 20 | battle | - | weaken, status, throw balls | catch rate 3 (Ultra 6, Heavy 23) | Lugia caught, `disappear` + `reloadmapafterbattle` |
|
||||
| 21 | `WHIRL_ISLAND_LUGIA_CHAMBER` | bag | use `ESCAPE_ROPE` | environment `CAVE` | back at last Pokecenter spawn |
|
||||
|
||||
**Silver branch - Ho-Oh**
|
||||
|
||||
| # | Map | Target | Intent | Precondition | Postcondition |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | any | Ecruteak City | fly | `ENGINE_FLYPOINT_ECRUTEAK` | on `ECRUTEAK_CITY` |
|
||||
| 2 | `ECRUTEAK_CITY` | warp 3 `(18, 11)` | walk onto | - | `ECRUTEAK_TIN_TOWER_ENTRANCE` `(4, 17)` |
|
||||
| 3 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | `(4, 7)` / `(5, 7)` | walk through | map scene must be `NOOP` (Fog Badge) | no coord event fires |
|
||||
| 4 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | warp 3 `(5, 3)` | walk onto | - | same map, `(17, 15)` |
|
||||
| 5 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | warp 5 `(17, 3)` | walk onto | - | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` `(2, 4)` |
|
||||
| 6 | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | warp 1 `(7, 4)` | walk onto | - | `ECRUTEAK_CITY` `(20, 2)` |
|
||||
| 7 | `ECRUTEAK_CITY` | warp 12 `(37, 7)` | walk east then onto | - | `TIN_TOWER_1F` `(9, 15)` |
|
||||
| 8 | `TIN_TOWER_1F` | warp 3 `(10, 2)` | walk onto | `EVENT_TEAM_ROCKET_DISBANDED` **set** (sage masked) | `TIN_TOWER_2F` `(10, 2)` |
|
||||
| 9 | `TIN_TOWER_2F` | warp 1 `(10, 14)` | walk onto | - | `TIN_TOWER_3F` |
|
||||
| 10 | `TIN_TOWER_3F` | `(3, 14)` then warp 2 `(16, 2)` | pick up, walk onto | `EVENT_TIN_TOWER_3F_FULL_HEAL` clear | `FULL_HEAL`; `TIN_TOWER_4F` |
|
||||
| 11 | `TIN_TOWER_4F` | `(14, 10)`, `(17, 14)`, `(2, 12)`, hidden `(11, 6)` | pick up | matching flags clear | Ultra Ball, Super Potion, Escape Rope, hidden Max Potion |
|
||||
| 12 | `TIN_TOWER_4F` | warp 1 `(2, 4)` / 3 `(2, 14)` / 4 `(17, 15)` | walk onto | - | `TIN_TOWER_5F` |
|
||||
| 13 | `TIN_TOWER_5F` | `(9, 9)`, hidden `(16, 14)` and `(3, 15)` | pick up | matching flags clear | Rare Candy, Full Restore, Carbos |
|
||||
| 14 | `TIN_TOWER_5F` | warp 1 `(11, 15)` | walk onto | - | `TIN_TOWER_6F` `(11, 15)` |
|
||||
| 15 | `TIN_TOWER_6F` | warp 1 `(3, 9)` | walk onto | - | `TIN_TOWER_7F` `(3, 9)` |
|
||||
| 16 | `TIN_TOWER_7F` | `(16, 1)`; warps 3 `(12, 7)` / 4 `(8, 3)` | pick up, hop the pair | `EVENT_TIN_TOWER_7F_MAX_REVIVE` clear | `MAX_REVIVE` |
|
||||
| 17 | `TIN_TOWER_7F` | warp 2 `(10, 15)` | walk onto | - | `TIN_TOWER_8F` `(2, 5)` |
|
||||
| 18 | `TIN_TOWER_8F` | `(7, 13)`, `(11, 6)`, `(3, 1)` | pick up | matching flags clear | Nugget, Max Elixer, Full Restore |
|
||||
| 19 | `TIN_TOWER_8F` | warp 2 `(2, 11)` (or 3/4/5/6) | walk onto | - | `TIN_TOWER_9F` |
|
||||
| 20 | `TIN_TOWER_9F` | warp 4 `(7, 9)` | walk onto | - | `TIN_TOWER_ROOF` `(9, 13)` |
|
||||
| 21 | `TIN_TOWER_ROOF` | - | **SAVE** | - | recoverable |
|
||||
| 22 | `TIN_TOWER_ROOF` | object at `(9, 5)` | walk up, A | `RAINBOW_WING` in bag, `EVENT_FOUGHT_HO_OH` clear | `EVENT_FOUGHT_HO_OH` set, wild Ho-Oh Lv70 (Silver) / Lv40 (Gold) holding `SACRED_ASH` |
|
||||
| 23 | battle | - | weaken, status, Ultra Balls | catch rate 3; Heavy Ball gives no bonus here | Ho-Oh caught |
|
||||
| 24 | `TIN_TOWER_9F` | warp 1 on the roof, then bag | descend, use `ESCAPE_ROPE` | roof is `ROUTE` environment - rope fails there | back at spawn |
|
||||
|
||||
---
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Map headers, warps, coord/bg/object events for all of these maps | `src/import/RomExtractorGen2.lua` (walks all 26 map groups, `MAP_GROUP_COUNT` at line 47), consumed by `src/world/gen2/Map.lua` / `World.lua` | implemented (generic - no map-specific code, so these maps come in with everything else) |
|
||||
| `MAPCALLBACK_OBJECTS` (the Lugia / Ho-Oh appear-check) | `src/world/gen2/World.lua:5700` `runMapCallback("MAPCALLBACK_OBJECTS")`; regression driver `tests/drivers/gold_map_callbacks.lua` | implemented |
|
||||
| Object masking by event flag (`CheckObjectFlag`) - the Tin Tower 1F sage, every item ball | `src/world/gen2/World.lua:5106,5122` `events:objectVisible(obj.eventFlag)` | implemented |
|
||||
| `checkver` version split (Lv70 vs Lv40 mascot) | `src/script/gen2/Vm.lua:774-783`; opcode table `src/script/gen2/Opcodes.lua:29` | implemented (comments even cite `WhirlIslandLugiaChamber` by name) |
|
||||
| `loadwildmon` / `startbattle` / `reloadmapafterbattle` | `src/script/gen2/Opcodes.lua:98-101`, `src/script/gen2/Vm.lua:817-896` | implemented |
|
||||
| `BATTLETYPE_FORCEITEM` -> Ho-Oh's guaranteed `SACRED_ASH` | `src/world/gen2/World.lua:107-111` and `:4571-4579` | implemented |
|
||||
| Whirlpool field move, badge gate and block replacement | `src/world/gen2/FieldMoves.lua:109` (`WHIRLPOOL = "GLACIER"`), `:219` `WHIRLPOOL_BLOCKS`, `:247` `somethingToWhirlpool`, `:542` `whirlpoolFromMenu`, `:626` `tryWhirlpoolOW` | implemented |
|
||||
| Flash + `PALETTE_DARK` darkness | `src/world/gen2/FieldMoves.lua:105,468`; `src/world/gen2/Palettes.lua:53,75-103` | implemented |
|
||||
| Waterfall (up-only gate, `RISINGBADGE`) | `src/world/gen2/FieldMoves.lua:110,258,646` | implemented |
|
||||
| Hidden items (`hiddenitem` bg events) | `src/world/gen2/HiddenItems.lua`, `src/world/gen2/World.lua:1373,5286` | implemented |
|
||||
| Catch rate formula incl. the two cart bugs | `src/battle/gen2/Catching.lua:1-90` | implemented |
|
||||
| **Heavy Ball weight bonus** (the FAQ's Lugia tip) | `src/battle/gen2/Catching.lua:36` - `HEAVY_BALL = 1`, a flat multiplier with a comment saying the specialty balls "key off conditions the caller supplies", and no dex-weight lookup anywhere | **missing** - a Heavy Ball currently behaves as a plain Poke Ball on Lugia |
|
||||
| Magnet Train hop that opens the section | `src/core/gen2/MagnetTrain.lua` | implemented (not re-verified for this section) |
|
||||
| Escape Rope environment check (`CAVE`/`DUNGEON` only, roof excluded) | not located in `src/world/gen2/` or `src/battle/gen2/` by grep | **unverified** - could not find an `EscapeRopeFunction` equivalent; a bot should not assume the roof refusal is modelled |
|
||||
| Hand-ported scripts for these specific maps | none - grep for `whirl`/`tintower`/`lugia`/`hooh` across `src/` returns only battle-anim and title-screen hits | not applicable (all script bodies are extracted, run through `src/script/gen2/Vm.lua`) |
|
||||
|
||||
---
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
1. **"You'll need eight badges in order to use Whirlpool."** The asm gates
|
||||
Whirlpool on `ENGINE_GLACIERBADGE` alone (`engine/events/overworld.asm:1077`
|
||||
and `:1171`), i.e. seven badges. Eight is wrong, though harmless in practice
|
||||
for a post-game section.
|
||||
2. **Whirl Islands item list.** The FAQ lists Calcium, Escape Rope, Guard Specs,
|
||||
Max Revive x2, Ultra Ball. The asm has *one* Max Revive in the entire dungeon
|
||||
(`WhirlIslandB2FMaxRevive` at B2F `(6, 4)`) and additionally a `FULL_RESTORE`,
|
||||
`CARBOS`, `NUGGET` on B1F, a second `FULL_RESTORE` and a `MAX_ELIXER` on B2F,
|
||||
plus three hidden items on B1F that the FAQ never mentions. The walkthrough's
|
||||
two "grab the Max Revive" beats cannot both be the B2F ball; the first one is
|
||||
unlocatable.
|
||||
3. **Tin Tower item list.** The FAQ names a PP Up, an HP Up, a Max Potion item
|
||||
ball, and a second Full Heal. None exist in pokegold: there is no `PP_UP` or
|
||||
`HP_UP` anywhere in `maps/TinTower*.asm`, the only Max Potion is the *hidden*
|
||||
`TinTower4FHiddenMaxPotion` at `(11, 6)`, and the only Full Heal is
|
||||
`TinTower3FFullHeal`. Conversely the FAQ omits `SUPER_POTION` (4F),
|
||||
`NUGGET`/`FULL_RESTORE` (8F) and the three hidden items on 4F/5F. The list
|
||||
looks like it was written against a different version of the game.
|
||||
4. **"On the next level, go up and you'll get a Max Potion ... Full Heal"** places
|
||||
two items on the 6F-shaped floor; `maps/TinTower6F.asm` has an empty
|
||||
`def_bg_events` and an empty `def_object_events`. Not locatable.
|
||||
5. **Ho-Oh's moveset.** The FAQ says "Safeguard and Ancient Power ..., Punishment,
|
||||
and Sacred Fire". `PUNISHMENT` does not exist in Gen 2 at all
|
||||
(`data/pokemon/evos_attacks.asm:3324-3336` is the whole learnset). Derived
|
||||
from that table plus `FillMoves` (`engine/pokemon/evolve.asm:478`, which keeps
|
||||
the last four moves learnable at or below the level), a level-70 Ho-Oh should
|
||||
have `RECOVER`, `FIRE_BLAST`, `SUNNY_DAY`, `SWIFT`, and a level-40 one
|
||||
`SACRED_FIRE`, `SAFEGUARD`, `GUST`, `RECOVER`. Worth confirming in-engine
|
||||
before a bot plans around Sacred Fire; the `FillMoves` derivation is mine, not
|
||||
a literal table in the asm.
|
||||
6. **Lugia's Pokemon list at the Whirl Islands.** The FAQ lists Zubat, Seel,
|
||||
Krabby, Horsea, Seadra and omits `GOLBAT` (in every grass slot 5),
|
||||
`TENTACOOL` / `TENTACRUEL` (SW and B2F water). Seadra only appears in
|
||||
`WHIRL_ISLAND_LUGIA_CHAMBER`'s water table.
|
||||
7. **"Swimmer Kara" as a whirlpool landmark.** Her object is at `(44, 28)` on
|
||||
Route 41; the whirlpool "just northwest" of her is a `.blk` block, not an
|
||||
event, so the exact coordinate could not be pinned from the asm text. A bot
|
||||
must scan `maps/Route41.blk` for `TILESET_JOHTO` block `$07`.
|
||||
8. **The Whirl Islands ledge/jump routing** ("leap the ledge to the right on that
|
||||
bike", "two items on the cliff but you can't reach those") is entirely block
|
||||
and collision data in `maps/WhirlIsland*.blk`. None of it is expressible from
|
||||
the event tables; a bot needs the decoded block/collision grid.
|
||||
9. **The Tin Tower jump-platform routing** (the long "left x5, down, right x2 ..."
|
||||
sequences) is likewise `.blk` data. Only the warp endpoints above are asm
|
||||
facts.
|
||||
10. **"go into the Bell Tower entry house"** - the map, the sign and every text
|
||||
string in pokegold call it TIN TOWER (`TinTowerSign`,
|
||||
`EcruteakTinTowerEntranceSageText`). "Bell Tower" is the post-GS rename.
|
||||
11. **Escape Rope on the port.** Could not find the port's equivalent of
|
||||
`EscapeRopeFunction`'s `CAVE`/`DUNGEON` environment check, so I cannot say
|
||||
whether the port correctly refuses the rope on `TIN_TOWER_ROOF`.
|
||||
@@ -0,0 +1,893 @@
|
||||
# Section 26 - Mount Moon and Routes 1-4
|
||||
|
||||
Source: `../section-26-mount-moon-and-routes-1-4.txt` (walkthrough heading "32 > Mount Moon and Routes 1-4")
|
||||
Maps covered: `MAP_ROUTE_3`, `MAP_MOUNT_MOON`, `MAP_MOUNT_MOON_SQUARE`, `MAP_MOUNT_MOON_GIFT_SHOP`, `MAP_ROUTE_4`, `MAP_VIRIDIAN_CITY`, `MAP_TRAINER_HOUSE_1F`, `MAP_TRAINER_HOUSE_B1F`, `MAP_ROUTE_1`, `MAP_PALLET_TOWN`, `MAP_OAKS_LAB`
|
||||
Badges / key milestones in this section: no badge. The milestones are `EVENT_BEAT_RIVAL_IN_MT_MOON` (the last mandatory Silver fight, and the unlock for both rival re-encounters), `ENGINE_FLYPOINT_VIRIDIAN` and `ENGINE_FLYPOINT_PALLET` (Fly targets), `EVENT_TALKED_TO_OAK_IN_KANTO` (Oak's Kanto-badge conversation, which later opens Mt. Silver via `EVENT_OPENED_MT_SILVER`), plus the `HP_UP` item ball on Route 4.
|
||||
|
||||
Note on paths: everything under `maps/`, `data/`, `constants/`, `engine/`, `macros/` is relative to the pokegold checkout at `/Users/bryanbassett/Documents/development/pokegold`. Everything under `src/` and `tests/` is relative to this repo root. Addresses are `bank:addr` from `/Users/bryanbassett/Documents/development/pokegold-symbols/pokegold.sym`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Route order
|
||||
|
||||
The section starts with the player already standing on Route 3 (arriving from Pewter City, which belongs to the previous section) and ends by handing off to Route 21 (next section).
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `MAP_ROUTE_3` | `maps/Route3.asm` | west connection from `PEWTER_CITY` (`data/maps/attributes.asm:377`) | warp 1 at (52, 1) -> `MOUNT_MOON` warp 1 | four trainers east: Warren, Jimmy, Otis, Burt |
|
||||
| 2 | `MAP_ROUTE_3` -> Pewter and back | - | - | - | walkthrough tells the player to heal at the Pewter Pokemon Center and save before Mt. Moon (no asm gate; pure advice) |
|
||||
| 3 | `MAP_MOUNT_MOON` | `maps/MountMoon.asm` | warp 1 at (3, 3) | see below | mandatory rival battle, fired by `scene_script MountMoonRivalEncounterScene, SCENE_MOUNTMOON_RIVAL_BATTLE` |
|
||||
| 4 | `MAP_MOUNT_MOON` (north lane) | `maps/MountMoon.asm` | warp 3 at (13, 3) -> warp 7 at (25, 3) | warp 5 at (25, 5) -> `MOUNT_MOON_SQUARE` warp 1 | "go right from your Rival battle, up the ladder, and then out" |
|
||||
| 5 | `MAP_MOUNT_MOON_SQUARE` | `maps/MountMoonSquare.asm` | warp 1 at (20, 5) | warp 3 at (13, 7) -> gift shop; warp 2 at (22, 11) -> `MOUNT_MOON` warp 6 | the outdoor shop and the Monday-night Clefairy dance |
|
||||
| 6 | `MAP_MOUNT_MOON_GIFT_SHOP` | `maps/MountMoonGiftShop.asm` | warp 1 (3, 7) / warp 2 (4, 7) | either warp -> `MOUNT_MOON_SQUARE` warp 3 | "a shop outside that sells Fresh Water, Soda Pop, and Lemonade" (`MART_MT_MOON`) |
|
||||
| 7 | `MAP_MOUNT_MOON` (south lane) | `maps/MountMoon.asm` | warp 6 at (25, 15) | warp 8 at (25, 13) -> warp 4 at (15, 11), then warp 2 at (15, 15) | "go down into another cave entrance and down the ladder, you'll make your way through" |
|
||||
| 8 | `MAP_ROUTE_4` | `maps/Route4.asm` | warp 1 at (2, 5) from `MOUNT_MOON` warp 2 | east connection to `CERULEAN_CITY` (not taken here) | three trainers: Hope, Hank, Sharon; `HP_UP` item ball at (26, 3) |
|
||||
| 9 | `MAP_VIRIDIAN_CITY` | `maps/ViridianCity.asm` | walkthrough routes Fly -> Pewter -> `ROUTE_2` -> `VIRIDIAN_FOREST` -> Viridian (those two maps belong to a neighbouring section) | warp 3 at (23, 15) -> `TRAINER_HOUSE_1F` | Trainer House daily battle |
|
||||
| 10 | `MAP_TRAINER_HOUSE_1F` | `maps/TrainerHouse1F.asm` | warp 1/2 at (2, 13)/(3, 13) | warp 3 at (8, 2) -> `TRAINER_HOUSE_B1F` warp 1 | "simply go downstairs in that house" |
|
||||
| 11 | `MAP_TRAINER_HOUSE_B1F` | `maps/TrainerHouseB1F.asm` | warp 1 at (9, 4) | same warp back up | coord_event at (7, 3) runs `TrainerHouseReceptionistScript`, the once-a-day CAL battle |
|
||||
| 12 | `MAP_ROUTE_1` | `maps/Route1.asm` | north connection from `VIRIDIAN_CITY` (`data/maps/attributes.asm:267`) | south connection to `PALLET_TOWN` | Bitter Berry fruit tree, Schoolboy Danny, Cooltrainerf Quinn |
|
||||
| 13 | `MAP_PALLET_TOWN` | `maps/PalletTown.asm` | north connection from `ROUTE_1` | warp 3 at (12, 11) -> `OAKS_LAB` warp 1 | Oak's badge conversation; sets `ENGINE_FLYPOINT_PALLET` |
|
||||
| 14 | `MAP_OAKS_LAB` | `maps/OaksLab.asm` | warp 1 at (4, 11) / warp 2 at (5, 11) | either warp -> `PALLET_TOWN` warp 3 | "Talk with Professor Oak in his lab" |
|
||||
| 15 | hand-off | - | - | south connection from `PALLET_TOWN` to `ROUTE_21` (`data/maps/attributes.asm:273`) | "Now, surf south onto Route 21" - Route 21 belongs to the next section; stopping here. |
|
||||
|
||||
`ROUTE_2` and `VIRIDIAN_FOREST` are named in the walkthrough only as the walking route from Pewter back to Viridian; they are not covered here.
|
||||
|
||||
---
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_ROUTE_3
|
||||
|
||||
- Script: `maps/Route3.asm` (`Route3_MapEvents` = `50:514b`)
|
||||
- Blocks: `maps/Route3.blk`
|
||||
- Header: `data/maps/maps.asm:321` -> `map Route3, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_3, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:296` -> `map_const ROUTE_3, 30, 9` (30x9 blocks = 60x18 walk cells), group `PEWTER`
|
||||
- Connections: `data/maps/attributes.asm:377` -> west `PewterCity` (offset -5), east `Route4` (offset 0). No north/south.
|
||||
- Scene scripts: none (`def_scene_scripts` is empty). Callbacks: none.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 52 | 1 | `MOUNT_MOON` | 1 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
None.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 49 | 13 | `BGEVENT_READ` | `Route3MtMoonSquareSign` -> `Route3MtMoonSquareSignText` ("MT.MOON SQUARE / Just go up the stairs.") |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE3_FISHER1` | `SPRITE_FISHER` | 26 | 12 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerFirebreatherOtis` (`50:4f26`) | -1 |
|
||||
| `ROUTE3_YOUNGSTER1` | `SPRITE_YOUNGSTER` | 11 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 2 | `TrainerYoungsterWarren` (`50:4f3a`) | -1 |
|
||||
| `ROUTE3_YOUNGSTER2` | `SPRITE_YOUNGSTER` | 20 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerYoungsterJimmy` (`50:4f4e`) | -1 |
|
||||
| `ROUTE3_FISHER2` | `SPRITE_FISHER` | 49 | 5 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerFirebreatherBurt` (`50:4f62`) | -1 |
|
||||
|
||||
Column order comes from `macros/scripts/maps.asm:113` (`object_event x, y, sprite, movement, radius_x, radius_y, hour1, hour2, palette, type, sight_range, script, event_flag`). All four here have radius 0/0 and hour limits `-1, -1` (always present).
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `TrainerFirebreatherOtis` / `TrainerYoungsterWarren` / `TrainerYoungsterJimmy` / `TrainerFirebreatherBurt`: each is a plain `trainer CLASS, ID, EVENT_BEAT_*, SeenText, BeatenText, 0, .Script` header. `.Script` is `endifjustbattled / opentext / writetext <After>Text / waitbutton / closetext / end`. No items, no flags beyond the trainer's own beaten flag, no warps.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BEAT_FIREBREATHER_OTIS` | `constants/event_flags.asm:576` | `trainer` header | set on win; trainer will not re-challenge |
|
||||
| `EVENT_BEAT_FIREBREATHER_BURT` | `constants/event_flags.asm:579` | `trainer` header | same |
|
||||
| `EVENT_BEAT_YOUNGSTER_WARREN` | `constants/event_flags.asm:971` | `trainer` header | same |
|
||||
| `EVENT_BEAT_YOUNGSTER_JIMMY` | `constants/event_flags.asm:972` | `trainer` header | same |
|
||||
|
||||
**Items**
|
||||
|
||||
None on this map.
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `WARREN` | `YOUNGSTER` | YOUNGSTER (9), line 386 | L35 FEAROW (`TRAINERTYPE_NORMAL`) | `TrainerYoungsterWarren` | none |
|
||||
| `JIMMY` | `YOUNGSTER` | YOUNGSTER (10), line 391 | L33 RATICATE, L33 ARBOK | `TrainerYoungsterJimmy` | none |
|
||||
| `OTIS` | `FIREBREATHER` | FIREBREATHER (1), line 2334 | L29 MAGMAR, L32 WEEZING, L29 MAGMAR | `TrainerFirebreatherOtis` | none |
|
||||
| `BURT` | `FIREBREATHER` | FIREBREATHER (4), line 2353 | L32 KOFFING, L32 SLUGMA | `TrainerFirebreatherBurt` | none |
|
||||
|
||||
All four are `TRAINERTYPE_NORMAL`, i.e. level+species only, moves come from the learnset.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
`data/wild/kanto_grass.asm:314` `def_grass_wildmons ROUTE_3`, rates `10 percent, 10 percent, 10 percent` (morn/day/nite). The table is version-split:
|
||||
|
||||
- `IF DEF(_GOLD)` - morn/day: L5 SPEAROW, L5 RATTATA, L8 SPEAROW, L6 JIGGLYPUFF, L10 RATTATA, L8 SPEAROW, L8 SPEAROW; nite: L5 RATTATA, L5 ZUBAT, L8 RATTATA, L6 JIGGLYPUFF, L10 RATTATA, L8 RATTATA, L8 RATTATA.
|
||||
- `ELIF DEF(_SILVER)` - morn/day: L5 SPEAROW, L5 RATTATA, L8 EKANS, L6 JIGGLYPUFF, L10 ARBOK, L8 SPEAROW, L8 SPEAROW; nite: L5 RATTATA, L5 ZUBAT, L8 EKANS, L6 JIGGLYPUFF, L10 ARBOK, L8 RATTATA, L8 RATTATA.
|
||||
|
||||
The walkthrough's "Rattata / Ekans / Arbok" is the **Silver** column. On Gold there is no Ekans or Arbok on Route 3. See section 6.
|
||||
|
||||
No water/fishing/headbutt entry for `ROUTE_3` in `data/wild/kanto_water.asm` or `data/wild/treemons.asm`.
|
||||
|
||||
---
|
||||
|
||||
### MAP_MOUNT_MOON
|
||||
|
||||
- Script: `maps/MountMoon.asm` (`MountMoon_MapEvents` = `42:587e`, `MountMoonRivalBattleScript` = `42:55b0`)
|
||||
- Blocks: `maps/MountMoon.blk`
|
||||
- Header: `data/maps/maps.asm:154` -> `map MountMoon, TILESET_CAVE, CAVE, LANDMARK_MT_MOON, MUSIC_MT_MOON, TRUE, PALETTE_NITE, FISHGROUP_SHORE` (the `TRUE` column is the phone/"requires flash-style" fixed palette flag position in the `map` macro; palette is `PALETTE_NITE`, so the cave is dark-toned regardless of clock)
|
||||
- Dimensions: `constants/map_constants.asm:141` -> `map_const MOUNT_MOON, 15, 9` (15x9 blocks = 30x18 walk cells), group `CERULEAN`-adjacent index 76 in its group listing
|
||||
- Attributes: `data/maps/attributes.asm:488` -> `map_attributes MountMoon, MOUNT_MOON, $09`. **No connections** - the map is entered and left only through warps.
|
||||
- Scene var: `data/maps/scenes.asm:46` -> `wMountMoonSceneID`
|
||||
|
||||
**Scene scripts** (`def_scene_scripts`, ids allocated from 0 by `macros/scripts/maps.asm:12`)
|
||||
|
||||
| scene id | constant | script |
|
||||
|---|---|---|
|
||||
| 0 | `SCENE_MOUNTMOON_RIVAL_BATTLE` | `MountMoonRivalEncounterScene` -> `sdefer MountMoonRivalBattleScript` |
|
||||
| 1 | `SCENE_MOUNTMOON_NOOP` | `MountMoonNoopScene` -> `end` |
|
||||
|
||||
Nothing in the game sets scene 0; it is the power-on default, so the very first entry to Mt. Moon fires the rival cutscene. `RunSceneScript` is polled in `engine/overworld/events.asm:256`, after `CheckTrainerEvent` and `CheckTileEvent`, so it lands on the first player-event poll after the warp completes.
|
||||
|
||||
**Callbacks**: none.
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 3 | `ROUTE_3` | 1 |
|
||||
| 2 | 15 | 15 | `ROUTE_4` | 1 |
|
||||
| 3 | 13 | 3 | `MOUNT_MOON` | 7 |
|
||||
| 4 | 15 | 11 | `MOUNT_MOON` | 8 |
|
||||
| 5 | 25 | 5 | `MOUNT_MOON_SQUARE` | 1 |
|
||||
| 6 | 25 | 15 | `MOUNT_MOON_SQUARE` | 2 |
|
||||
| 7 | 25 | 3 | `MOUNT_MOON` | 3 |
|
||||
| 8 | 25 | 13 | `MOUNT_MOON` | 4 |
|
||||
|
||||
Warps 3<->7 and 4<->8 are the two internal ladder pairs. Read as a graph:
|
||||
`ROUTE_3 -(1)- (3,3) ... (13,3) -(3/7)- (25,3) ... (25,5) -(5)- SQUARE`
|
||||
and `SQUARE -(2/6)- (25,15) ... (25,13) -(8/4)- (15,11) ... (15,15) -(2)- ROUTE_4`.
|
||||
|
||||
**Coord events**: none. **BG events**: none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `MOUNTMOON_RIVAL` | `SPRITE_RIVAL` | 7 | 3 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT`, sight 0 | `ObjectEvent` (the shared ROM0 stub) | `EVENT_MT_MOON_RIVAL` |
|
||||
|
||||
Flag semantics (`engine/overworld/scripting.asm:879-898`): `appear` clears the object's event flag, `disappear` sets it. `EVENT_MT_MOON_RIVAL` therefore starts **clear** = rival visible, and `disappear MOUNTMOON_RIVAL` at the end of the cutscene sets it = rival gone forever.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
`MountMoonRivalBattleScript` (`42:55b0`), reached through `sdefer` from scene 0:
|
||||
|
||||
1. `turnobject PLAYER, RIGHT`, `showemote EMOTE_SHOCK, PLAYER, 15`, `special FadeOutMusic`, `pause 15`.
|
||||
2. `applymovement MOUNTMOON_RIVAL, MountMoonRivalMovementBefore` = three `step LEFT` (rival walks from (7,3) toward the player at the (3,3) entrance).
|
||||
3. `playmusic MUSIC_RIVAL_ENCOUNTER`, `opentext`, `writetext MountMoonRivalTextBefore`, `waitbutton`, `closetext`.
|
||||
4. Starter branch:
|
||||
- `checkevent EVENT_GOT_TOTODILE_FROM_ELM` -> `.Totodile` -> `loadtrainer RIVAL2, RIVAL2_1_CHIKORITA`
|
||||
- `checkevent EVENT_GOT_CHIKORITA_FROM_ELM` -> `.Chikorita` -> `loadtrainer RIVAL2, RIVAL2_1_CYNDAQUIL`
|
||||
- fallthrough (player took Cyndaquil) -> `loadtrainer RIVAL2, RIVAL2_1_TOTODILE`
|
||||
Note the label names describe the *player's* starter; the constant names describe the *rival's*. Each arm does `winlosstext MountMoonRivalTextWin, MountMoonRivalTextLoss`, `setlasttalked MOUNTMOON_RIVAL`, `startbattle`, `dontrestartmapmusic`, `reloadmapafterbattle`.
|
||||
5. `.FinishBattle`: `playmusic MUSIC_RIVAL_AFTER`, after-text, `turnobject PLAYER, UP`, `turnobject PLAYER, RIGHT`, `applymovement MOUNTMOON_RIVAL, MountMoonRivalMovementAfter` (`RIGHT, RIGHT, DOWN x5`), `disappear MOUNTMOON_RIVAL`, `setscene SCENE_MOUNTMOON_NOOP`, `setevent EVENT_BEAT_RIVAL_IN_MT_MOON`, `playmapmusic`, `end`.
|
||||
|
||||
The battle is unavoidable: there is no `iftrue` skip and no branch that reaches the rest of the map without running it, because the scene id is 0 until `setscene` runs at the very end of the script.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_MT_MOON_RIVAL` | `constants/event_flags.asm:1308` | object row; `disappear` in `MountMoonRivalBattleScript` | clear = rival object on the map |
|
||||
| `EVENT_BEAT_RIVAL_IN_MT_MOON` | `constants/event_flags.asm:456` | set here; read by `maps/DragonsDenB1F.asm:13` and `maps/IndigoPlateauPokecenter1F.asm:45,67` | unlocks both post-game rival re-encounters |
|
||||
| `EVENT_GOT_TOTODILE_FROM_ELM` | `constants/event_flags.asm:37` | read here | selects rival party |
|
||||
| `EVENT_GOT_CHIKORITA_FROM_ELM` | `constants/event_flags.asm:38` | read here | selects rival party |
|
||||
| `SCENE_MOUNTMOON_RIVAL_BATTLE` = 0 | `maps/MountMoon.asm:6` (allocated by `def_scene_scripts`) | `wMountMoonSceneID` | 0 = cutscene armed |
|
||||
| `SCENE_MOUNTMOON_NOOP` = 1 | `maps/MountMoon.asm:7` | `setscene` at end of script | 1 = cutscene spent |
|
||||
|
||||
**Items**: none on this map.
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `RIVAL2_1_CHIKORITA` | `RIVAL2` (`constants/trainer_constants.asm:424`) | RIVAL2 (1), line 2042 | L41 SNEASEL (Quick Attack, Screech, Faint Attack, Fury Cutter); L42 GOLBAT (Leech Life, Bite, Confuse Ray, Wing Attack); L41 MAGNETON (ThunderShock, SonicBoom, Thunder Wave, Swift); L43 GENGAR (Mean Look, Curse, Shadow Ball, Confuse Ray); L43 ALAKAZAM (Disable, Recover, Future Sight, Psychic); **L45 MEGANIUM** (Razor Leaf, PoisonPowder, Body Slam, Light Screen) | `MountMoonRivalBattleScript` `.Totodile` | rematch at Indigo Plateau / Dragon's Den, see gates |
|
||||
| `RIVAL2_1_CYNDAQUIL` | `RIVAL2` | RIVAL2 (2), line 2052 | same first five, **L45 TYPHLOSION** (SmokeScreen, Quick Attack, Flame Wheel, Swift) | `.Chikorita` | same |
|
||||
| `RIVAL2_1_TOTODILE` | `RIVAL2` | RIVAL2 (3), line 2062 | same first five, **L45 FERALIGATR** (Rage, Water Gun, Scary Face, Slash) | fallthrough | same |
|
||||
|
||||
`TRAINERTYPE_MOVES`, so the four move slots above are exactly what the rival fields. `RIVAL2_2_*` (rows 4-6, levels 45-50 with Crobat) are the Indigo Plateau rematch parties, not this fight.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
`data/wild/kanto_grass.asm:33` `def_grass_wildmons MOUNT_MOON`, rates `6 percent, 6 percent, 6 percent`.
|
||||
|
||||
- `IF DEF(_GOLD)` - identical morn/day/nite: L6 ZUBAT, L8 GEODUDE, L8 SANDSHREW, L12 PARAS, L10 SANDSLASH, L8 CLEFAIRY, L8 CLEFAIRY.
|
||||
- `ELIF DEF(_SILVER)` - L6 ZUBAT, L8 GEODUDE, L8 ZUBAT, L12 PARAS, L10 GEODUDE, L8 CLEFAIRY, L8 CLEFAIRY.
|
||||
|
||||
The walkthrough's "Zubat / Parus [sic, Paras] / Geodude" is a subset of both columns. Gold additionally has Sandshrew and Sandslash, and both versions have a 2-slot Clefairy line the walkthrough does not mention.
|
||||
|
||||
---
|
||||
|
||||
### MAP_MOUNT_MOON_SQUARE
|
||||
|
||||
- Script: `maps/MountMoonSquare.asm` (`MountMoonSquare_MapEvents` = `5b:6827`, `ClefairyDance` = `5b:676a`)
|
||||
- Blocks: `maps/MountMoonSquare.blk`
|
||||
- Header: `data/maps/maps.asm:342` -> `map MountMoonSquare, TILESET_KANTO, ROUTE, LANDMARK_MT_MOON, MUSIC_MT_MOON_SQUARE, FALSE, PALETTE_AUTO, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:316` -> `map_const MOUNT_MOON_SQUARE, 15, 9` (30x18 walk cells), group `FAST_SHIP`
|
||||
- Attributes: `data/maps/attributes.asm:595` -> `$2d`, no connections
|
||||
- Scene var: `data/maps/scenes.asm:66` -> `wMountMoonSquareSceneID`
|
||||
|
||||
**Scene scripts**
|
||||
|
||||
| scene id | constant | script |
|
||||
|---|---|---|
|
||||
| 0 | `SCENE_MOUNTMOONSQUARE_CLEFAIRY_DANCE` | `MountMoonSquareNoopScene` -> `end` (the constant only exists to name the coord_event's scene, the scene script itself is a no-op and the id is never changed) |
|
||||
|
||||
**Callbacks**
|
||||
|
||||
- `MAPCALLBACK_NEWMAP` -> `MountMoonSquareDisappearMoonStoneCallback`: `setevent EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE` / `endcallback`. Every fresh entry re-hides the Moon Stone.
|
||||
- `MAPCALLBACK_OBJECTS` -> `MountMoonSquareDisappearRockCallback`: `disappear MOUNTMOONSQUARE_ROCK` / `endcallback`. The smashable rock is hidden on every object refresh; it exists on-screen only during the dance cutscene, which `appear`s it mid-script.
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 20 | 5 | `MOUNT_MOON` | 5 |
|
||||
| 2 | 22 | 11 | `MOUNT_MOON` | 6 |
|
||||
| 3 | 13 | 7 | `MOUNT_MOON_GIFT_SHOP` | 1 |
|
||||
|
||||
**Coord events**
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_MOUNTMOONSQUARE_CLEFAIRY_DANCE` (0) | 7 | 11 | `ClefairyDance` (`5b:676a`) | the Monday-night Clefairy cutscene; always armed because the scene id is never advanced |
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 7 | 7 | `BGEVENT_ITEM` | `MountMoonSquareHiddenMoonStone` (`5b:67e8`) = `hiddenitem MOON_STONE, EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE` |
|
||||
| 17 | 7 | `BGEVENT_READ` | `DontLitterSign` -> `DontLitterSignText` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `MOUNTMOONSQUARE_FAIRY1` | `SPRITE_FAIRY` | 6 | 6 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, sight 0 | `ObjectEvent` | `EVENT_MT_MOON_SQUARE_CLEFAIRY` |
|
||||
| `MOUNTMOONSQUARE_FAIRY2` | `SPRITE_FAIRY` | 7 | 6 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, sight 0 | `ObjectEvent` | `EVENT_MT_MOON_SQUARE_CLEFAIRY` |
|
||||
| `MOUNTMOONSQUARE_ROCK` | `SPRITE_ROCK` | 7 | 7 | `SPRITEMOVEDATA_SMASHABLE_ROCK` | `OBJECTTYPE_SCRIPT`, sight 0 | `MtMoonSquareRock` (`5b:67ee`) = `jumpstd SmashRockScript` | `EVENT_MT_MOON_SQUARE_ROCK` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
`ClefairyDance` - the whole cutscene is guarded by three conditions in a row, all of which must pass:
|
||||
|
||||
```
|
||||
checkflag ENGINE_MT_MOON_SQUARE_CLEFAIRY ; iftrue .NoDancing (once per save)
|
||||
readvar VAR_WEEKDAY / ifnotequal MONDAY, .NoDancing
|
||||
checktime NITE / iffalse .NoDancing
|
||||
```
|
||||
|
||||
Then: `appear` both fairies, `applymovement PLAYER, PlayerWalksUpToDancingClefairies` (one `step UP`, so the player ends on (7, 10)), `pause 15`, `appear MOUNTMOONSQUARE_ROCK`, a long `follow`/`applymovement`/`cry CLEFAIRY` dance across `ClefairyDanceStep1..7`, `showemote EMOTE_SHOCK` when the Clefairy notice the player, `applymovement ... ClefairyFleeMovement`, `disappear` both fairies, `stopfollow`, then the payoff:
|
||||
|
||||
```
|
||||
clearevent EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE
|
||||
setflag ENGINE_MT_MOON_SQUARE_CLEFAIRY
|
||||
```
|
||||
|
||||
So the Moon Stone at bg_event (7, 7) becomes takeable, but the rock object was `appear`ed onto that exact cell during the cutscene and is only cleared by `MAPCALLBACK_OBJECTS` on the next map load. That is the asm behind Thard_Verad's "Bring Rock Smash": you either smash the rock at (7, 7) in the same visit, or leave and come back (the `MAPCALLBACK_NEWMAP` callback then re-sets the Moon Stone flag and re-hides the item, so leaving loses it). See section 6.
|
||||
|
||||
`MtMoonSquareRock` -> `jumpstd SmashRockScript` (`engine/events/std_scripts.asm:199`) -> `farsjump AskRockSmashScript` (`engine/events/overworld.asm:1365`), which is `callasm HasRockSmash` (`engine/events/overworld.asm:1386`: `ld d, ROCK_SMASH / call CheckPartyMove`) then a yes/no prompt. **No badge check** - Rock Smash in Gen 2 is TM08, not an HM.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE` | `constants/event_flags.asm:236` | set by `MAPCALLBACK_NEWMAP`; cleared at the end of `ClefairyDance`; consumed by `hiddenitem` | set = Moon Stone not present |
|
||||
| `EVENT_MT_MOON_SQUARE_CLEFAIRY` | `constants/event_flags.asm:1307` | object rows; `appear`/`disappear` in `ClefairyDance` | clear = the two Clefairy are on the map (only during the cutscene) |
|
||||
| `EVENT_MT_MOON_SQUARE_ROCK` | `constants/event_flags.asm:1306` | object row; `disappear` in the OBJECTS callback, `appear` mid-cutscene | clear = rock is standing on (7, 7) |
|
||||
| `ENGINE_MT_MOON_SQUARE_CLEFAIRY` | `constants/engine_flags.asm:106` | `checkflag`/`setflag` in `ClefairyDance` | set = the dance has been seen once and will never replay |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `MOON_STONE` | hidden item on (7, 7), only after the Clefairy dance clears its flag | `bg_event 7, 7, BGEVENT_ITEM, MountMoonSquareHiddenMoonStone` | `EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE` |
|
||||
|
||||
**Trainers**: none. **Wild encounters**: no `MOUNT_MOON_SQUARE` entry in `data/wild/kanto_grass.asm` or `kanto_water.asm`.
|
||||
|
||||
---
|
||||
|
||||
### MAP_MOUNT_MOON_GIFT_SHOP
|
||||
|
||||
- Script: `maps/MountMoonGiftShop.asm` (`MountMoonGiftShop_MapEvents` = `5b:68b6`, `MountMoonGiftShopClerkScript` = `5b:6877`)
|
||||
- Blocks: `data/maps/blocks.asm:701` -> `INCBIN "maps/GiftShop.blk"` (shared blocks file, no `MountMoonGiftShop.blk`)
|
||||
- Header: `data/maps/maps.asm:343` -> `map MountMoonGiftShop, TILESET_TRADITIONAL_HOUSE, INDOOR, LANDMARK_MT_MOON, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:317` -> `map_const MOUNT_MOON_GIFT_SHOP, 4, 4` (8x8 walk cells)
|
||||
- Attributes: `data/maps/attributes.asm:596` -> `$00`, no connections
|
||||
- No scene scripts, no callbacks.
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 7 | `MOUNT_MOON_SQUARE` | 3 |
|
||||
| 2 | 4 | 7 | `MOUNT_MOON_SQUARE` | 3 |
|
||||
|
||||
**Coord events**: none. **BG events**: none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `MOUNTMOONGIFTSHOP_GRAMPS1` | `SPRITE_GRAMPS` | 4 | 3 | `SPRITEMOVEDATA_STANDING_DOWN`, hours `-1, MORN` | `OBJECTTYPE_SCRIPT` | `MountMoonGiftShopClerkScript` | -1 |
|
||||
| `MOUNTMOONGIFTSHOP_GRAMPS2` | `SPRITE_GRAMPS` | 1 | 2 | `SPRITEMOVEDATA_STANDING_RIGHT`, hours `-1, DAY` | `OBJECTTYPE_SCRIPT` | `MountMoonGiftShopClerkScript` | -1 |
|
||||
| `MOUNTMOONGIFTSHOP_LASS1` | `SPRITE_LASS` | 1 | 6 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 1,0), hours `-1, MORN` | `OBJECTTYPE_SCRIPT` | `MountMoonGiftShopLassScript` | -1 |
|
||||
| `MOUNTMOONGIFTSHOP_LASS2` | `SPRITE_LASS` | 5 | 4 | `SPRITEMOVEDATA_WALK_UP_DOWN` (radius 0,1), hours `-1, DAY` | `OBJECTTYPE_SCRIPT` | `MountMoonGiftShopLassScript` | -1 |
|
||||
|
||||
The `hour1 = -1` form means `hour2` is a time-of-day mask (`macros/scripts/maps.asm:123`). **There is no NITE clerk**: at night the shop is unattended, which the walkthrough does not mention.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
`MountMoonGiftShopClerkScript`: `faceplayer / opentext / pokemart MARTTYPE_STANDARD, MART_MT_MOON / closetext / end`.
|
||||
|
||||
**Items** (`MartMtMoon`, `data/items/marts.asm:374`, 6 items)
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `POKE_DOLL` | bought | `MartMtMoon` | - |
|
||||
| `FRESH_WATER` | bought | `MartMtMoon` | - |
|
||||
| `SODA_POP` | bought | `MartMtMoon` | - |
|
||||
| `LEMONADE` | bought | `MartMtMoon` | - |
|
||||
| `REPEL` | bought | `MartMtMoon` | - |
|
||||
| `PORTRAITMAIL` | bought | `MartMtMoon` | - |
|
||||
|
||||
The walkthrough names only Fresh Water / Soda Pop / Lemonade; the other three are real and in the same list.
|
||||
|
||||
**Trainers**: none. **Wild encounters**: none.
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_4
|
||||
|
||||
- Script: `maps/Route4.asm` (`Route4_MapEvents` = `50:5338`)
|
||||
- Blocks: `maps/Route4.blk`
|
||||
- Header: `data/maps/maps.asm:216` -> `map Route4, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_4, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_LAKE`
|
||||
- Dimensions: `constants/map_constants.asm:198` -> `map_const ROUTE_4, 20, 9` (40x18 walk cells), group `CERULEAN`
|
||||
- Connections: `data/maps/attributes.asm:381` -> west `Route3` (offset 0), east `CeruleanCity` (offset -5)
|
||||
- No scene scripts, no callbacks.
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 2 | 5 | `MOUNT_MOON` | 2 |
|
||||
|
||||
**Coord events**: none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 3 | 7 | `BGEVENT_READ` | `MtMoonSquareSign` -> `MtMoonSquareSignText` |
|
||||
| 10 | 3 | `BGEVENT_ITEM` | `Route4HiddenUltraBall` (`50:51d2`) = `hiddenitem ULTRA_BALL, EVENT_ROUTE_4_HIDDEN_ULTRA_BALL` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE4_YOUNGSTER` | `SPRITE_YOUNGSTER` | 17 | 9 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerBirdKeeperHank` (`50:5191`) | -1 |
|
||||
| `ROUTE4_LASS1` | `SPRITE_LASS` | 10 | 8 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_TRAINER`, sight 5 | `TrainerPicnickerHope` (`50:51a5`) | -1 |
|
||||
| `ROUTE4_LASS2` | `SPRITE_LASS` | 21 | 6 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerPicnickerSharon` (`50:51b9`) | -1 |
|
||||
| `ROUTE4_POKE_BALL` | `SPRITE_POKE_BALL` | 26 | 3 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `Route4HPUp` (`50:51d0`) = `itemball HP_UP` | `EVENT_ROUTE_4_HP_UP` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
All three trainer scripts are the standard `endifjustbattled / opentext / writetext <After>Text / waitbutton / closetext / end`. `Route4HPUp` is not bytecode at all - `itemball HP_UP` is two raw bytes (`macros/scripts/maps.asm`, `dwb`-style), read by the engine's item-ball path rather than the script VM.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BEAT_BIRD_KEEPER_HANK` | `constants/event_flags.asm:501` | `trainer` header | set on win |
|
||||
| `EVENT_BEAT_PICNICKER_HOPE` | `constants/event_flags.asm:641` | `trainer` header | set on win |
|
||||
| `EVENT_BEAT_PICNICKER_SHARON` | `constants/event_flags.asm:642` | `trainer` header | set on win |
|
||||
| `EVENT_ROUTE_4_HP_UP` | `constants/event_flags.asm:1322` | item-ball object row | set = ball already taken (object hidden) |
|
||||
| `EVENT_ROUTE_4_HIDDEN_ULTRA_BALL` | `constants/event_flags.asm:241` | `hiddenitem` | set = Ultra Ball already found |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `HP_UP` | walk onto / face and A the Poke Ball object at (26, 3) | `object_event ... OBJECTTYPE_ITEMBALL, 0, Route4HPUp` | `EVENT_ROUTE_4_HP_UP` |
|
||||
| `ULTRA_BALL` | hidden, face (10, 3) and press A, or Itemfinder | `bg_event 10, 3, BGEVENT_ITEM, Route4HiddenUltraBall` | `EVENT_ROUTE_4_HIDDEN_ULTRA_BALL` |
|
||||
|
||||
The walkthrough lists only the HP Up.
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `HOPE` | `PICNICKER` | PICNICKER (6), line 2611 | L34 FLAAFFY | `TrainerPicnickerHope` | none |
|
||||
| `HANK` | `BIRD_KEEPER` | BIRD_KEEPER (8), line 569 | L12 PIDGEY, L34 PIDGEOT | `TrainerBirdKeeperHank` | none |
|
||||
| `SHARON` | `PICNICKER` | PICNICKER (7), line 2616 | L31 FURRET, L33 RAPIDASH | `TrainerPicnickerSharon` | none |
|
||||
|
||||
All `TRAINERTYPE_NORMAL`. The L12 Pidgey in Hank's party is not a typo in the walkthrough; it really is level 12.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
- Grass: `data/wild/kanto_grass.asm:369` `def_grass_wildmons ROUTE_4`, rates `10/10/10 percent`.
|
||||
- `IF DEF(_GOLD)` - morn/day: L5 SPEAROW, L5 RATTATA, L8 SPEAROW, L6 JIGGLYPUFF, L10 RATTATA, L8 SPEAROW, L8 SPEAROW; nite: L5 SPEAROW, L5 ZUBAT, L8 RATTATA, L6 JIGGLYPUFF, L10 RATTATA, L8 RATTATA, L8 RATTATA.
|
||||
- `ELIF DEF(_SILVER)` - morn/day: L5 SPEAROW, L5 RATTATA, L8 EKANS, L6 JIGGLYPUFF, L10 ARBOK, L8 SPEAROW, L8 SPEAROW; nite: L5 RATTATA, L5 ZUBAT, L8 EKANS, L6 JIGGLYPUFF, L10 ARBOK, L8 RATTATA, L8 RATTATA.
|
||||
The walkthrough's "Rattata / Spearow / Arbok" is the Silver column again.
|
||||
- Water: `data/wild/kanto_water.asm:5` `def_water_wildmons ROUTE_4`, 4 percent: L10 GOLDEEN, L5 GOLDEEN, L10 SEAKING.
|
||||
- Fishing group `FISHGROUP_LAKE` from the map header.
|
||||
|
||||
---
|
||||
|
||||
### MAP_VIRIDIAN_CITY
|
||||
|
||||
- Script: `maps/ViridianCity.asm` (`ViridianCity_MapEvents` = `4e:4486`)
|
||||
- Blocks: `maps/ViridianCity.blk`
|
||||
- Header: `data/maps/maps.asm:457` -> `map ViridianCity, TILESET_KANTO, TOWN, LANDMARK_VIRIDIAN_CITY, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_AUTO, FISHGROUP_POND`
|
||||
- Dimensions: `constants/map_constants.asm:423` -> `map_const VIRIDIAN_CITY, 20, 18` (40x36 walk cells), group `VIRIDIAN`
|
||||
- Connections: `data/maps/attributes.asm:259` -> north `Route2` (offset 5), south `Route1` (offset 10), west `Route22` (offset 4)
|
||||
- Spawn: `data/maps/spawn_points.asm:15` -> `spawn VIRIDIAN_CITY, 23, 26`; flypoint `data/maps/flypoints.asm:19` -> `db LANDMARK_VIRIDIAN_CITY, SPAWN_VIRIDIAN`
|
||||
|
||||
**Scene scripts**: none. **Callbacks**: `MAPCALLBACK_NEWMAP` -> `ViridianCityFlypointCallback` = `setflag ENGINE_FLYPOINT_VIRIDIAN / endcallback`.
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 32 | 7 | `VIRIDIAN_GYM` | 1 |
|
||||
| 2 | 21 | 9 | `VIRIDIAN_NICKNAME_SPEECH_HOUSE` | 1 |
|
||||
| 3 | 23 | 15 | `TRAINER_HOUSE_1F` | 1 |
|
||||
| 4 | 29 | 19 | `VIRIDIAN_MART` | 2 |
|
||||
| 5 | 23 | 25 | `VIRIDIAN_POKECENTER_1F` | 1 |
|
||||
|
||||
The Trainer House (23, 15) is exactly 10 cells due north of the Pokemon Center (23, 25), which is the walkthrough's "north from the Pokemon Center".
|
||||
|
||||
**Coord events**: none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 17 | 17 | `BGEVENT_READ` | `ViridianCitySign` |
|
||||
| 27 | 7 | `BGEVENT_READ` | `ViridianGymSign` |
|
||||
| 19 | 1 | `BGEVENT_READ` | `ViridianCityWelcomeSign` |
|
||||
| 21 | 15 | `BGEVENT_READ` | `TrainerHouseSign` |
|
||||
| 24 | 25 | `BGEVENT_READ` | `ViridianCityPokecenterSign` (`jumpstd PokecenterSignScript`) |
|
||||
| 30 | 19 | `BGEVENT_READ` | `ViridianCityMartSign` (`jumpstd MartSignScript`) |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `VIRIDIANCITY_GRAMPS1` | `SPRITE_GRAMPS` | 18 | 5 | `SPRITEMOVEDATA_WANDER` (radius 2,2) | `OBJECTTYPE_SCRIPT` | `ViridianCityCoffeeGramps` | -1 |
|
||||
| `VIRIDIANCITY_GRAMPS2` | `SPRITE_GRAMPS` | 30 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ViridianCityGrampsNearGym` | -1 |
|
||||
| `VIRIDIANCITY_FISHER` | `SPRITE_FISHER` | 6 | 23 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ViridianCityDreamEaterFisher` (`4e:4032`) | -1 |
|
||||
| `VIRIDIANCITY_YOUNGSTER` | `SPRITE_YOUNGSTER` | 17 | 21 | `SPRITEMOVEDATA_WANDER` (radius 3,3) | `OBJECTTYPE_SCRIPT` | `ViridianCityYoungsterScript` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `ViridianCityFlypointCallback` - runs on `MAPCALLBACK_NEWMAP`, so simply *entering* Viridian City registers it as a Fly destination.
|
||||
- `ViridianCityDreamEaterFisher` (`4e:4032`) - `faceplayer / opentext / checkevent EVENT_GOT_TM42_DREAM_EATER / iftrue .GotDreamEater / writetext ... / promptbutton / verbosegiveitem TM_DREAM_EATER / iffalse .NoRoomForDreamEater / setevent EVENT_GOT_TM42_DREAM_EATER`. A free TM42 the walkthrough never mentions.
|
||||
- `ViridianCityGrampsNearGym` - branches on `EVENT_BLUE_IN_CINNABAR`; flavour only.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_FLYPOINT_VIRIDIAN` | `constants/engine_flags.asm:68` | set by `ViridianCityFlypointCallback` | Fly target unlocked on first visit |
|
||||
| `EVENT_GOT_TM42_DREAM_EATER` | `constants/event_flags.asm:223` | `ViridianCityDreamEaterFisher` | one-time TM |
|
||||
| `EVENT_BLUE_IN_CINNABAR` | `constants/event_flags.asm:1303` | read only, here | selects the gym gramps' text |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `TM_DREAM_EATER` (TM42) | talk to `VIRIDIANCITY_FISHER` at (6, 23) | `ViridianCityDreamEaterFisher`, `verbosegiveitem` | `EVENT_GOT_TM42_DREAM_EATER` |
|
||||
|
||||
**Trainers**: none on the overworld map.
|
||||
|
||||
**Wild encounters**: `data/wild/kanto_water.asm:124` `def_water_wildmons VIRIDIAN_CITY`, 2 percent (surf only). No grass entry for `VIRIDIAN_CITY`.
|
||||
|
||||
---
|
||||
|
||||
### MAP_TRAINER_HOUSE_1F
|
||||
|
||||
- Script: `maps/TrainerHouse1F.asm` (`TrainerHouse1F_MapEvents` = `5f:48f9`)
|
||||
- Blocks: `maps/TrainerHouse1F.blk`
|
||||
- Header: `data/maps/maps.asm:460` -> `map TrainerHouse1F, TILESET_HOUSE, INDOOR, LANDMARK_VIRIDIAN_CITY, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:425` -> `map_const TRAINER_HOUSE_1F, 5, 7` (10x14 walk cells)
|
||||
- Attributes: `data/maps/attributes.asm:659` -> `$00`, no connections
|
||||
- No scene scripts, no callbacks.
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 2 | 13 | `VIRIDIAN_CITY` | 3 |
|
||||
| 2 | 3 | 13 | `VIRIDIAN_CITY` | 3 |
|
||||
| 3 | 8 | 2 | `TRAINER_HOUSE_B1F` | 1 |
|
||||
|
||||
**Coord events**: none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 5 | 0 | `BGEVENT_READ` | `TrainerHouseSign1` |
|
||||
| 7 | 0 | `BGEVENT_READ` | `TrainerHouseSign2` |
|
||||
| 7 | 10 | `BGEVENT_READ` | `TrainerHouseIllegibleBook` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `TRAINERHOUSE1F_RECEPTIONIST` | `SPRITE_RECEPTIONIST` | 0 | 11 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `TrainerHouse1FReceptionistScript` | -1 |
|
||||
| `TRAINERHOUSE1F_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 7 | 11 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | `TrainerHouse1FCooltrainerMScript` | -1 |
|
||||
| `TRAINERHOUSE1F_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 6 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` (radius 2,0) | `OBJECTTYPE_SCRIPT` | `TrainerHouse1FCooltrainerFScript` | -1 |
|
||||
| `TRAINERHOUSE1F_YOUNGSTER` | `SPRITE_YOUNGSTER` | 4 | 8 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 2,0) | `OBJECTTYPE_SCRIPT` | `TrainerHouse1FYoungsterScript` | -1 |
|
||||
| `TRAINERHOUSE1F_GENTLEMAN` | `SPRITE_GENTLEMAN` | 2 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT` | `TrainerHouse1FGentlemanScript` | -1 |
|
||||
|
||||
Every 1F NPC is `jumptextfaceplayer` flavour. Nothing here gates the basement.
|
||||
|
||||
---
|
||||
|
||||
### MAP_TRAINER_HOUSE_B1F
|
||||
|
||||
- Script: `maps/TrainerHouseB1F.asm` (`TrainerHouseB1F_MapEvents` = `5f:4b97`, `TrainerHouseReceptionistScript` = `5f:4965`)
|
||||
- Blocks: `maps/TrainerHouseB1F.blk`
|
||||
- Header: `data/maps/maps.asm:461` -> `map TrainerHouseB1F, TILESET_FACILITY, INDOOR, LANDMARK_VIRIDIAN_CITY, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:426` -> `map_const TRAINER_HOUSE_B1F, 5, 8` (10x16 walk cells)
|
||||
- Attributes: `data/maps/attributes.asm:660` -> `$00`, no connections
|
||||
- Scene var: `data/maps/scenes.asm:15` -> `wTrainerHouseB1FSceneID`
|
||||
|
||||
**Scene scripts**
|
||||
|
||||
| scene id | constant | script |
|
||||
|---|---|---|
|
||||
| 0 | `SCENE_TRAINERHOUSEB1F_ASK_BATTLE` | `TrainerHouseB1FNoopScene` -> `end` (the id only exists to arm the coord_event; it is never changed) |
|
||||
|
||||
**Callbacks**: none.
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 9 | 4 | `TRAINER_HOUSE_1F` | 3 |
|
||||
|
||||
**Coord events**
|
||||
|
||||
| scene | x | y | script label | effect |
|
||||
|---|---|---|---|---|
|
||||
| `SCENE_TRAINERHOUSEB1F_ASK_BATTLE` (0) | 7 | 3 | `TrainerHouseReceptionistScript` (`5f:4965`) | the once-a-day CAL battle |
|
||||
|
||||
The bot walks in at (9, 4) and must reach (7, 3) - two cells left and one up. That is the walkthrough's "head left after you go downstairs and the lady will talk to you".
|
||||
|
||||
**BG events**: none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `TRAINERHOUSEB1F_RECEPTIONIST` | `SPRITE_RECEPTIONIST` | 7 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` (stub - she is only reachable through the coord_event) | -1 |
|
||||
| `TRAINERHOUSEB1F_CHRIS` | `SPRITE_CHRIS` | 6 | 11 | `SPRITEMOVEDATA_STANDING_LEFT` | `OBJECTTYPE_SCRIPT` | `ObjectEvent` | -1 |
|
||||
|
||||
`TRAINERHOUSEB1F_CHRIS` is the opponent sprite in the battle room; `setlasttalked TRAINERHOUSEB1F_CHRIS` points the battle at it.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
`TrainerHouseReceptionistScript`:
|
||||
|
||||
1. `turnobject PLAYER, UP`, `opentext`.
|
||||
2. `checkflag ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY` -> `iftrue .FoughtTooManyTimes` (refusal text, `applymovement PLAYER, Movement_TrainerHouseTurnBack`).
|
||||
3. `special TrainerHouse` (`engine/events/specials.asm:454`: reads `sMysteryGiftTrainerHouseFlag` into `wScriptVar`). True -> name from `gettrainername STRING_BUFFER_3, CAL, CAL2`; false -> `CAL, CAL3`.
|
||||
4. `yesorno`. No -> `.Declined`. Yes -> `setflag ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY`, text, `applymovement PLAYER, Movement_EnterTrainerHouseBattleRoom` (`LEFT x3, DOWN x8, LEFT, turn_head RIGHT`).
|
||||
5. `special TrainerHouse` again. True -> `loadtrainer CAL, CAL2`; false -> `.NoSpecialBattle` -> `loadtrainer CAL, CAL3`. Both arms `winlosstext TrainerHouseB1FCalBeatenText, 0`, `setlasttalked TRAINERHOUSEB1F_CHRIS`, `startbattle`, `reloadmapafterbattle`.
|
||||
6. `.End`: `applymovement PLAYER, Movement_ExitTrainerHouseBattleRoom`.
|
||||
|
||||
`CAL, CAL2` is the only trainer in the game whose party is **not** read from `data/trainers/parties.asm`: `engine/battle/read_trainer_party.asm:17-22` branches to `.cal2` and reads `sMysteryGiftTrainer` out of SRAM, and `GetTrainerName` (same file, line 326) reads `sMysteryGiftPartnerName`. With no Mystery Gift ever performed the flag is 0 and the game always fights `CAL3`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY` | `constants/engine_flags.asm:104` | `checkflag`/`setflag` here | set = already battled today; cleared by the daily reset |
|
||||
| `SCENE_TRAINERHOUSEB1F_ASK_BATTLE` = 0 | `maps/TrainerHouseB1F.asm:7` | `wTrainerHouseB1FSceneID` | always 0, so the coord_event is always armed |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `CAL3` | `CAL` (`constants/trainer_constants.asm:73`) | CAL (3), line 222 | L50 MEGANIUM, L50 TYPHLOSION, L50 FERALIGATR (`TRAINERTYPE_NORMAL`) | `TrainerHouseReceptionistScript` `.NoSpecialBattle` | once per day |
|
||||
| `CAL2` | `CAL` | CAL (2), line 214 - **dead data** | table row is L30 BAYLEEF/QUILAVA/CROCONAW, but `ReadTrainerParty` never reaches it; the real party comes from `sMysteryGiftTrainer` | `TrainerHouseReceptionistScript` main arm | Mystery Gift only |
|
||||
|
||||
The walkthrough's "Level 50 Meganium / Typhlosion / Feraligatr" is `CAL3`, and its Thard_Verad note ("Cal is a placeholder; if you Mystery Gift with anyone, they can show up") is exactly the `CAL2` branch.
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_1
|
||||
|
||||
- Script: `maps/Route1.asm` (`Route1_MapEvents` = `4e:4675`)
|
||||
- Blocks: `maps/Route1.blk`
|
||||
- Header: `data/maps/maps.asm:311` -> `map Route1, TILESET_KANTO, ROUTE, LANDMARK_ROUTE_1, MUSIC_ROUTE_1, FALSE, PALETTE_AUTO, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:287` -> `map_const ROUTE_1, 10, 18` (20x36 walk cells), group `PALLET`
|
||||
- Connections: `data/maps/attributes.asm:267` -> north `ViridianCity` (offset -10), south `PalletTown` (offset 0)
|
||||
- No scene scripts, no callbacks, **no warps** (connections only).
|
||||
|
||||
**Coord events**: none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 7 | 27 | `BGEVENT_READ` | `Route1Sign` -> `Route1SignText` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE1_YOUNGSTER` | `SPRITE_YOUNGSTER` | 7 | 17 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER`, sight 3 | `TrainerSchoolboyDanny` (`4e:4531`) | -1 |
|
||||
| `ROUTE1_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 3 | 26 | `SPRITEMOVEDATA_STANDING_RIGHT` (radius 1,0) | `OBJECTTYPE_TRAINER`, sight 4 | `TrainerCooltrainerfQuinn` (`4e:4545`) | -1 |
|
||||
| `ROUTE1_FRUIT_TREE` | `SPRITE_FRUIT_TREE` | 3 | 7 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_SCRIPT` | `Route1FruitTree` (`4e:455c`) = `fruittree FRUITTREE_ROUTE_1` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
`Route1FruitTree` is one command: `fruittree FRUITTREE_ROUTE_1` (`constants/script_constants.asm:232`). The item comes from `FruitTreeItems` (`data/items/fruit_trees.asm`), whose `ROUTE_1` row is `db BITTER_BERRY`. This is the walkthrough's "Item on Route 1: Bitter Berry" - it is a **fruit tree**, not a ground item, so it is a daily respawn rather than a one-time pickup (`engine/events/fruit_trees.asm`, `TryResetFruitTrees`).
|
||||
|
||||
Both trainer scripts are the standard `endifjustbattled` after-battle text.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BEAT_SCHOOLBOY_DANNY` | `constants/event_flags.asm:621` | `trainer` header | set on win |
|
||||
| `EVENT_BEAT_COOLTRAINERF_QUINN` | `constants/event_flags.asm:890` | `trainer` header | set on win |
|
||||
|
||||
The fruit tree's picked state is **not** an `EVENT_*` - it is a per-tree bit in the fruit-tree flag array with a daily reset (see `engine/events/fruit_trees.asm`).
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `BITTER_BERRY` | face the tree object at (3, 7) and press A | `Route1FruitTree` -> `FruitTreeItems` row `ROUTE_1` | daily per-tree flag, not an event flag |
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `DANNY` | `SCHOOLBOY` | SCHOOLBOY (5), line 435 | L31 JYNX, L31 ELECTABUZZ, L31 MAGMAR | `TrainerSchoolboyDanny` | none |
|
||||
| `QUINN` | `COOLTRAINERF` | COOLTRAINERF (14), line 948 | L38 IVYSAUR, L38 STARMIE | `TrainerCooltrainerfQuinn` | none |
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
`data/wild/kanto_grass.asm:231` `def_grass_wildmons ROUTE_1`, rates `10/10/10 percent`. **No version split.**
|
||||
|
||||
- morn/day: L2 PIDGEY, L2 RATTATA, L3 SENTRET, L3 PIDGEY, L6 FURRET, L4 PIDGEY, L4 PIDGEY.
|
||||
- nite: L2 HOOTHOOT, L2 RATTATA, L3 RATTATA, L3 HOOTHOOT, L6 RATTATA, L4 HOOTHOOT, L4 HOOTHOOT.
|
||||
|
||||
The walkthrough's "Pidgey / Furret" is the morn/day column minus Rattata and Sentret; at night there is no Pidgey or Furret at all.
|
||||
|
||||
---
|
||||
|
||||
### MAP_PALLET_TOWN
|
||||
|
||||
- Script: `maps/PalletTown.asm` (`PalletTown_MapEvents` = `4e:47aa`)
|
||||
- Blocks: `maps/PalletTown.blk`
|
||||
- Header: `data/maps/maps.asm:312` -> `map PalletTown, TILESET_KANTO, TOWN, LANDMARK_PALLET_TOWN, MUSIC_PALLET_TOWN, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN`
|
||||
- Dimensions: `constants/map_constants.asm:288` -> `map_const PALLET_TOWN, 10, 9` (20x18 walk cells), group `PALLET`
|
||||
- Connections: `data/maps/attributes.asm:271` -> north `Route1` (offset 0), south `Route21` (offset 0)
|
||||
- Spawn: `data/maps/spawn_points.asm:14` -> `spawn PALLET_TOWN, 5, 6`; flypoint `data/maps/flypoints.asm:18` -> `db LANDMARK_PALLET_TOWN, SPAWN_PALLET`
|
||||
|
||||
**Scene scripts**: none. **Callbacks**: `MAPCALLBACK_NEWMAP` -> `PalletTownFlypointCallback` = `setflag ENGINE_FLYPOINT_PALLET / endcallback`. This is the asm behind "You can now fly to Pallet Town" - it fires the moment the map loads, before any NPC is talked to.
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 5 | 5 | `REDS_HOUSE_1F` | 1 |
|
||||
| 2 | 13 | 5 | `BLUES_HOUSE` | 1 |
|
||||
| 3 | 12 | 11 | `OAKS_LAB` | 1 |
|
||||
|
||||
**Coord events**: none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 7 | 9 | `BGEVENT_READ` | `PalletTownSign` |
|
||||
| 3 | 5 | `BGEVENT_READ` | `RedsHouseSign` |
|
||||
| 13 | 13 | `BGEVENT_READ` | `OaksLabSign` |
|
||||
| 11 | 5 | `BGEVENT_READ` | `BluesHouseSign` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `PALLETTOWN_TEACHER` | `SPRITE_TEACHER` | 3 | 8 | `SPRITEMOVEDATA_WANDER` (radius 2,2) | `OBJECTTYPE_SCRIPT` | `PalletTownTeacherScript` | -1 |
|
||||
| `PALLETTOWN_FISHER` | `SPRITE_FISHER` | 12 | 14 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 2,0) | `OBJECTTYPE_SCRIPT` | `PalletTownFisherScript` | -1 |
|
||||
|
||||
Both are `jumptextfaceplayer` flavour.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_FLYPOINT_PALLET` | `constants/engine_flags.asm:67` | set by `PalletTownFlypointCallback` | Fly target unlocked on first visit |
|
||||
|
||||
**Items**: none. **Trainers**: none.
|
||||
|
||||
**Wild encounters**: `data/wild/kanto_water.asm:117` `def_water_wildmons PALLET_TOWN`, 6 percent: L35 TENTACOOL, L30 TENTACOOL, L35 TENTACRUEL. No grass entry. Fishing group `FISHGROUP_OCEAN`.
|
||||
|
||||
---
|
||||
|
||||
### MAP_OAKS_LAB
|
||||
|
||||
- Script: `maps/OaksLab.asm` (`OaksLab_MapEvents` = `59:5f33`, `Oak` = `59:58c3`)
|
||||
- Blocks: `maps/OaksLab.blk`
|
||||
- Header: `data/maps/maps.asm:316` -> `map OaksLab, TILESET_LAB, INDOOR, LANDMARK_PALLET_TOWN, MUSIC_POKEMON_TALK, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:291` -> `map_const OAKS_LAB, 5, 6` (10x12 walk cells)
|
||||
- No connections. `OaksLabNoopScene` exists but is marked `; unreferenced`; `def_scene_scripts` is empty and there are no callbacks.
|
||||
|
||||
**Warps**
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 11 | `PALLET_TOWN` | 3 |
|
||||
| 2 | 5 | 11 | `PALLET_TOWN` | 3 |
|
||||
|
||||
**Coord events**: none.
|
||||
|
||||
**BG events**
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 6,7,8,9 | 1 | `BGEVENT_READ` | `OaksLabBookshelf` (`jumpstd DifficultBookshelfScript`) |
|
||||
| 0,1,2,3 | 7 | `BGEVENT_READ` | `OaksLabBookshelf` |
|
||||
| 6,7,8,9 | 7 | `BGEVENT_READ` | `OaksLabBookshelf` |
|
||||
| 4 | 0 | `BGEVENT_READ` | `OaksLabPoster1` |
|
||||
| 5 | 0 | `BGEVENT_READ` | `OaksLabPoster2` |
|
||||
| 9 | 3 | `BGEVENT_READ` | `OaksLabTrashcan` |
|
||||
| 0 | 1 | `BGEVENT_READ` | `OaksLabPC` |
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `OAKSLAB_OAK` | `SPRITE_OAK` | 4 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `Oak` (`59:58c3`) | -1 |
|
||||
| `OAKSLAB_SCIENTIST1` | `SPRITE_SCIENTIST` | 1 | 8 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT` | `OaksAssistant1Script` | -1 |
|
||||
| `OAKSLAB_SCIENTIST2` | `SPRITE_SCIENTIST` | 8 | 9 | `SPRITEMOVEDATA_WALK_UP_DOWN` (radius 0,1) | `OBJECTTYPE_SCRIPT` | `OaksAssistant2Script` | -1 |
|
||||
| `OAKSLAB_SCIENTIST3` | `SPRITE_SCIENTIST` | 1 | 4 | `SPRITEMOVEDATA_WANDER` (radius 1,1) | `OBJECTTYPE_SCRIPT` | `OaksAssistant3Script` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
`Oak` (`59:58c3`):
|
||||
|
||||
```
|
||||
faceplayer / opentext
|
||||
checkevent EVENT_OPENED_MT_SILVER ; iftrue .CheckPokedex
|
||||
checkevent EVENT_TALKED_TO_OAK_IN_KANTO ; iftrue .CheckBadges
|
||||
writetext OakWelcomeKantoText / promptbutton / setevent EVENT_TALKED_TO_OAK_IN_KANTO
|
||||
.CheckBadges:
|
||||
readvar VAR_BADGES
|
||||
ifequal NUM_BADGES, .OpenMtSilver ; 16
|
||||
ifequal NUM_JOHTO_BADGES, .Complain ; 8 (i.e. zero Kanto badges)
|
||||
sjump .AhGood ; 9..15 badges
|
||||
.OpenMtSilver: writetext OakOpenMtSilverText / promptbutton / setevent EVENT_OPENED_MT_SILVER / sjump .CheckPokedex
|
||||
.Complain: writetext OakNoKantoBadgesText / promptbutton / sjump .CheckPokedex
|
||||
.AhGood: writetext OakYesKantoBadgesText / promptbutton / sjump .CheckPokedex
|
||||
.CheckPokedex: writetext OakLabDexCheckText / waitbutton / special ProfOaksPCBoot / writetext OakLabGoodbyeText / waitbutton / closetext / end
|
||||
```
|
||||
|
||||
`VAR_BADGES` = `constants/script_constants.asm:55`. `NUM_JOHTO_BADGES`, `NUM_KANTO_BADGES` and `NUM_BADGES = NUM_JOHTO_BADGES + NUM_KANTO_BADGES` are at `constants/ram_constants.asm:260,272,273`. The walkthrough's "He notices your Kanto badges. He tells you that he'll give you a prize if you get all the badges" is the `.AhGood` arm; the actual prize is the Mt. Silver unlock in `.OpenMtSilver`, which needs all 16.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_TALKED_TO_OAK_IN_KANTO` | `constants/event_flags.asm:224` | set on the first `Oak` conversation | skips the welcome text afterwards |
|
||||
| `EVENT_OPENED_MT_SILVER` | `constants/event_flags.asm:1265` | set when `VAR_BADGES == NUM_BADGES` | Mt. Silver / the Red fight opens |
|
||||
|
||||
**Items**: none. **Trainers**: none. **Wild encounters**: none.
|
||||
|
||||
---
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Mt. Moon rival battle is unavoidable | `maps/MountMoon.asm` `MountMoonRivalEncounterScene` / `MountMoonRivalBattleScript` (`42:55b0`), armed by `wMountMoonSceneID == SCENE_MOUNTMOON_RIVAL_BATTLE` (0) | be able to win a 6-mon L41-45 fight | `setscene SCENE_MOUNTMOON_NOOP` + `setevent EVENT_BEAT_RIVAL_IN_MT_MOON` at the end of the script. Losing does not set the flag; the scene id is unchanged so the fight re-arms on re-entry. |
|
||||
| Mt. Moon Square Moon Stone | `maps/MountMoonSquare.asm` `MountMoonSquareDisappearMoonStoneCallback` (sets `EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE` on every `MAPCALLBACK_NEWMAP`) | see the Clefairy dance | `ClefairyDance` `clearevent EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE`. Leaving the map re-sets it. |
|
||||
| Clefairy dance itself | `maps/MountMoonSquare.asm` `ClefairyDance` head | `ENGINE_MT_MOON_SQUARE_CLEFAIRY` clear **and** `VAR_WEEKDAY == MONDAY` **and** `checktime NITE` | one-shot: the script `setflag ENGINE_MT_MOON_SQUARE_CLEFAIRY` at the end |
|
||||
| Rock on the Moon Stone tile | `maps/MountMoonSquare.asm` `MtMoonSquareRock` -> `engine/events/std_scripts.asm:199 SmashRockScript` -> `engine/events/overworld.asm:1365 AskRockSmashScript` / `HasRockSmash` (line 1386) | `ROCK_SMASH` on a party member (`CheckPartyMove`) - **no badge check** | teach TM08 to anything |
|
||||
| Trainer House CAL, once per day | `maps/TrainerHouseB1F.asm` `TrainerHouseReceptionistScript` `checkflag ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY` | flag clear | the game's daily flag reset clears `ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY` |
|
||||
| Trainer House opponent identity | `engine/events/specials.asm:454 TrainerHouse` reading `sMysteryGiftTrainerHouseFlag`; party via `engine/battle/read_trainer_party.asm:17` | Mystery Gift performed | not reachable without a second cartridge; falls back to `CAL3` |
|
||||
| Fly back to Pewter/Pallet/Viridian | `engine/events/overworld.asm:544 FlyFunction .TryFly` -> `ld de, ENGINE_STORMBADGE / call CheckBadge` **plus** the destination's own `ENGINE_FLYPOINT_*` | Storm Badge + having visited the town | `ViridianCityFlypointCallback` / `PalletTownFlypointCallback` set their flags on `MAPCALLBACK_NEWMAP` |
|
||||
| Surf south from Pallet to Route 21 (next section) | `engine/events/overworld.asm:338 SurfFunction .TrySurf` -> `ld de, ENGINE_FOGBADGE / call CheckBadge` | Fog Badge + a party member that knows Surf | out of scope here |
|
||||
| Indigo Plateau rival re-encounter | `maps/IndigoPlateauPokecenter1F.asm:45,67` `PlateauRivalBattle1/2` | `EVENT_BEAT_RIVAL_IN_MT_MOON` set, `ENGINE_INDIGO_PLATEAU_RIVAL_FIGHT` clear, `VAR_WEEKDAY` not SUNDAY/TUESDAY/THURSDAY/FRIDAY/SATURDAY (so Monday or Wednesday) | matches the walkthrough exactly |
|
||||
| Dragon's Den rival sighting | `maps/DragonsDenB1F.asm:12` `DragonsDenB1FCheckRivalCallback` | `EVENT_BEAT_RIVAL_IN_MT_MOON` set and `VAR_WEEKDAY == TUESDAY` or `THURSDAY` | matches the walkthrough exactly |
|
||||
|
||||
Nothing in this section requires a badge, an HM or a key item to pass through. The only hard stop is the Mt. Moon rival battle.
|
||||
|
||||
---
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
Coordinates are asm walk cells `(x, y)` on the named map.
|
||||
|
||||
1. `ROUTE_3` - enter from the west connection from `PEWTER_CITY`. Walk east.
|
||||
2. `ROUTE_3` - trainer `ROUTE3_YOUNGSTER1` at (11, 2), sight 3 downward-ish (`STANDING_DOWN`, range 2). Precondition: `EVENT_BEAT_YOUNGSTER_WARREN` clear. Intent: battle (L35 Fearow). Postcondition: `EVENT_BEAT_YOUNGSTER_WARREN` set.
|
||||
3. `ROUTE_3` - trainer `ROUTE3_YOUNGSTER2` at (20, 4), `STANDING_RIGHT`, sight 3. Battle (L33 Raticate, L33 Arbok). Post: `EVENT_BEAT_YOUNGSTER_JIMMY`.
|
||||
4. `ROUTE_3` - trainer `ROUTE3_FISHER1` at (26, 12), `STANDING_UP`, sight 2. Battle (L29 Magmar, L32 Weezing, L29 Magmar). Post: `EVENT_BEAT_FIREBREATHER_OTIS`.
|
||||
5. `ROUTE_3` - trainer `ROUTE3_FISHER2` at (49, 5), `SPINRANDOM_FAST`, sight 3. Battle (L32 Koffing, L32 Slugma). Post: `EVENT_BEAT_FIREBREATHER_BURT`.
|
||||
6. `ROUTE_3` - optional: read the sign at (49, 13). Then heal (walkthrough advice, no flag) and save.
|
||||
7. `ROUTE_3` - step on warp 1 at (52, 1). Postcondition: player on `MOUNT_MOON` at (3, 3).
|
||||
8. `MOUNT_MOON` - do nothing; the scene script fires. Precondition: `wMountMoonSceneID == 0`, `EVENT_MT_MOON_RIVAL` clear. Intent: battle `RIVAL2` with the party chosen by `EVENT_GOT_TOTODILE_FROM_ELM` / `EVENT_GOT_CHIKORITA_FROM_ELM`. Postcondition: `EVENT_BEAT_RIVAL_IN_MT_MOON` set, `EVENT_MT_MOON_RIVAL` set, `wMountMoonSceneID = 1`.
|
||||
9. Optional detour: `MOUNT_MOON` (13, 3) warp 3 -> lands at (25, 3); walk to (25, 5) warp 5 -> `MOUNT_MOON_SQUARE` (20, 5).
|
||||
10. `MOUNT_MOON_SQUARE` - (13, 7) warp 3 -> `MOUNT_MOON_GIFT_SHOP`. Talk to the clerk (`(4, 3)` in MORN, `(1, 2)` in DAY - **no clerk at NITE**) to open `MART_MT_MOON`. Exit at (3, 7) or (4, 7).
|
||||
11. `MOUNT_MOON_SQUARE` optional, Monday night only: walk onto (7, 11) to trip `ClefairyDance`. Preconditions: `ENGINE_MT_MOON_SQUARE_CLEFAIRY` clear, `VAR_WEEKDAY == MONDAY`, `checktime NITE` true. Postcondition: `ENGINE_MT_MOON_SQUARE_CLEFAIRY` set, `EVENT_MOUNT_MOON_SQUARE_HIDDEN_MOON_STONE` cleared. Then, **without leaving the map**, face (7, 7); if the rock object is there, use Rock Smash (requires `ROCK_SMASH` in the party), then press A on (7, 7) to take `MOON_STONE`.
|
||||
12. `MOUNT_MOON_SQUARE` - (22, 11) warp 2 -> `MOUNT_MOON` (25, 15). Walk to (25, 13) warp 8 -> lands at (15, 11). Walk to (15, 15) warp 2 -> `ROUTE_4` (2, 5).
|
||||
13. `ROUTE_4` - trainer `ROUTE4_LASS1` at (10, 8), `STANDING_LEFT`, sight 5. Battle (L34 Flaaffy). Post: `EVENT_BEAT_PICNICKER_HOPE`.
|
||||
14. `ROUTE_4` - trainer `ROUTE4_YOUNGSTER` at (17, 9), `STANDING_DOWN`, sight 3. Battle (L12 Pidgey, L34 Pidgeot). Post: `EVENT_BEAT_BIRD_KEEPER_HANK`.
|
||||
15. `ROUTE_4` - trainer `ROUTE4_LASS2` at (21, 6), `STANDING_RIGHT`, sight 4. Battle (L31 Furret, L33 Rapidash). Post: `EVENT_BEAT_PICNICKER_SHARON`.
|
||||
16. `ROUTE_4` - item ball `ROUTE4_POKE_BALL` at (26, 3). Intent: take `HP_UP`. Precondition: `EVENT_ROUTE_4_HP_UP` clear. Postcondition: set.
|
||||
17. `ROUTE_4` - optional hidden item: face (10, 3), press A -> `ULTRA_BALL`. Post: `EVENT_ROUTE_4_HIDDEN_ULTRA_BALL`.
|
||||
18. Travel to `VIRIDIAN_CITY` (Fly needs `ENGINE_STORMBADGE` and the destination flypoint; the walkthrough's route is Fly to Pewter, then `ROUTE_2` south through `VIRIDIAN_FOREST`). Entering Viridian sets `ENGINE_FLYPOINT_VIRIDIAN`.
|
||||
19. `VIRIDIAN_CITY` - optional: talk to `VIRIDIANCITY_FISHER` at (6, 23) for `TM_DREAM_EATER`. Precondition: `EVENT_GOT_TM42_DREAM_EATER` clear and pack room. Post: set.
|
||||
20. `VIRIDIAN_CITY` - warp 3 at (23, 15) -> `TRAINER_HOUSE_1F` (2, 13). Then warp 3 at (8, 2) -> `TRAINER_HOUSE_B1F` (9, 4).
|
||||
21. `TRAINER_HOUSE_B1F` - walk to (7, 3) to trip the coord_event. Precondition: `ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY` clear. Answer YES. Intent: battle `CAL, CAL3` (L50 Meganium / Typhlosion / Feraligatr). Postcondition: `ENGINE_FOUGHT_IN_TRAINER_HALL_TODAY` set. The script walks the player in and out with `applymovement`; do not fight the movement.
|
||||
22. Return to `VIRIDIAN_CITY` and cross the south connection into `ROUTE_1`.
|
||||
23. `ROUTE_1` - fruit tree object at (3, 7). Face it, press A -> `BITTER_BERRY`. Daily respawn.
|
||||
24. `ROUTE_1` - trainer `ROUTE1_YOUNGSTER` at (7, 17), `SPINRANDOM_FAST`, sight 3. Battle (L31 Jynx, L31 Electabuzz, L31 Magmar). Post: `EVENT_BEAT_SCHOOLBOY_DANNY`.
|
||||
25. `ROUTE_1` - trainer `ROUTE1_COOLTRAINER_F` at (3, 26), `STANDING_RIGHT`, sight 4. Battle (L38 Ivysaur, L38 Starmie). Post: `EVENT_BEAT_COOLTRAINERF_QUINN`.
|
||||
26. `ROUTE_1` - south connection into `PALLET_TOWN`. Postcondition on map load: `ENGINE_FLYPOINT_PALLET` set.
|
||||
27. `PALLET_TOWN` - warp 3 at (12, 11) -> `OAKS_LAB` (4, 11).
|
||||
28. `OAKS_LAB` - talk to `OAKSLAB_OAK` at (4, 2). Postcondition: `EVENT_TALKED_TO_OAK_IN_KANTO` set; if `VAR_BADGES == 16`, also `EVENT_OPENED_MT_SILVER`.
|
||||
29. Exit to `PALLET_TOWN` and head south to the `ROUTE_21` connection (Surf, `ENGINE_FOGBADGE`) - next section.
|
||||
|
||||
---
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Kanto map geometry, warps, connections, bg/coord/object rows | `src/import/RomExtractorGen2.lua` (extracts all map events), `src/world/gen2/Map.lua`, `src/world/gen2/World.lua` | implemented (generic - every map in the ROM comes through the same path, no per-map Kanto code needed) |
|
||||
| Scene scripts / `wMapSceneID` (Mt. Moon rival, Trainer House coord_event) | `src/world/gen2/World.lua:1175-1183` (`GetMapSceneID` port), `:5014-5040` (coord event and scene script lookup) | implemented |
|
||||
| `sdefer` timing for the Mt. Moon rival cutscene | `src/script/gen2/Vm.lua:92-93` | partial - the comment says "sdefer queues after the map settles in retail; run immediately here", so the rival scene starts a frame or two earlier than the cart. Cosmetic for a bot, visible in a screenshot diff. |
|
||||
| Map callbacks (`MAPCALLBACK_NEWMAP` / `MAPCALLBACK_OBJECTS`) - the flypoint setters and the Mt. Moon Square rock/moon-stone callbacks | `src/world/gen2/World.lua:5969`, driver `tests/drivers/gold_map_callbacks.lua` | implemented, with an assertion driver |
|
||||
| `appear` / `disappear` object visibility via the event bitfield | `src/world/gen2/Events.lua` (header comment cites `CheckObjectFlag`) | implemented |
|
||||
| Overworld trainers: `trainer` struct, eyesight, party build | `src/world/gen2/Trainers.lua`, `src/world/gen2/World.lua:5257-5264`, driver `tests/drivers/gold_trainer_smoke.lua` | implemented |
|
||||
| `loadtrainer` / `startbattle` / `winlosstext` / `setlasttalked` (the rival and CAL fights) | `src/script/gen2/Opcodes.lua:99,100,105,109`, `src/script/gen2/Vm.lua:806` | implemented |
|
||||
| Trainer House daily CAL battle + Mystery Gift fallback | `src/world/gen2/TrainerHouse.lua` | implemented (deliberately answers `CAL3`; Mystery Gift is out of scope, documented in the file header) |
|
||||
| Cutscene opcodes used by `ClefairyDance` and the rival scene: `showemote`, `follow`, `stopfollow`, `cry`, `moveobject`, `applymovement` | `src/script/gen2/Opcodes.lua:117-137`, `src/script/gen2/Vm.lua:961`, `src/script/gen2/Movement.lua` | implemented |
|
||||
| `checktime` / `VAR_WEEKDAY` (the Monday-night Clefairy guard) | `src/script/gen2/Vm.lua:784`, `src/core/gen2/BugContest.lua:112` (`weekday`) | implemented |
|
||||
| Hidden items (`BGEVENT_ITEM`: Mt. Moon Square Moon Stone, Route 4 Ultra Ball) | `src/world/gen2/HiddenItems.lua`, wired at `src/world/gen2/World.lua:5291` | implemented |
|
||||
| Item balls (`OBJECTTYPE_ITEMBALL`: the Route 4 HP Up) | extracted at `src/import/RomExtractorGen2.lua:2968-2969` as `obj.itemball`; **nothing reads it** - `World:interact` (`src/world/gen2/World.lua:5257`) only dispatches `npc.def.trainer`, strength boulders, `npc.def.scriptKey`, bg events and hidden items | **missing** - a bot cannot pick up the Route 4 HP Up in this port today |
|
||||
| Fruit trees (`fruittree FRUITTREE_ROUTE_1` -> Bitter Berry) | `src/core/gen2/Apricorns.lua` (`FruitTreeItems` table, per-tree flag, `tryResetFruitTrees`), `src/script/gen2/Vm.lua:1191` | implemented |
|
||||
| `pokemart` / `MART_MT_MOON` | `src/script/gen2/Vm.lua:597`, `src/ui/gen2/MartMenu.lua:336-348` | implemented (list comes from the extracted `data/generated/marts.lua`) |
|
||||
| `verbosegiveitem` (Viridian TM42) | `src/script/gen2/Vm.lua:490-498` | implemented |
|
||||
| Rock Smash on the Mt. Moon Square rock | `src/world/gen2/FieldMoves.lua` (comment at :588 lists `ROCK_SMASH` among the field moves); the strength-boulder equivalent is special-cased in `World:interact` | partial - `jumpstd SmashRockScript` is a `farsjump` into ASM, so there is no bytecode for the extractor to find. `World:interact` has an explicit arm for strength boulders (`World.isStrengthBoulder`) but **no equivalent arm for `SPRITEMOVEDATA_SMASHABLE_ROCK`**, so pressing A on the Mt. Moon Square rock reaches a nil `scriptKey`. Verify by hand. |
|
||||
| Fly / flypoints (`ENGINE_FLYPOINT_PALLET` = 52, `ENGINE_FLYPOINT_VIRIDIAN` = 53) | `src/world/gen2/FieldMoves.lua:342-370` (both Kanto rows present) | implemented |
|
||||
| Kanto wild tables (Gold vs Silver split) | extracted by `src/import/RomExtractorGen2.lua` from the ROM, so whichever cart was imported is what you get | implemented (no version conditional needed - the ROM already picked) |
|
||||
|
||||
---
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
1. **Rival party order contradicts the walkthrough.** The walkthrough lists Sneasel, Golbat, *Starter Evolution*, Magneton, Gengar, Alakazam. `data/trainers/parties.asm:2042-2069` lists Sneasel(41), Golbat(42), Magneton(41), Gengar(43), Alakazam(43), **starter last at level 45**. The walkthrough's per-mon strategy text is still correct; only the ordering is wrong. Levels and species otherwise match.
|
||||
2. **Route 3 and Route 4 wild lists in the walkthrough are the Silver tables.** `data/wild/kanto_grass.asm:314` and `:369` both have `IF DEF(_GOLD) / ELIF DEF(_SILVER)` arms. Ekans and Arbok only exist in the `_SILVER` arm. On Gold, Route 3 is Spearow/Rattata/Jigglypuff (+Zubat at nite) and Route 4 is the same. The walkthrough is titled for Gold but quoted the Silver data.
|
||||
3. **Mt. Moon wild list is incomplete in the walkthrough.** Gold also has L8 SANDSHREW, L10 SANDSLASH and two L8 CLEFAIRY slots (`data/wild/kanto_grass.asm:33`). The Clefairy slots mean Clefairy can be caught in the cave at any time, independent of the Monday-night dance.
|
||||
4. **Route 1 wild list is incomplete and time-dependent.** The walkthrough lists Pidgey and Furret; the nite column (`data/wild/kanto_grass.asm:250`) has neither - it is Hoothoot and Rattata only.
|
||||
5. **"Bitter Berry" on Route 1 is a fruit tree, not a ground item.** `Route1FruitTree` = `fruittree FRUITTREE_ROUTE_1`, and `data/items/fruit_trees.asm` row `ROUTE_1` is `BITTER_BERRY`. It respawns daily rather than being a one-time pickup, which the walkthrough's "Item on Route 1" phrasing implies.
|
||||
6. **The two Mt. Moon routes described in the walkthrough could not be verified against the collision map.** The warp graph in section 2 is exact, but which of warps 2/3/4 are reachable from the (3, 3) entrance without passing through the Square depends on `maps/MountMoon.blk` collision, which was not decoded here. The walkthrough's "either way, you end up in the same spot" is consistent with warps 4<->8 and 6 meeting on the (25, 13)/(25, 15) side, but confirm by walking it.
|
||||
7. **"Bring Rock Smash" for the Clefairy Moon Stone.** The asm supports the claim indirectly: `MountMoonSquareDisappearRockCallback` hides `MOUNTMOONSQUARE_ROCK` on every `MAPCALLBACK_OBJECTS`, `ClefairyDance` `appear`s it at (7, 7) mid-cutscene, and (7, 7) is exactly the `BGEVENT_ITEM` tile the dance unlocks. Whether the rock actually blocks the tile-facing A press (as opposed to being walkable) was not verified; the item could also be reachable by facing (7, 7) from an adjacent cell. Verify in-game.
|
||||
8. **`ULTRA_BALL` at Route 4 (10, 3) and `TM_DREAM_EATER` in Viridian are absent from the walkthrough.** Both are real (`maps/Route4.asm` bg_event, `maps/ViridianCity.asm` `ViridianCityDreamEaterFisher`). Not a contradiction, just an omission worth carrying into a completionist route.
|
||||
9. **Mt. Moon Gift Shop has no NITE clerk.** Both `SPRITE_GRAMPS` rows are gated `-1, MORN` and `-1, DAY`. A bot that arrives at night finds no shop, which the walkthrough does not warn about.
|
||||
10. **`ROUTE_3` warp position vs. its sign.** The only warp is at (52, 1) but the "MT.MOON SQUARE / Just go up the stairs" sign is at (49, 13), twelve cells south. That is what the file says; it reads oddly and is worth an eyeball on the rendered map.
|
||||
11. **Port: `OBJECTTYPE_ITEMBALL` pickup is unimplemented** (see section 5). The Route 4 HP Up is the first item ball this section needs, and `World:interact` has no arm for it. Confirmed by reading `src/world/gen2/World.lua:5257-5310` and grepping the whole `src/` tree for `itemball` (only the extractor and one comment in `src/script/gen2/CallAsm.lua:547` mention it).
|
||||
12. **Port: smashable-rock objects have no interact arm** (see section 5). Same class of gap as the item ball, but lower stakes because the only rock in this section is optional.
|
||||
@@ -0,0 +1,570 @@
|
||||
# Section 27 - Cinnabar Island and Seafoam Islands Gym
|
||||
|
||||
Source: `../section-27-cinnabar-island-and-seafoam-islands-gym.txt`
|
||||
(the FAQ numbers this chapter "33 > Cinnabar Island and Seafoam Islands Gym"; the
|
||||
file index is 27)
|
||||
|
||||
Maps covered: `MAP_ROUTE_21`, `MAP_CINNABAR_ISLAND`, `MAP_CINNABAR_POKECENTER_1F`,
|
||||
`MAP_ROUTE_20`, `MAP_SEAFOAM_GYM`
|
||||
|
||||
Badges / key milestones in this section:
|
||||
|
||||
- `ENGINE_FLYPOINT_CINNABAR` (set on first entry to Cinnabar Island)
|
||||
- Blue's one-off speech on Cinnabar Island, which is what unlocks him in Viridian Gym
|
||||
(`clearevent EVENT_VIRIDIAN_GYM_BLUE`)
|
||||
- Hidden RARE_CANDY on Cinnabar Island
|
||||
- `EVENT_CINNABAR_ROCKS_CLEARED` (set silently by walking onto Route 20; this is what
|
||||
clears the boulders that seal Route 19 to the east)
|
||||
- **VOLCANOBADGE** from BLAINE in the Seafoam Islands cave (`ENGINE_VOLCANOBADGE`,
|
||||
`EVENT_BEAT_BLAINE`)
|
||||
|
||||
---
|
||||
|
||||
## 1. Route order
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 0 | `PALLET_TOWN` | - | - | south connection to `ROUTE_21` (`data/maps/attributes.asm:270-272`) | **Pallet Town belongs to the previous section; only the hop south is listed here.** |
|
||||
| 1 | `ROUTE_21` | `maps/Route21.asm` | north connection from `PALLET_TOWN` (`data/maps/attributes.asm:274-275`) | south connection to `CINNABAR_ISLAND` (`attributes.asm:277`) | Surf south. Three trainers: Nikki, Arnold, Seth |
|
||||
| 2 | `CINNABAR_ISLAND` | `maps/CinnabarIsland.asm` | north connection from `ROUTE_21` (`attributes.asm:279-280`) | warp 1 at (11,11) -> `CINNABAR_POKECENTER_1F` 1; east connection to `ROUTE_20` (`attributes.asm:281`) | Sets `ENGINE_FLYPOINT_CINNABAR`; heal; talk to Blue; hidden RARE_CANDY |
|
||||
| 3 | `CINNABAR_POKECENTER_1F` | `maps/CinnabarPokecenter1F.asm` | Cinnabar warp 1 | warps 1/2 at (3,7)/(4,7) back to `CINNABAR_ISLAND` 1 | Heal; the two flavour NPCs the walkthrough quotes ("a year since the volcano erupted", "BLAINE lives alone in the SEAFOAM ISLANDS cave") |
|
||||
| 4 | `ROUTE_20` | `maps/Route20.asm` | west connection from `CINNABAR_ISLAND` (`attributes.asm:283-284`) | warp 1 at (38,7) -> `SEAFOAM_GYM` 1 | `MAPCALLBACK_NEWMAP` sets `EVENT_CINNABAR_ROCKS_CLEARED`; Swimmer Cameron at (12,13) |
|
||||
| 5 | `SEAFOAM_GYM` | `maps/SeafoamGym.asm` | Route 20 warp 1 | warp 1 at (5,5) back to `ROUTE_20` 1 | BLAINE -> **VOLCANOBADGE** |
|
||||
|
||||
Spillover: Route 20 continues east past the gym to Swimmer Lori (45,13) and Swimmer
|
||||
Nicole (52,8) and then over the `connection east, Route19, ROUTE_19, -9` boundary into
|
||||
Fuchsia territory; the walkthrough does not visit them here and they belong to the
|
||||
following section.
|
||||
|
||||
---
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_ROUTE_21
|
||||
|
||||
- Script: `maps/Route21.asm`
|
||||
- Blocks: `maps/Route21.blk`
|
||||
- Header: `data/maps/maps.asm:198` -> `TILESET_KANTO, ROUTE, LANDMARK_ROUTE_21, MUSIC_ROUTE_3, FALSE (phone calls allowed), PALETTE_AUTO, FISHGROUP_OCEAN`
|
||||
- Dimensions: `constants/map_constants.asm:182` `map_const ROUTE_21, 10, 18` (group `CINNABAR` = 6, map id 7). 10x18 blocks = 20x36 tile coordinates.
|
||||
- Attributes / border: `data/maps/attributes.asm:274` `map_attributes Route21, ROUTE_21, $43`
|
||||
- Connections: north `PALLET_TOWN` (offset 0), south `CINNABAR_ISLAND` (offset 0)
|
||||
- Landmark position: `data/maps/landmarks.asm:96` `landmark 52, 120, Route21Name`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
None (`def_warp_events` at `maps/Route21.asm:95` is empty).
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
None (`maps/Route21.asm:97`).
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
None (`maps/Route21.asm:99`).
|
||||
|
||||
**Object events** (`def_object_events`, `maps/Route21.asm:101-104`)
|
||||
|
||||
| const | sprite | x | y | movement | type | sight | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE21_SWIMMER_GIRL` | `SPRITE_SWIMMER_GIRL` | 11 | 16 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmerfNikki` | -1 (always) |
|
||||
| `ROUTE21_SWIMMER_GUY` | `SPRITE_SWIMMER_GUY` | 2 | 30 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_TRAINER` | 4 | `TrainerSwimmermSeth` | -1 (always) |
|
||||
| `ROUTE21_FISHER` | `SPRITE_FISHER` | 14 | 22 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_TRAINER` | 1 | `TrainerFisherArnold` | -1 (always) |
|
||||
|
||||
The walkthrough's order (Nikki, then the fisherman, then "head way left then down") is
|
||||
exactly the y ordering 16 -> 22 -> 30, with Seth in the far south-west corner at x=2.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `TrainerSwimmerfNikki` (`maps/Route21.asm:22`, sym `4e:4803`) -
|
||||
`trainer SWIMMERF, NIKKI, EVENT_BEAT_SWIMMERF_NIKKI, SwimmerfNikkiSeenText, SwimmerfNikkiBeatenText, 0, .Script`.
|
||||
The `0` is the loss-text slot (no whiteout text). `.Script` is `endifjustbattled` +
|
||||
one after-battle line; no items, no flags beyond the beat flag.
|
||||
- `TrainerFisherArnold` (`maps/Route21.asm:33`, sym `4e:4817`) - same shape,
|
||||
`EVENT_BEAT_FISHER_ARNOLD`.
|
||||
- `TrainerSwimmermSeth` (`maps/Route21.asm:11`, sym `4e:47ef`) - same shape,
|
||||
`EVENT_BEAT_SWIMMERM_SETH`.
|
||||
|
||||
There are no scene scripts and no callbacks on this map (`def_scene_scripts` /
|
||||
`def_callbacks` at `maps/Route21.asm:7-9` are both empty).
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_BEAT_SWIMMERF_NIKKI` | `constants/event_flags.asm:490` | `trainer` macro row, `maps/Route21.asm:23` | set once Nikki is beaten; suppresses re-battle |
|
||||
| `EVENT_BEAT_FISHER_ARNOLD` | `constants/event_flags.asm:587` | `maps/Route21.asm:34` | as above |
|
||||
| `EVENT_BEAT_SWIMMERM_SETH` | `constants/event_flags.asm:958` | `maps/Route21.asm:12` | as above |
|
||||
|
||||
**Items**
|
||||
|
||||
None on this map.
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `SWIMMERF, NIKKI` | SWIMMERF ($27) | 17 | `; SWIMMERF (17)` at `parties.asm:1872`: L28 SEEL, L28 SEEL, L28 SEEL, L28 DEWGONG (`TRAINERTYPE_NORMAL`) | `TrainerSwimmerfNikki` | no |
|
||||
| `FISHER, ARNOLD` | FISHER ($25) | 3 | `; FISHER (3)` at `parties.asm:1511`: L34 TENTACRUEL | `TrainerFisherArnold` | no |
|
||||
| `SWIMMERM, SETH` | SWIMMERM ($26) | 18 | `; SWIMMERM (18)` at `parties.asm:1754`: L29 QUAGSIRE, L29 OCTILLERY, L32 QUAGSIRE | `TrainerSwimmermSeth` | no |
|
||||
|
||||
Payouts (base reward x last-mon level x 4, see section 3 for the citation):
|
||||
Nikki 5 x 28 x 4 = **560**, Arnold 10 x 34 x 4 = **1360**, Seth 2 x 32 x 4 = **256**.
|
||||
All three match the walkthrough exactly.
|
||||
Base rewards: `data/trainers/attributes.asm:223` (Fisher, 10), `:229` (Swimmerm, 2),
|
||||
`:235` (Swimmerf, 5).
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
- Water (`data/wild/kanto_water.asm:61-66`), 6% rate: L35 TENTACOOL / L30 TENTACOOL / L35 TENTACRUEL.
|
||||
- Grass (`data/wild/kanto_grass.asm:1005-1031`), 6/6/6% morn/day/nite. There *is* a
|
||||
grass table, on the small land tiles:
|
||||
- morn: 30 TANGELA, 25 TANGELA, 35 TANGELA, 20 TANGELA, 30 TANGELA, 28 MR__MIME, 28 MR__MIME
|
||||
- day: 30 TANGELA, 25 TANGELA, 35 TANGELA, 20 TANGELA, 28 MR__MIME, 30 MR__MIME, 30 MR__MIME
|
||||
- nite: 30 TANGELA, 25 TANGELA, 35 TANGELA, 20 TANGELA, 30 TANGELA, 28 MR__MIME, 28 MR__MIME
|
||||
- Fishing: `FISHGROUP_OCEAN` (`data/wild/fish.asm:13`). Old rod `.Ocean_Old` (`:42`)
|
||||
MAGIKARP/MAGIKARP/TENTACOOL L10; Good rod `.Ocean_Good` (`:46`) MAGIKARP L20 /
|
||||
TENTACOOL L20 / CHINCHOU L20 / `time_group 2` = SHELLDER L20 day and nite
|
||||
(`fish.asm:214`); Super rod `.Ocean_Super` (`:51`) CHINCHOU L40 / `time_group 3` =
|
||||
SHELLDER L40 (`fish.asm:215`) / TENTACRUEL L40 / LANTURN L40.
|
||||
- No headbutt tree rows and no rock smash on this map.
|
||||
|
||||
---
|
||||
|
||||
### MAP_CINNABAR_ISLAND
|
||||
|
||||
- Script: `maps/CinnabarIsland.asm`
|
||||
- Blocks: `maps/CinnabarIsland.blk`
|
||||
- Header: `data/maps/maps.asm:199` -> `TILESET_KANTO, TOWN, LANDMARK_CINNABAR_ISLAND, MUSIC_VIRIDIAN_CITY, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN`
|
||||
- Dimensions: `constants/map_constants.asm:183` `map_const CINNABAR_ISLAND, 10, 9` (group `CINNABAR` = 6, map id 8). 20x18 tile coordinates.
|
||||
- Attributes / border: `data/maps/attributes.asm:279` `map_attributes CinnabarIsland, CINNABAR_ISLAND, $43`
|
||||
- Connections: north `ROUTE_21` (offset 0), east `ROUTE_20` (offset 0)
|
||||
- Landmark: `data/maps/landmarks.asm:95` `landmark 52, 132, CinnabarIslandName`
|
||||
- Fly / respawn point: `data/maps/flypoints.asm:28` `db LANDMARK_CINNABAR_ISLAND, SPAWN_CINNABAR`;
|
||||
`data/maps/spawn_points.asm:24` `spawn CINNABAR_ISLAND, 11, 12`
|
||||
|
||||
**Warps** (`def_warp_events`, `maps/CinnabarIsland.asm:131-132`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 11 | 11 | `CINNABAR_POKECENTER_1F` | 1 |
|
||||
|
||||
**Coord events** (`def_coord_events`)
|
||||
|
||||
None (`maps/CinnabarIsland.asm:134`). Blue is a talk-to NPC, not a trip-wire.
|
||||
|
||||
**BG events** (`def_bg_events`, `maps/CinnabarIsland.asm:136-140`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 12 | 11 | `BGEVENT_READ` | `CinnabarIslandPokecenterSign` -> `jumpstd PokecenterSignScript` |
|
||||
| 9 | 11 | `BGEVENT_READ` | `CinnabarIslandGymSign` -> "CINNABAR GYM has relocated to SEAFOAM ISLANDS. BLAINE" |
|
||||
| 7 | 7 | `BGEVENT_READ` | `CinnabarIslandSign` -> town sign |
|
||||
| 9 | 1 | `BGEVENT_ITEM` | `CinnabarIslandHiddenRareCandy` -> `hiddenitem RARE_CANDY, EVENT_CINNABAR_ISLAND_HIDDEN_RARE_CANDY` (sym `4e:49a2`) |
|
||||
|
||||
**Object events** (`def_object_events`, `maps/CinnabarIsland.asm:142-143`)
|
||||
|
||||
| const | sprite | x | y | movement | type | sight | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `CINNABARISLAND_BLUE` | `SPRITE_BLUE` | 9 | 6 | `SPRITEMOVEDATA_SPINRANDOM_SLOW` | `OBJECTTYPE_SCRIPT` | 0 | `CinnabarIslandBlue` | `EVENT_BLUE_IN_CINNABAR` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `CinnabarIslandFlypointCallback` (`maps/CinnabarIsland.asm:10`, sym `4e:4981`), wired
|
||||
as `callback MAPCALLBACK_NEWMAP` at `:8`. Body is exactly
|
||||
`setflag ENGINE_FLYPOINT_CINNABAR` / `endcallback`. Walking in once is the whole
|
||||
unlock for flying here.
|
||||
- `CinnabarIslandBlue` (`maps/CinnabarIsland.asm:14`, sym `4e:4985`). Control flow, in
|
||||
order: `faceplayer`, `opentext`, `writetext CinnabarIslandBlueText`, `waitbutton`,
|
||||
`closetext`, `playsound SFX_WARP_TO`,
|
||||
`applymovement CINNABARISLAND_BLUE, CinnabarIslandBlueTeleport`,
|
||||
`disappear CINNABARISLAND_BLUE`, `clearevent EVENT_VIRIDIAN_GYM_BLUE`, `end`.
|
||||
- There is **no guard flag and no `iftrue`** at the top: the script is one-shot only
|
||||
because `disappear` sets the object's own `EVENT_BLUE_IN_CINNABAR` (see
|
||||
`Script_disappear` at `engine/overworld/scripting.asm:887`, `ld b, 1 ; set`).
|
||||
- `CinnabarIslandBlueTeleport` (`:38`) is a two-byte movement stream:
|
||||
`teleport_from` (`$4c`, `macros/scripts/movement.asm:148`) then `step_end`. That is
|
||||
the "levitates and flies off" the walkthrough describes.
|
||||
- `clearevent EVENT_VIRIDIAN_GYM_BLUE` is the load-bearing side effect: that flag is
|
||||
set at boot by `InitializeEventsScript` (`engine/events/std_scripts.asm:552`) and it
|
||||
hides both `VIRIDIANGYM_BLUE` and the Viridian gym guide
|
||||
(`maps/ViridianGym.asm:183-184`). **Talking to Blue here is the only thing that
|
||||
puts Blue in Viridian Gym.**
|
||||
- `CinnabarIslandHiddenRareCandy` (`maps/CinnabarIsland.asm:35`) - a `hiddenitem`
|
||||
operand, not a script body: `dwb EVENT_CINNABAR_ISLAND_HIDDEN_RARE_CANDY, RARE_CANDY`.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_FLYPOINT_CINNABAR` | `constants/engine_flags.asm:77` | set by `CinnabarIslandFlypointCallback` | Fly destination unlocked; also the wVisitedSpawns bit for respawn |
|
||||
| `EVENT_BLUE_IN_CINNABAR` | `constants/event_flags.asm:1303` | *set* implicitly by `disappear CINNABARISLAND_BLUE`; read by `maps/ViridianCity.asm:37` and `maps/ViridianPokecenter1F.asm:18` for "Blue returned" dialogue | clear = Blue is standing on Cinnabar; set = you have heard the speech |
|
||||
| `EVENT_VIRIDIAN_GYM_BLUE` | `constants/event_flags.asm:1304` | set at `engine/events/std_scripts.asm:552`; cleared by `CinnabarIslandBlue` | while set, Viridian Gym is empty |
|
||||
| `EVENT_CINNABAR_ISLAND_HIDDEN_RARE_CANDY` | `constants/event_flags.asm:254` | the `hiddenitem` at `maps/CinnabarIsland.asm:36` | one-shot guard on the hidden candy |
|
||||
|
||||
Note: `EVENT_BLUE_IN_CINNABAR` is **not** in the `InitializeEventsScript` list, so it
|
||||
starts clear and Blue is visible from the first time you reach the island.
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `RARE_CANDY` | face tile (9,1) and press A (or ITEMFINDER) | `bg_event 9, 1, BGEVENT_ITEM, CinnabarIslandHiddenRareCandy` | `EVENT_CINNABAR_ISLAND_HIDDEN_RARE_CANDY` |
|
||||
|
||||
The walkthrough never mentions this candy.
|
||||
|
||||
**Trainers**
|
||||
|
||||
None.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
No `def_grass_wildmons`/`def_water_wildmons` row for `CINNABAR_ISLAND` in
|
||||
`data/wild/kanto_grass.asm` or `data/wild/kanto_water.asm`. The surrounding water belongs
|
||||
to Route 20 / Route 21. Fishing on the map's own water uses `FISHGROUP_OCEAN`
|
||||
(`data/maps/maps.asm:199`).
|
||||
|
||||
---
|
||||
|
||||
### MAP_CINNABAR_POKECENTER_1F
|
||||
|
||||
- Script: `maps/CinnabarPokecenter1F.asm`
|
||||
- Header: `data/maps/maps.asm:192` -> `TILESET_POKECENTER, INDOOR, LANDMARK_CINNABAR_ISLAND, MUSIC_POKEMON_CENTER, FALSE, PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:176` `map_const CINNABAR_POKECENTER_1F, 5, 4` (group `CINNABAR` = 6, map id 1)
|
||||
- Attributes: `data/maps/attributes.asm:510` `map_attributes CinnabarPokecenter1F, CINNABAR_POKECENTER_1F, $00`; no connections
|
||||
|
||||
**Warps** (`def_warp_events`, `maps/CinnabarPokecenter1F.asm:38-41`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 7 | `CINNABAR_ISLAND` | 1 |
|
||||
| 2 | 4 | 7 | `CINNABAR_ISLAND` | 1 |
|
||||
| 3 | 0 | 7 | `POKECENTER_2F` | 1 |
|
||||
|
||||
**Coord events / BG events**
|
||||
|
||||
None (`maps/CinnabarPokecenter1F.asm:43`, `:45`).
|
||||
|
||||
**Object events** (`maps/CinnabarPokecenter1F.asm:47-50`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `CINNABARPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CinnabarPokecenter1FNurseScript` -> `jumpstd PokecenterNurseScript` | -1 |
|
||||
| `CINNABARPOKECENTER1F_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 7 | 6 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius x 2) | `OBJECTTYPE_SCRIPT` | `CinnabarPokecenter1FCooltrainerFScript` (BLAINE lives in the Seafoam cave) | -1 |
|
||||
| `CINNABARPOKECENTER1F_FISHER` | `SPRITE_FISHER` | 2 | 4 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `CinnabarPokecenter1FFisherScript` ("It's been a year since the volcano erupted.") | -1 |
|
||||
|
||||
Both flavour NPCs are unconditional `jumptextfaceplayer` - no flags, no branches. These
|
||||
are the two people the walkthrough tells you to talk to.
|
||||
|
||||
---
|
||||
|
||||
### MAP_ROUTE_20
|
||||
|
||||
- Script: `maps/Route20.asm`
|
||||
- Blocks: `maps/Route20.blk`
|
||||
- Header: `data/maps/maps.asm:197` -> `TILESET_KANTO, ROUTE, LANDMARK_ROUTE_20, MUSIC_ROUTE_3, FALSE, PALETTE_AUTO, FISHGROUP_OCEAN`
|
||||
- Dimensions: `constants/map_constants.asm:181` `map_const ROUTE_20, 30, 9` (group `CINNABAR` = 6, map id 6). 60x18 tile coordinates.
|
||||
- Attributes / border: `data/maps/attributes.asm:283` `map_attributes Route20, ROUTE_20, $43`
|
||||
- Connections: west `CINNABAR_ISLAND` (offset 0), east `ROUTE_19` (offset -9)
|
||||
- Landmarks in view: `data/maps/landmarks.asm:93` Route 20, `:94` `SeafoamIslandsName`
|
||||
|
||||
**Warps** (`def_warp_events`, `maps/Route20.asm:116-117`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 38 | 7 | `SEAFOAM_GYM` | 1 |
|
||||
|
||||
**Coord events**
|
||||
|
||||
None (`maps/Route20.asm:119`).
|
||||
|
||||
**BG events** (`maps/Route20.asm:121-122`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 37 | 11 | `BGEVENT_READ` | `CinnabarGymSign` -> "CINNABAR GYM / LEADER: BLAINE" |
|
||||
|
||||
**Object events** (`maps/Route20.asm:124-127`)
|
||||
|
||||
| const | sprite | x | y | movement | type | sight | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE20_SWIMMER_GIRL1` | `SPRITE_SWIMMER_GIRL` | 52 | 8 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmerfNicole` | -1 |
|
||||
| `ROUTE20_SWIMMER_GIRL2` | `SPRITE_SWIMMER_GIRL` | 45 | 13 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmerfLori` | -1 |
|
||||
| `ROUTE20_SWIMMER_GUY` | `SPRITE_SWIMMER_GUY` | 12 | 13 | `SPRITEMOVEDATA_SPINRANDOM_FAST` | `OBJECTTYPE_TRAINER` | 3 | `TrainerSwimmermCameron` | -1 |
|
||||
|
||||
Cameron at x=12 is the only one west of the gym door at x=38, which is why the
|
||||
walkthrough sees "one trainer" on the way in. Lori (45) and Nicole (52) are east of it.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route20ClearRocksCallback` (`maps/Route20.asm:12`, sym `4e:4cfa`), wired as
|
||||
`callback MAPCALLBACK_NEWMAP` at `:10`. Body is `setevent EVENT_CINNABAR_ROCKS_CLEARED`
|
||||
/ `endcallback`. It fires silently the first time the map loads.
|
||||
Consumers: `Route19ClearRocksCallback` (`maps/Route19.asm:15`, sym `4e:4f09`, a
|
||||
`MAPCALLBACK_TILES` callback) draws six `changeblock ... $7a ; rock` blocks at
|
||||
(6,6), (8,6), (10,6), (12,8), (4,8), (10,10) **while the flag is clear**; the officer
|
||||
in `maps/Route19FuchsiaGate.asm:12` and the two fishers in `maps/Route19.asm:74`/`:90`
|
||||
swap dialogue on it.
|
||||
- `TrainerSwimmermCameron` (`maps/Route20.asm:38`, sym `4e:4d26`) - plain trainer,
|
||||
`EVENT_BEAT_SWIMMERM_CAMERON`, `endifjustbattled` + one line.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_CINNABAR_ROCKS_CLEARED` | `constants/event_flags.asm:214` | set by `Route20ClearRocksCallback`; read by `maps/Route19.asm:16`, `:74`, `:90` and `maps/Route19FuchsiaGate.asm:12` | while clear, six rock blocks seal the Route 19 water path from the Fuchsia side |
|
||||
| `EVENT_BEAT_SWIMMERM_CAMERON` | `constants/event_flags.asm:957` | `maps/Route20.asm:39` | beat flag |
|
||||
| `EVENT_BEAT_SWIMMERF_NICOLE` | `constants/event_flags.asm:487` | `maps/Route20.asm:17` | beat flag (next section) |
|
||||
| `EVENT_BEAT_SWIMMERF_LORI` | `constants/event_flags.asm:488` | `maps/Route20.asm:28` | beat flag (next section) |
|
||||
|
||||
**Items**
|
||||
|
||||
None on this map.
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `SWIMMERM, CAMERON` | SWIMMERM ($26) | 17 | `; SWIMMERM (17)` at `parties.asm:1749`: L34 MARILL | `TrainerSwimmermCameron` | no |
|
||||
| `SWIMMERF, NICOLE` | SWIMMERF ($27) | 14 | `; SWIMMERF (14)` at `parties.asm:1854`: L29 MARILL, L29 MARILL, L32 LAPRAS | `TrainerSwimmerfNicole` | no |
|
||||
| `SWIMMERF, LORI` | SWIMMERF ($27) | 15 | `; SWIMMERF (15)` at `parties.asm:1861`: L32 STARMIE, L32 STARMIE | `TrainerSwimmerfLori` | no |
|
||||
|
||||
Cameron pays 2 x 34 x 4 = **272**, matching the walkthrough.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
- Water (`data/wild/kanto_water.asm:54-59`), 6% rate: L35 TENTACOOL / L30 TENTACOOL / L35 TENTACRUEL.
|
||||
- No `def_grass_wildmons ROUTE_20` row exists in `data/wild/kanto_grass.asm` - there is
|
||||
no land grass on this route.
|
||||
- Fishing: `FISHGROUP_OCEAN`, same three rod tables as Route 21 above.
|
||||
|
||||
---
|
||||
|
||||
### MAP_SEAFOAM_GYM
|
||||
|
||||
- Script: `maps/SeafoamGym.asm`
|
||||
- Blocks: `maps/SeafoamGym.blk`
|
||||
- Header: `data/maps/maps.asm:195` -> `TILESET_CAVE, INDOOR, LANDMARK_SEAFOAM_ISLANDS, MUSIC_GYM, TRUE (phone calls blocked), PALETTE_DAY, FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:179` `map_const SEAFOAM_GYM, 5, 4` (group `CINNABAR` = 6, map id 4). 10x8 tile coordinates - smaller than the 20x18 screen, which is the walkthrough's "this gym doesn't even fit the entire screen".
|
||||
- Attributes / border: `data/maps/attributes.asm:513` `map_attributes SeafoamGym, SEAFOAM_GYM, $09`; no connections
|
||||
- There is **no** `MAP_SEAFOAM_ISLANDS` dungeon in Gen 2 - `constants/map_constants.asm`
|
||||
has only `SEAFOAM_GYM` in the whole Seafoam family.
|
||||
|
||||
**Warps** (`def_warp_events`, `maps/SeafoamGym.asm:161-162`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 5 | 5 | `ROUTE_20` | 1 |
|
||||
|
||||
**Coord events**
|
||||
|
||||
None (`maps/SeafoamGym.asm:164`). The map does declare one scene script,
|
||||
`scene_script SeafoamGymNoopScene` (`:7`), and the asm itself annotates it
|
||||
`; unusable` - the body at `:11` is a bare `end`.
|
||||
|
||||
**BG events**
|
||||
|
||||
None (`maps/SeafoamGym.asm:166`).
|
||||
|
||||
**Object events** (`maps/SeafoamGym.asm:168-170`)
|
||||
|
||||
| const | sprite | x | y | movement | type | sight | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `SEAFOAMGYM_BLAINE` | `SPRITE_BLAINE` | 5 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | 0 | `SeafoamGymBlaineScript` | -1 (always present) |
|
||||
| `SEAFOAMGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 6 | 5 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT` | 0 | `SeafoamGymGuideScript` | `EVENT_SEAFOAM_GYM_GYM_GUIDE` |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `SeafoamGymBlaineScript` (`maps/SeafoamGym.asm:14`, sym `53:516d`). Control flow:
|
||||
1. `faceplayer`, `opentext`
|
||||
2. `checkflag ENGINE_VOLCANOBADGE` / `iftrue .FightDone` - the entire re-talk guard is
|
||||
the badge engine flag, **not** an event flag.
|
||||
3. `writetext BlaineIntroText`, `waitbutton`, `closetext`
|
||||
4. `winlosstext BlaineWinLossText, 0` (no loss text)
|
||||
5. `loadtrainer BLAINE, BLAINE1`, `startbattle`
|
||||
6. `iftrue .ReturnAfterBattle` - i.e. if the battle result is non-zero (you lost /
|
||||
drew) it *skips* `appear SEAFOAMGYM_GYM_GUIDE`
|
||||
7. `appear SEAFOAMGYM_GYM_GUIDE` - clears `EVENT_SEAFOAM_GYM_GYM_GUIDE`, which is how
|
||||
the guide "comes in late"
|
||||
8. `.ReturnAfterBattle`: `reloadmapafterbattle`, `setevent EVENT_BEAT_BLAINE`,
|
||||
`opentext`, `writetext ReceivedVolcanoBadgeText`, `playsound SFX_GET_BADGE`,
|
||||
`waitsfx`, **`setflag ENGINE_VOLCANOBADGE`**, `writetext BlaineAfterBattleText`,
|
||||
`waitbutton`, `closetext`, `end`
|
||||
- No `verbosegiveitem` anywhere: **Blaine gives no TM**, only the badge.
|
||||
- Note the ordering quirk: `setevent EVENT_BEAT_BLAINE` / `setflag ENGINE_VOLCANOBADGE`
|
||||
are on the shared `.ReturnAfterBattle` path, reached whether or not you won, but
|
||||
`startbattle` does not return to the script on a whiteout, so in practice this is
|
||||
only reached after a win.
|
||||
- `SeafoamGymGuideScript` (`maps/SeafoamGym.asm:46`, sym `53:51a3`) - branches on
|
||||
`EVENT_TALKED_TO_SEAFOAM_GYM_GUIDE_ONCE`, first line is the apology
|
||||
("CINNABAR GYM was gone, so I didn't know where to find you"), then a second line
|
||||
forever after.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_VOLCANOBADGE` | `constants/engine_flags.asm:53` (Kanto badge 7; `wKantoBadges` bit 6 per `constants/ram_constants.asm:270`) | checked at `maps/SeafoamGym.asm:17`, set at `:34` | the badge itself; also gates the re-talk branch |
|
||||
| `EVENT_BEAT_BLAINE` | `constants/event_flags.asm:721` | set at `maps/SeafoamGym.asm:29` | not read by any other map in the checkout; it is the record, not the gate |
|
||||
| `EVENT_SEAFOAM_GYM_GYM_GUIDE` | `constants/event_flags.asm:1305` | set at boot (`engine/events/std_scripts.asm:553`), cleared by `appear` at `maps/SeafoamGym.asm:26` | while set, the guide object does not exist |
|
||||
| `EVENT_TALKED_TO_SEAFOAM_GYM_GUIDE_ONCE` | `constants/event_flags.asm:212` | `maps/SeafoamGym.asm:49`, `:54` | first-talk vs repeat text |
|
||||
|
||||
What VOLCANOBADGE actually does mechanically: `data/types/badge_type_boosts.asm:19`
|
||||
`db FIRE ; VOLCANOBADGE`, consumed by `DoBadgeTypeBoosts` (`engine/battle/misc.asm:146`,
|
||||
called from `engine/battle/effect_commands.asm:1261`) - a +12.5% damage boost on the
|
||||
player's FIRE-type moves. It grants no field move.
|
||||
|
||||
**Items**
|
||||
|
||||
None.
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `BLAINE, BLAINE1` | BLAINE ($2e), `constants/trainer_constants.asm:469-470` | 1 | `BlaineGroup` (sym `0e:6e70`) at `parties.asm:2303`, `TRAINERTYPE_MOVES`: **L45 MAGCARGO** (CURSE, SMOG, FLAMETHROWER, ROCK_SLIDE), **L45 MAGMAR** (THUNDERPUNCH, FIRE_PUNCH, SUNNY_DAY, CONFUSE_RAY), **L50 RAPIDASH** (QUICK_ATTACK, FIRE_SPIN, FURY_ATTACK, FIRE_BLAST) | `SeafoamGymBlaineScript` | no rematch, no phone |
|
||||
|
||||
Class attributes (`data/trainers/attributes.asm:275-279`): AI items are
|
||||
**`MAX_POTION, FULL_HEAL`** (not two Full Restores); base reward 25; AI mask
|
||||
`AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY`,
|
||||
`CONTEXT_USE | SWITCH_SOMETIMES`. Payout 25 x 50 x 4 = **5000**, matching the walkthrough.
|
||||
`BLAINE` is in `KantoGymLeaders` (`data/trainers/leaders.asm`), so the battle uses the
|
||||
gym-leader music and awards `HAPPINESS_GYMBATTLE`.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
None. The map is `INDOOR` with `TILESET_CAVE`; its `FISHGROUP_SHORE` header entry is the
|
||||
default and has no reachable water.
|
||||
|
||||
---
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Everything in this section is across open sea (Pallet -> Route 21 -> Cinnabar -> Route 20 -> gym door) | `engine/events/overworld.asm:469` `TrySurfOW` (walk-into-water path): `ld de, ENGINE_FOGBADGE` / `CheckEngineFlag` at `:490`, then `ld d, SURF` / `CheckPartyMove`. Menu path is `SurfFunction` `.TrySurf` at `:340` with the same `ENGINE_FOGBADGE`. `CheckBadge` itself is at `:50`. | FOGBADGE + a party mon that knows SURF | already held long before Kanto (Johto badge 4) |
|
||||
| Fly to Cinnabar Island | `data/maps/flypoints.asm:28`; the flag is `ENGINE_FLYPOINT_CINNABAR`, set by `CinnabarIslandFlypointCallback` (`maps/CinnabarIsland.asm:10`) | STORMBADGE + FLY + having stood on Cinnabar Island once | walk in once |
|
||||
| Blue is not in Viridian Gym | `maps/ViridianGym.asm:183-184` object rows carry `EVENT_VIRIDIAN_GYM_BLUE`, set at `engine/events/std_scripts.asm:552` | - | `clearevent EVENT_VIRIDIAN_GYM_BLUE` at the tail of `CinnabarIslandBlue` (`maps/CinnabarIsland.asm:23`) - **this section is a hard prerequisite for the Viridian Gym leader** |
|
||||
| Route 19 (east of Route 20) is sealed by six boulder blocks | `maps/Route19.asm:15` `Route19ClearRocksCallback`, a `MAPCALLBACK_TILES` callback that lays `changeblock` rock blocks while `EVENT_CINNABAR_ROCKS_CLEARED` is clear | - | `setevent EVENT_CINNABAR_ROCKS_CLEARED` in `Route20ClearRocksCallback` (`maps/Route20.asm:13`), i.e. simply loading Route 20 once from the Cinnabar side |
|
||||
| Blaine will not re-battle | `maps/SeafoamGym.asm:17` `checkflag ENGINE_VOLCANOBADGE` / `iftrue .FightDone` | - | none in vanilla; `.FightDone` is terminal |
|
||||
| The gym guide does not exist until Blaine is beaten | object row event flag `EVENT_SEAFOAM_GYM_GYM_GUIDE` (`maps/SeafoamGym.asm:170`), set at `engine/events/std_scripts.asm:553` | beat Blaine | `appear SEAFOAMGYM_GYM_GUIDE` (`maps/SeafoamGym.asm:26`), only on the win branch |
|
||||
|
||||
Nothing in this section needs CUT, STRENGTH, WHIRLPOOL, WATERFALL, FLASH or a key item.
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
Ordered, machine-actionable. `(g,m)` is `(map group, map id)` from
|
||||
`constants/map_constants.asm`. All coordinates are the raw asm map-event values.
|
||||
|
||||
1. `PALLET_TOWN`: SURF south across the connection into `ROUTE_21 (6,7)`.
|
||||
Pre: FOGBADGE + SURF in the party. Post: none.
|
||||
2. `ROUTE_21`: battle `TrainerSwimmerfNikki` at (11,16) - sight radius 3, so she will
|
||||
trigger on approach. Party L28 SEEL x3 + L28 DEWGONG. Post: `EVENT_BEAT_SWIMMERF_NIKKI`, +560 money.
|
||||
3. `ROUTE_21`: battle `TrainerFisherArnold` at (14,22) - sight radius 1, faces up.
|
||||
Party L34 TENTACRUEL. Post: `EVENT_BEAT_FISHER_ARNOLD`, +1360 money.
|
||||
4. `ROUTE_21`: surf west then south to `TrainerSwimmermSeth` at (2,30) - sight radius 4,
|
||||
faces right. Party L29 QUAGSIRE / L29 OCTILLERY / L32 QUAGSIRE.
|
||||
Post: `EVENT_BEAT_SWIMMERM_SETH`, +256 money.
|
||||
(Both trainers are avoidable - all three are `OBJECTTYPE_TRAINER` with a finite sight
|
||||
radius, none are on a coord event.)
|
||||
5. `ROUTE_21`: continue south across the connection into `CINNABAR_ISLAND (6,8)`.
|
||||
Post: `MAPCALLBACK_NEWMAP` sets `ENGINE_FLYPOINT_CINNABAR`; respawn point becomes
|
||||
`SPAWN_CINNABAR` = (11,12).
|
||||
6. `CINNABAR_ISLAND`: step on warp 1 at (11,11) -> `CINNABAR_POKECENTER_1F (6,1)`.
|
||||
Talk to `CINNABARPOKECENTER1F_NURSE` at (3,1) to heal. Optional: the two flavour NPCs
|
||||
at (7,6) and (2,4). Exit via warp 1/2 at (3,7)/(4,7).
|
||||
7. `CINNABAR_ISLAND`: optional - face (9,1) and press A for the hidden **RARE_CANDY**.
|
||||
Pre: `EVENT_CINNABAR_ISLAND_HIDDEN_RARE_CANDY` clear + a free bag slot.
|
||||
Post: flag set. (Not in the walkthrough.)
|
||||
8. `CINNABAR_ISLAND`: walk to `CINNABARISLAND_BLUE` at (9,6) and talk.
|
||||
Pre: `EVENT_BLUE_IN_CINNABAR` clear. Expect a long `writetext` (about 13 pages of
|
||||
`para` breaks - budget A presses accordingly), then `SFX_WARP_TO` + a `teleport_from`
|
||||
movement, then the object vanishes.
|
||||
Post: `EVENT_BLUE_IN_CINNABAR` set (via `disappear`), `EVENT_VIRIDIAN_GYM_BLUE`
|
||||
**cleared**. Do not skip this step: it is the only unlock for the Viridian Gym leader.
|
||||
9. `CINNABAR_ISLAND`: optional signs at (12,11), (9,11), (7,7).
|
||||
10. `CINNABAR_ISLAND`: SURF east across the connection into `ROUTE_20 (6,6)`.
|
||||
Post: `MAPCALLBACK_NEWMAP` sets `EVENT_CINNABAR_ROCKS_CLEARED` (silent; unseals
|
||||
Route 19 for the next section).
|
||||
11. `ROUTE_20`: battle `TrainerSwimmermCameron` at (12,13) - sight radius 3.
|
||||
Party L34 MARILL. Post: `EVENT_BEAT_SWIMMERM_CAMERON`, +272 money.
|
||||
12. `ROUTE_20`: optional sign at (37,11). Then step onto warp 1 at (38,7) ->
|
||||
`SEAFOAM_GYM (6,4)`.
|
||||
13. `SEAFOAM_GYM`: walk to `SEAFOAMGYM_BLAINE` at (5,2) and talk.
|
||||
Pre: `ENGINE_VOLCANOBADGE` clear (otherwise you get `.FightDone` only).
|
||||
Battle: `loadtrainer BLAINE, BLAINE1` - L45 MAGCARGO / L45 MAGMAR / L50 RAPIDASH,
|
||||
AI holds MAX_POTION + FULL_HEAL.
|
||||
Post: `EVENT_BEAT_BLAINE` set, `EVENT_SEAFOAM_GYM_GYM_GUIDE` cleared (guide appears
|
||||
at (6,5)), **`ENGINE_VOLCANOBADGE` set**, +5000 money. No TM, no item.
|
||||
14. `SEAFOAM_GYM`: optional - talk to `SEAFOAMGYM_GYM_GUIDE` at (6,5).
|
||||
Post: `EVENT_TALKED_TO_SEAFOAM_GYM_GUIDE_ONCE`.
|
||||
15. `SEAFOAM_GYM`: exit via warp 1 at (5,5) back to `ROUTE_20` warp 1.
|
||||
|
||||
Follow-up (next section): Route 20 continues east past Lori (45,13) and Nicole (52,8)
|
||||
into `ROUTE_19`, whose rock blocks are now gone because step 10 set
|
||||
`EVENT_CINNABAR_ROCKS_CLEARED`.
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Kanto map headers, dimensions, connections, warp / coord / bg / object tables | `src/import/RomExtractorGen2.lua` (all 26 map groups, `MAP_GROUP_COUNT = 26` at `:47`, `MapGroupPointers` walk at `:728`) | implemented - fully data-driven, nothing Cinnabar-specific needed |
|
||||
| `MAPCALLBACK_NEWMAP` (`CinnabarIslandFlypointCallback`, `Route20ClearRocksCallback`) | `src/world/gen2/World.lua:5659` `self:runMapCallback("MAPCALLBACK_NEWMAP")`, in `HandleNewMap` order (`:5646`) | implemented; asserted end-to-end by `tests/drivers/gold_map_callbacks.lua` |
|
||||
| `MAPCALLBACK_TILES` + `changeblock` (the Route 19 rocks that this section unseals) | `src/world/gen2/World.lua:5662`; `changeblock` opcode at `src/script/gen2/Opcodes.lua:127` | implemented |
|
||||
| `setflag ENGINE_FLYPOINT_CINNABAR` -> flyable | `src/world/gen2/FieldMoves.lua:367` (`LANDMARK_CINNABAR_ISLAND` / `SPAWN_CINNABAR` / flag 62), `World:setEngineFlag` at `src/world/gen2/World.lua:1326` | implemented |
|
||||
| `checkevent` / `setevent` / `clearevent`, `checkflag` / `setflag` | `src/script/gen2/Vm.lua:187-229` | implemented - id-keyed stores, `engineFlags` kept separate from `wEventFlags` |
|
||||
| `appear` / `disappear` writing the object's event flag (Blue vanishing, the guide arriving) | `src/script/gen2/Opcodes.lua:115-116`, handled in `src/script/gen2/Vm.lua` | implemented |
|
||||
| Hidden item `BGEVENT_ITEM` (Cinnabar RARE_CANDY at 9,1) | `src/world/gen2/HiddenItems.lua` (`BGEVENT_ITEM = 7` at `:22`), extractor branch at `src/import/RomExtractorGen2.lua:2986` | implemented |
|
||||
| `loadtrainer` / `startbattle` / `winlosstext` / `reloadmapafterbattle` / `endifjustbattled` (the whole Blaine script) | `src/script/gen2/Vm.lua:806`, `:817`, `:886`, `:918` | implemented, incl. the `iftrue` on `wBattleResult` that guards `appear SEAFOAMGYM_GYM_GUIDE` |
|
||||
| Gym-leader battle music / victory music for BLAINE | `src/battle/gen2/BattleMusic.lua:21`, `src/battle/gen2/Battle.lua:82` (both list `BLAINE = true`) | implemented |
|
||||
| Prize money = base x last-mon level x 4 (560 / 1360 / 256 / 272 / 5000) | `src/battle/gen2/Prize.lua` (header documents `ComputeTrainerReward` and the `ld c, 4` loop; `Prize.reward`) | implemented, incl. the Mom's-savings split |
|
||||
| Trainer sight radii (3 / 4 / 1 on Route 21) | `src/world/gen2/Trainers.lua` | implemented |
|
||||
| SURF gate on FOGBADGE + party move | `src/world/gen2/FieldMoves.lua:106` (`SURF = "FOG"`), `:481` `surfFromMenu`, `:678` overworld path | implemented; driven by `tests/drivers/gold_water_moves.lua` |
|
||||
| Ocean wild/fish tables for Routes 20/21 | extracted by `src/import/RomExtractorGen2.lua`, consumed by `src/battle/gen2/Encounter.lua` | implemented - correct by construction from the ROM |
|
||||
| Radio "Place & Name" un-hiding BLAINE after the Kanto badge sweep | `src/ui/gen2/Pokegear.lua:172` (`PNP_HIDDEN`), `:178` `PNP_HIDDEN_BEAT_KANTO`, `:1265` | implemented |
|
||||
| `setflag ENGINE_VOLCANOBADGE` reaching the trainer card / `VAR_BADGES` count | `src/script/gen2/Vm.lua:208-229` writes `save.engineFlags[53]`; but `VAR_BADGES` (`src/world/gen2/World.lua:1243-1244`) counts `save.player.badges` + `save.player.kantoBadges`, and `src/ui/gen2/TrainerCard.lua:395` reads `player.badges`. Nothing in `src/` ever writes `player.kantoBadges` (only initialised at `src/core/gen2/Save.lua:119`). | **partial** - Blaine's own re-talk guard works (same `engineFlags` id is read back by `checkflag`), but the badge does not show on the trainer card and does not increment `VAR_BADGES` |
|
||||
| `BadgeTypeBoosts` (VOLCANOBADGE = +12.5% FIRE damage) | no Gen 2 implementation - `grep -rn "badge" src/battle/gen2/` returns nothing; the only badge-boost code is the Gen 1 `src/battle/Damage.lua:35` | **missing** |
|
||||
| `teleport_from` (`$4c`) movement for Blue's exit | `src/script/gen2/Movement.lua:22` `decodeByte` - `$4c` matches no family branch and falls through to `{ kind = "nop" }` | **partial** - Blue disappears with the sound but without the rise-and-vanish animation |
|
||||
| End-to-end driver coverage for this stretch | `tests/drivers/gold_*.lua` (25 drivers, all Johto: boot, walk, warp, trainer, ice path, roamers, ...) | **missing** - no Cinnabar / Route 20 / Route 21 / Seafoam Gym driver exists |
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
- **Blaine's movesets in the walkthrough are not the pokegold movesets.** The FAQ
|
||||
describes Magcargo with Yawn / Recover / Overheat, Magmar with Confuse Ray, and
|
||||
Rapidash with Flare Blitz / Bounce. `data/trainers/parties.asm:2303-2308` gives
|
||||
MAGCARGO (CURSE, SMOG, FLAMETHROWER, ROCK_SLIDE), MAGMAR (THUNDERPUNCH, FIRE_PUNCH,
|
||||
SUNNY_DAY, CONFUSE_RAY) and RAPIDASH (QUICK_ATTACK, FIRE_SPIN, FURY_ATTACK,
|
||||
FIRE_BLAST). Only CONFUSE_RAY overlaps; Overheat, Flare Blitz, Bounce and Yawn do not
|
||||
exist in `constants/move_constants.asm` at all for this generation. **Species and
|
||||
levels match exactly** (L45 / L45 / L50), so treat the strategy prose as written
|
||||
against a different (modernised) build and the asm as authoritative.
|
||||
- **"Blaine will have two Full Restores available."** `data/trainers/attributes.asm:276`
|
||||
is `db MAX_POTION, FULL_HEAL`. Two different items, neither of them a Full Restore.
|
||||
The count of *uses* is AI-driven, not a fixed two.
|
||||
- **"You now have 15 badges!"** Nothing in the asm counts badges as an ordinal; Kanto
|
||||
gyms have no enforced order (`ENGINE_VOLCANOBADGE` is one bit,
|
||||
`constants/engine_flags.asm:53`). The 15 is walkthrough prose that assumes a specific
|
||||
visiting order.
|
||||
- **Route 21 wild list is abridged in the walkthrough.** The FAQ lists Tentacool and
|
||||
Tangela. `data/wild/kanto_grass.asm:1005` also has **MR__MIME** in all three
|
||||
time slots, and `data/wild/kanto_water.asm:61` also has **TENTACRUEL**. Route 20's
|
||||
list is likewise incomplete (TENTACRUEL is there too).
|
||||
- **"Surf south on Route 21 ... head way left then down."** The trainer coordinates
|
||||
confirm the shape of the route (16 -> 22 -> 30, then far west), but the actual walkable
|
||||
water path is block data in `maps/Route21.blk` and was not decoded here.
|
||||
- **"You should see a small cave at the Seafoam Islands."** The only asm anchor is
|
||||
`warp_event 38, 7` on `maps/Route20.asm:117` plus `bg_event 37, 11` for the gym sign.
|
||||
The cave mouth graphic itself is block data in `maps/Route20.blk`; there is no
|
||||
Seafoam Islands dungeon map in Gen 2 (`constants/map_constants.asm` has only
|
||||
`SEAFOAM_GYM`), so the walkthrough's "Seafoam Islands" is a landmark name
|
||||
(`data/maps/landmarks.asm:94` `LANDMARK_SEAFOAM_ISLANDS`) rather than a walkable
|
||||
dungeon.
|
||||
- **EXP figures.** The per-mon EXP numbers in the FAQ (600 for a L28 Seel, 1056 for a
|
||||
L28 Dewgong, 1492 for a L34 Tentacruel) are consistent with the vanilla
|
||||
`base_exp * level / 7` times the 1.5x trainer-battle bonus, but nothing in
|
||||
`data/trainers/parties.asm` stores them and they still vary with participation, so
|
||||
they are not reproducible from the asm as written. The **money** figures all reproduce
|
||||
exactly and are recorded above.
|
||||
- **`EVENT_BEAT_BLAINE` has no reader.** Grepping `maps/`, `data/`, `engine/`, `home/`
|
||||
and `constants/` finds it only at its definition (`constants/event_flags.asm:721`) and
|
||||
its single `setevent` (`maps/SeafoamGym.asm:29`). If the port needs a "Blaine beaten"
|
||||
predicate for anything, `ENGINE_VOLCANOBADGE` is the flag the cart actually branches on.
|
||||
@@ -0,0 +1,460 @@
|
||||
# Section 28 - Routes 19-20 and Viridian City Gym
|
||||
|
||||
Source: `../section-28-routes-19-20-and-viridian-city-gym.txt` (the FAQ numbers this chapter "34 > Routes 19-20 and Viridian City Gym")
|
||||
Maps covered: `MAP_ROUTE_20`, `MAP_ROUTE_19`, `MAP_ROUTE_19_FUCHSIA_GATE`, `MAP_VIRIDIAN_CITY`, `MAP_VIRIDIAN_POKECENTER_1F`, `MAP_VIRIDIAN_GYM`
|
||||
Badges / key milestones in this section: EARTHBADGE (`ENGINE_EARTHBADGE`) from Blue in Viridian Gym, `EVENT_BEAT_BLUE`, five optional Swimmer battles on Routes 19/20. This is the 16th badge, which is what `maps/OaksLab.asm` reads to open Mt. Silver in a later section.
|
||||
|
||||
Map group / id pairs a bot needs (from `constants/map_constants.asm`):
|
||||
|
||||
| Map constant | group | id |
|
||||
|---|---|---|
|
||||
| `MAP_ROUTE_19_FUCHSIA_GATE` | 6 (`CINNABAR`, `constants/map_constants.asm:175`) | 3 |
|
||||
| `MAP_ROUTE_19` | 6 | 5 |
|
||||
| `MAP_ROUTE_20` | 6 | 6 |
|
||||
| `MAP_VIRIDIAN_CITY` | 23 (`VIRIDIAN`, `constants/map_constants.asm:420`) | 3 |
|
||||
| `MAP_VIRIDIAN_GYM` | 23 | 4 |
|
||||
| `MAP_VIRIDIAN_POKECENTER_1F` | 23 | 9 |
|
||||
|
||||
Coordinate convention reminder: `warp_event` / `coord_event` / `bg_event` / `object_event` all take `x, y` in map cell coordinates, 0-based from the top-left (`macros/scripts/maps.asm:63`, `:79`, `:97`, `:113`). A map that is `W, H` blocks is `2W, 2H` cells. `changeblock` also takes cell coordinates and halves them internally (`engine/overworld/scripting.asm:2030` `Script_changeblock`, `home/map.asm:2099` `GetBlockLocation`).
|
||||
|
||||
## 1. Route order
|
||||
|
||||
| # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) |
|
||||
|---|---|---|---|---|---|
|
||||
| 0 | `MAP_SEAFOAM_GYM` | `maps/SeafoamGym.asm` (previous section) | - | its exit warp back to Route 20 warp 1 | Blaine's gym sits on Route 20; the previous section ends there |
|
||||
| 1 | `MAP_ROUTE_20` | `maps/Route20.asm` | Seafoam Gym exit -> Route 20 warp 1 `(38, 7)`, or the west connection from Cinnabar Island | east map connection to Route 19 (`data/maps/attributes.asm:285`, offset -9) | "East on Route 20 and to Route 19 are optional ... Surf down and right to face the swimmer in the water" (Lori, then Nicole) |
|
||||
| 2 | `MAP_ROUTE_19` | `maps/Route19.asm` | west map connection from Route 20 (`data/maps/attributes.asm:289`, offset 9) | Fly (walkthrough), or warp 1 `(7, 3)` -> `ROUTE_19_FUCHSIA_GATE` warp 3, or the north connection to Fuchsia City | "You'll find a drowning guy next to a girl" (Tucker, Dawn, then Jerome). "Fly back to Viridian City instead" |
|
||||
| 2a | `MAP_ROUTE_19_FUCHSIA_GATE` | `maps/Route19FuchsiaGate.asm` | Route 19 warp 1 | warps 1/2 `(4, 0)` / `(5, 0)` -> `FUCHSIA_CITY` warps 10 / 11 | Only referenced by the walkthrough's "blocked ... south of Fuchsia City" line; see section 6 |
|
||||
| 3 | `MAP_VIRIDIAN_CITY` | `maps/ViridianCity.asm` | Fly (`ENGINE_FLYPOINT_VIRIDIAN`, spawn `(23, 26)`) | warp 5 `(23, 25)` -> Pokecenter; warp 1 `(32, 7)` -> gym | "fly back to Viridian City ... First, heal at the Pokémon Center" |
|
||||
| 4 | `MAP_VIRIDIAN_POKECENTER_1F` | `maps/ViridianPokecenter1F.asm` | Viridian warp 5 | warps 1/2 `(3, 7)` / `(4, 7)` -> Viridian warp 5 | Heal before and after Blue |
|
||||
| 5 | `MAP_VIRIDIAN_GYM` | `maps/ViridianGym.asm` | Viridian warp 1 `(32, 7)` | warps 1/2 `(4, 17)` / `(5, 17)` -> Viridian warp 1 | "GYM LEADER BLUE ... You get: Earth Badge, 5,800G" |
|
||||
|
||||
Spillover: the closing line "There are still a few things you can do before you clear Kanto" points at `maps/OaksLab.asm:26` (`readvar VAR_BADGES` / `ifequal NUM_BADGES, .OpenMtSilver`), which belongs to the next section. Nothing past the Viridian Gym exit is described here.
|
||||
|
||||
## 2. Maps
|
||||
|
||||
### MAP_ROUTE_20
|
||||
|
||||
- Script: `maps/Route20.asm`
|
||||
- Blocks: `maps/Route20.blk` (`data/maps/blocks.asm:118` `Route20_Blocks`)
|
||||
- Header: `data/maps/maps.asm:197` -> `TILESET_KANTO`, `ROUTE`, `LANDMARK_ROUTE_20`, `MUSIC_ROUTE_3`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_OCEAN`
|
||||
- Dimensions: `constants/map_constants.asm:181` `map_const ROUTE_20, 30, 9` (30x9 blocks = 60x18 cells)
|
||||
- Connections: `data/maps/attributes.asm:283-285` - west `CinnabarIsland` (`CINNABAR_ISLAND`, offset 0), east `Route19` (`ROUTE_19`, offset -9). No north/south.
|
||||
- Map events block: `Route20_MapEvents`, sym `4e:4ecd`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 38 | 7 | `SEAFOAM_GYM` | 1 |
|
||||
|
||||
**Coord events** (`def_coord_events`) - none (`def_coord_events` is empty).
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 37 | 11 | `BGEVENT_READ` | `CinnabarGymSign` (sym `4e:4d3a`) -> `CinnabarGymSignText` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE20_SWIMMER_GIRL1` | `SPRITE_SWIMMER_GIRL` | 52 | 8 | `SPRITEMOVEDATA_SPINRANDOM_FAST` (radius 0,0) | `OBJECTTYPE_TRAINER`, sight 3, `PAL_NPC_GREEN` | `TrainerSwimmerfNicole` (sym `4e:4cfe`) | -1 (always present) |
|
||||
| `ROUTE20_SWIMMER_GIRL2` | `SPRITE_SWIMMER_GIRL` | 45 | 13 | `SPRITEMOVEDATA_SPINRANDOM_FAST` (radius 0,0) | `OBJECTTYPE_TRAINER`, sight 3, `PAL_NPC_GREEN` | `TrainerSwimmerfLori` (sym `4e:4d12`) | -1 |
|
||||
| `ROUTE20_SWIMMER_GUY` | `SPRITE_SWIMMER_GUY` | 12 | 13 | `SPRITEMOVEDATA_SPINRANDOM_FAST` (radius 0,0) | `OBJECTTYPE_TRAINER`, sight 3, `PAL_NPC_RED` | `TrainerSwimmermCameron` (sym `4e:4d26`) | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route20_MapScripts` (`maps/Route20.asm:6`): no scene scripts; one callback, `callback MAPCALLBACK_NEWMAP, Route20ClearRocksCallback`.
|
||||
- `Route20ClearRocksCallback` (`maps/Route20.asm:12`, sym `4e:4cfa`): two opcodes, `setevent EVENT_CINNABAR_ROCKS_CLEARED` then `endcallback`. This is the single most load-bearing fact on this map: merely *entering* Route 20 for the first time permanently clears the Route 19 boulders. There is no other setter of that flag anywhere in `maps/`.
|
||||
- The three trainer scripts are the stock pattern: `trainer <CLASS>, <ID>, <EVENT>, SeenText, BeatenText, 0, .Script`, and `.Script` is `endifjustbattled / opentext / writetext <AfterBattleText> / waitbutton / closetext / end`. No items, no flags beyond the trainer's own beat flag.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_CINNABAR_ROCKS_CLEARED` | `constants/event_flags.asm:214` | set by `Route20ClearRocksCallback`; read by `Route19ClearRocksCallback`, `Route19Fisher1Script`, `Route19Fisher2Script`, `Route19FuchsiaGateOfficerScript` | Once set, the six Route 19 boulder blocks are never placed |
|
||||
| `EVENT_BEAT_SWIMMERF_NICOLE` | `constants/event_flags.asm:487` | `TrainerSwimmerfNicole` | One-time battle |
|
||||
| `EVENT_BEAT_SWIMMERF_LORI` | `constants/event_flags.asm:488` | `TrainerSwimmerfLori` | One-time battle |
|
||||
| `EVENT_BEAT_SWIMMERM_CAMERON` | `constants/event_flags.asm:957` | `TrainerSwimmermCameron` | One-time battle; the walkthrough never mentions him |
|
||||
|
||||
**Items** - none on this map (the only `bg_event` is a readable sign; no `hiddenitem`, no item balls).
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `SWIMMERF` / `LORI` | 27 (`constants/trainer_constants.asm:375`) | LORI = `SwimmerFGroup` entry 15 | `data/trainers/parties.asm:1781` `SwimmerFGroup` (sym `0e:69c7`), `TRAINERTYPE_NORMAL`: L32 STARMIE, L32 STARMIE | `TrainerSwimmerfLori` | none (no `phone_call` / rematch entry) |
|
||||
| `SWIMMERF` / `NICOLE` | 27 | NICOLE = `SwimmerFGroup` entry 14 | `TRAINERTYPE_NORMAL`: L29 MARILL, L29 MARILL, L32 LAPRAS | `TrainerSwimmerfNicole` | none |
|
||||
| `SWIMMERM` / `CAMERON` | 26 (`constants/trainer_constants.asm:352`) | CAMERON = `SwimmerMGroup` entry 17 | `data/trainers/parties.asm:1640` `SwimmerMGroup` (sym `0e:68a8`), `TRAINERTYPE_NORMAL`: L34 MARILL | `TrainerSwimmermCameron` | none |
|
||||
|
||||
Prize money: `data/trainers/attributes.asm:227` gives class Swimmerm base reward 2, `:233` gives Swimmerf base reward 5, no held items. `engine/battle/read_trainer_party.asm:300` `ComputeTrainerReward` = base x level of the last mon, and `engine/battle/core.asm:2340-2362` pays that quarter out four times, so the wallet figure is `4 x base x level`: Lori and Nicole `4 x 5 x 32 = 640` (matches the FAQ), Cameron `4 x 2 x 34 = 272`.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
- Surfing: `data/wild/kanto_water.asm:54` `def_water_wildmons ROUTE_20`, encounter rate `6 percent`: L35 TENTACOOL, L30 TENTACOOL, L35 TENTACRUEL. No morn/day/nite split (water tables have a single slot list).
|
||||
- No entry in `data/wild/kanto_grass.asm` (the map has no grass), none in `data/wild/treemons.asm`.
|
||||
- Fishing: header fish group is `FISHGROUP_OCEAN` (`data/maps/maps.asm:197`), `data/wild/fish.asm` `.Ocean_Old` / `.Ocean_Good` / `.Ocean_Super` (lines 42-55): Old = MAGIKARP/MAGIKARP/TENTACOOL L10; Good = MAGIKARP L20, TENTACOOL L20, CHINCHOU L20, time group 2; Super = CHINCHOU L40, time group 3, TENTACRUEL L40, LANTURN L40.
|
||||
|
||||
### MAP_ROUTE_19
|
||||
|
||||
- Script: `maps/Route19.asm`
|
||||
- Blocks: `maps/Route19.blk` (`data/maps/blocks.asm:48` `Route19_Blocks`)
|
||||
- Header: `data/maps/maps.asm:196` -> `TILESET_KANTO`, `ROUTE`, `LANDMARK_ROUTE_19`, `MUSIC_ROUTE_3`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:180` `map_const ROUTE_19, 10, 18` (10x18 blocks = 20x36 cells)
|
||||
- Connections: `data/maps/attributes.asm:287-289` - north `FuchsiaCity` (`FUCHSIA_CITY`, offset 0), west `Route20` (`ROUTE_20`, offset 9). No south/east.
|
||||
- Map events block: `Route19_MapEvents`, sym `4e:535c`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 7 | 3 | `ROUTE_19_FUCHSIA_GATE` | 3 |
|
||||
|
||||
**Coord events** (`def_coord_events`) - none.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 11 | 13 | `BGEVENT_READ` | `Route19Sign` (sym `4e:4fa0`) -> `Route19SignText` |
|
||||
| 11 | 1 | `BGEVENT_READ` | `CarefulSwimmingSign` (sym `4e:4fa3`) -> `CarefulSwimmingSignText` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE19_SWIMMER_GIRL` | `SPRITE_SWIMMER_GIRL` | 9 | 23 | `SPRITEMOVEDATA_STANDING_LEFT` (radius 0,0) | `OBJECTTYPE_TRAINER`, sight **0**, `PAL_NPC_GREEN` | `TrainerSwimmerfDawn` (sym `4e:4f28`) | -1 |
|
||||
| `ROUTE19_SWIMMER_GUY1` | `SPRITE_SWIMMER_GUY` | 13 | 28 | `SPRITEMOVEDATA_SPINRANDOM_FAST` (radius 0,0) | `OBJECTTYPE_TRAINER`, sight 3, `PAL_NPC_RED` | `TrainerSwimmermHarold` (sym `4e:4f3c`) | -1 |
|
||||
| `ROUTE19_SWIMMER_GUY2` | `SPRITE_SWIMMER_GUY` | 11 | 17 | `SPRITEMOVEDATA_SPINRANDOM_FAST` (radius 0,0) | `OBJECTTYPE_TRAINER`, sight 3, `PAL_NPC_RED` | `TrainerSwimmermJerome` (sym `4e:4f50`) | -1 |
|
||||
| `ROUTE19_SWIMMER_GUY3` | `SPRITE_SWIMMER_GUY` | 8 | 23 | `SPRITEMOVEDATA_STANDING_UP` (radius 0,0) | `OBJECTTYPE_TRAINER`, sight **0**, `PAL_NPC_RED` | `TrainerSwimmermTucker` (sym `4e:4f64`) | -1 |
|
||||
| `ROUTE19_FISHER1` | `SPRITE_FISHER` | 9 | 5 | `SPRITEMOVEDATA_STANDING_DOWN` (radius 0,0) | `OBJECTTYPE_SCRIPT` (sight field 1, unused) | `Route19Fisher1Script` (sym `4e:4f78`) | -1 |
|
||||
| `ROUTE19_FISHER2` | `SPRITE_FISHER` | 11 | 5 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT` (sight field 1, unused) | `Route19Fisher2Script` (sym `4e:4f8c`) | -1 |
|
||||
|
||||
Note for a bot: Tucker `(8, 23)` and Dawn `(9, 23)` both have sight range 0, so they never initiate a battle by line of sight. They must be talked to, which is exactly the walkthrough's "Talk to the guy for a battle" / "talk to the girl to battle her". Harold and Jerome have sight 3 and will pull the player in.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route19_MapScripts` (`maps/Route19.asm:9`): no scene scripts; one callback, `callback MAPCALLBACK_TILES, Route19ClearRocksCallback`.
|
||||
- `Route19ClearRocksCallback` (`maps/Route19.asm:15`, sym `4e:4f09`): `checkevent EVENT_CINNABAR_ROCKS_CLEARED` / `iftrue .Done`, otherwise six `changeblock` calls writing block `$7a` (a rock) at cells `(6, 6)`, `(8, 6)`, `(10, 6)`, `(12, 8)`, `(4, 8)`, `(10, 10)` - i.e. blocks `(3, 3)`, `(4, 3)`, `(5, 3)`, `(6, 4)`, `(2, 4)`, `(5, 5)`. Block `$7a` in `data/tilesets/kanto_collision.asm:123` is `tilecoll WALL, WALL, WALL, WALL`, so all four quadrants are solid; these are impassable terrain, not Strength boulders. Because this is `MAPCALLBACK_TILES` it re-runs on every map load, and because `EVENT_CINNABAR_ROCKS_CLEARED` is set by simply walking onto Route 20, by the time this section runs the rocks are already gone.
|
||||
- `Route19Fisher1Script` / `Route19Fisher2Script` (`maps/Route19.asm:71` / `:87`): `faceplayer / opentext / checkevent EVENT_CINNABAR_ROCKS_CLEARED / iftrue .RocksCleared`. Flavor only, no flags written, no items.
|
||||
- The four trainer scripts follow the same `endifjustbattled` pattern as Route 20's.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `EVENT_CINNABAR_ROCKS_CLEARED` | `constants/event_flags.asm:214` | read by `Route19ClearRocksCallback` and both Fisher scripts | Gate on whether the north half of Route 19 is walkable |
|
||||
| `EVENT_BEAT_SWIMMERF_DAWN` | `constants/event_flags.asm:485` | `TrainerSwimmerfDawn` | One-time battle |
|
||||
| `EVENT_BEAT_SWIMMERM_HAROLD` | `constants/event_flags.asm:941` | `TrainerSwimmermHarold` | One-time battle; not mentioned by the walkthrough |
|
||||
| `EVENT_BEAT_SWIMMERM_JEROME` | `constants/event_flags.asm:954` | `TrainerSwimmermJerome` | One-time battle |
|
||||
| `EVENT_BEAT_SWIMMERM_TUCKER` | `constants/event_flags.asm:955` | `TrainerSwimmermTucker` | One-time battle |
|
||||
|
||||
**Items** - none. Both `bg_event`s are `BGEVENT_READ` signs; there is no `hiddenitem` and no item ball on this map.
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `SWIMMERM` / `TUCKER` | 26 | `SwimmerMGroup` entry 15 | `TRAINERTYPE_NORMAL`: L30 SHELLDER, L34 CLOYSTER | `TrainerSwimmermTucker` | none |
|
||||
| `SWIMMERF` / `DAWN` | 27 | `SwimmerFGroup` entry 12 | `TRAINERTYPE_NORMAL`: L34 SEAKING | `TrainerSwimmerfDawn` | none |
|
||||
| `SWIMMERM` / `JEROME` | 26 | `SwimmerMGroup` entry 14 | `TRAINERTYPE_NORMAL`: L26 SEADRA, L28 TENTACOOL, L30 TENTACRUEL, L28 GOLDEEN | `TrainerSwimmermJerome` | none |
|
||||
| `SWIMMERM` / `HAROLD` | 26 | `SwimmerMGroup` entry 1 | `TRAINERTYPE_NORMAL`: L32 REMORAID, L30 SEADRA | `TrainerSwimmermHarold` | none |
|
||||
|
||||
Prize money using the same `4 x base x last-mon level` rule: Tucker `4 x 2 x 34 = 272`, Dawn `4 x 5 x 34 = 680`, Jerome `4 x 2 x 28 = 224`, Harold `4 x 2 x 30 = 240`. The first three match the FAQ exactly.
|
||||
|
||||
**Wild encounters**
|
||||
|
||||
- Surfing: `data/wild/kanto_water.asm:47` `def_water_wildmons ROUTE_19`, encounter rate `6 percent`: L35 TENTACOOL, L30 TENTACOOL, L35 TENTACRUEL.
|
||||
- No grass table, no headbutt table.
|
||||
- Fishing: `FISHGROUP_SHORE` (`data/maps/maps.asm:196`), `data/wild/fish.asm` `.Shore_Old` / `.Shore_Good` / `.Shore_Super` (lines 28-40): Old = MAGIKARP/MAGIKARP/KRABBY L10; Good = MAGIKARP L20, KRABBY L20 (x2), time group 0; Super = KRABBY L40, time group 1, KRABBY L40, KINGLER L40.
|
||||
|
||||
### MAP_ROUTE_19_FUCHSIA_GATE
|
||||
|
||||
Included because the walkthrough's "blocked by those boulders south of Fuchsia City" line points at it. The walkthrough never actually enters it.
|
||||
|
||||
- Script: `maps/Route19FuchsiaGate.asm`
|
||||
- Blocks: `maps/Route19FuchsiaGate.blk` (`data/maps/blocks.asm:204` `Route19FuchsiaGate_Blocks`)
|
||||
- Header: `data/maps/maps.asm:194` -> `TILESET_GATE`, `GATE`, `LANDMARK_ROUTE_19`, `MUSIC_ROUTE_3`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:178` `map_const ROUTE_19_FUCHSIA_GATE, 5, 4` (10x8 cells)
|
||||
- Connections: none (`data/maps/attributes.asm:512` has no connection rows)
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 0 | `FUCHSIA_CITY` | 10 |
|
||||
| 2 | 5 | 0 | `FUCHSIA_CITY` | 11 |
|
||||
| 3 | 4 | 7 | `ROUTE_19` | 1 |
|
||||
| 4 | 5 | 7 | `ROUTE_19` | 1 |
|
||||
|
||||
**Coord events** - none. **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `ROUTE19FUCHSIAGATE_OFFICER` | `SPRITE_OFFICER` | 0 | 4 | `SPRITEMOVEDATA_STANDING_RIGHT` (radius 0,0) | `OBJECTTYPE_SCRIPT`, `PAL_NPC_BLUE` | `Route19FuchsiaGateOfficerScript` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `Route19FuchsiaGateOfficerScript` (`maps/Route19FuchsiaGate.asm:9`): `faceplayer / opentext / checkevent EVENT_CINNABAR_ROCKS_CLEARED / iftrue .RocksCleared`, then one of two texts. He is at `(0, 4)`, off to the side of the `(4, ...)` / `(5, ...)` walkway, so he blocks nothing. There is no coord event and no movement-blocking script in this gate: the officer is flavor, and the actual road closure is the `changeblock` rocks on Route 19 itself.
|
||||
|
||||
### MAP_VIRIDIAN_CITY
|
||||
|
||||
- Script: `maps/ViridianCity.asm`
|
||||
- Blocks: `maps/ViridianCity.blk` (`data/maps/blocks.asm:235` `ViridianCity_Blocks`)
|
||||
- Header: `data/maps/maps.asm:457` -> `TILESET_KANTO`, `TOWN`, `LANDMARK_VIRIDIAN_CITY`, `MUSIC_VIRIDIAN_CITY`, phone `FALSE`, `PALETTE_AUTO`, `FISHGROUP_POND`
|
||||
- Dimensions: `constants/map_constants.asm:423` `map_const VIRIDIAN_CITY, 20, 18` (40x36 cells)
|
||||
- Connections: `data/maps/attributes.asm:259-261` - north `Route2` (`ROUTE_2`, offset 5), south `Route1` (`ROUTE_1`, offset 10), west `Route22` (`ROUTE_22`, offset 4). No east.
|
||||
- Map events block: `ViridianCity_MapEvents`, sym `4e:4486`
|
||||
- Fly target: `data/maps/flypoints.asm:19` `db LANDMARK_VIRIDIAN_CITY, SPAWN_VIRIDIAN`; `data/maps/spawn_points.asm:15` `spawn VIRIDIAN_CITY, 23, 26` (the Pokecenter doorstep)
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 32 | 7 | `VIRIDIAN_GYM` | 1 |
|
||||
| 2 | 21 | 9 | `VIRIDIAN_NICKNAME_SPEECH_HOUSE` | 1 |
|
||||
| 3 | 23 | 15 | `TRAINER_HOUSE_1F` | 1 |
|
||||
| 4 | 29 | 19 | `VIRIDIAN_MART` | 2 |
|
||||
| 5 | 23 | 25 | `VIRIDIAN_POKECENTER_1F` | 1 |
|
||||
|
||||
**Coord events** (`def_coord_events`) - none.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 17 | 17 | `BGEVENT_READ` | `ViridianCitySign` |
|
||||
| 27 | 7 | `BGEVENT_READ` | `ViridianGymSign` (sym `4e:4053`) - "LEADER: ... The rest of the text is illegible" |
|
||||
| 19 | 1 | `BGEVENT_READ` | `ViridianCityWelcomeSign` |
|
||||
| 21 | 15 | `BGEVENT_READ` | `TrainerHouseSign` |
|
||||
| 24 | 25 | `BGEVENT_READ` | `ViridianCityPokecenterSign` (`jumpstd PokecenterSignScript`) |
|
||||
| 30 | 19 | `BGEVENT_READ` | `ViridianCityMartSign` (`jumpstd MartSignScript`) |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `VIRIDIANCITY_GRAMPS1` | `SPRITE_GRAMPS` | 18 | 5 | `SPRITEMOVEDATA_WANDER` (radius 2,2) | `OBJECTTYPE_SCRIPT` | `ViridianCityCoffeeGramps` | -1 |
|
||||
| `VIRIDIANCITY_GRAMPS2` | `SPRITE_GRAMPS` | 30 | 8 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, `PAL_NPC_BLUE` | `ViridianCityGrampsNearGym` (sym `4e:401e`) | -1 |
|
||||
| `VIRIDIANCITY_FISHER` | `SPRITE_FISHER` | 6 | 23 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, `PAL_NPC_RED` | `ViridianCityDreamEaterFisher` (sym `4e:4032`) | -1 |
|
||||
| `VIRIDIANCITY_YOUNGSTER` | `SPRITE_YOUNGSTER` | 17 | 21 | `SPRITEMOVEDATA_WANDER` (radius 3,3) | `OBJECTTYPE_SCRIPT`, `PAL_NPC_GREEN` | `ViridianCityYoungsterScript` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `ViridianCityFlypointCallback` (`maps/ViridianCity.asm:13`, sym `4e:4005`), hooked as `callback MAPCALLBACK_NEWMAP`: `setflag ENGINE_FLYPOINT_VIRIDIAN / endcallback`. A bot cannot Fly here until it has physically entered Viridian City once.
|
||||
- `ViridianCityGrampsNearGym` (`maps/ViridianCity.asm:34`): `checkevent EVENT_BLUE_IN_CINNABAR`; if true, the "Are you going to battle the LEADER?" line. This is a free read-out of whether Blue has been moved into the gym - see the gate table.
|
||||
- `ViridianCityDreamEaterFisher` (`maps/ViridianCity.asm:50`): `checkevent EVENT_GOT_TM42_DREAM_EATER / iftrue .GotDreamEater`, else `writetext ... / promptbutton / verbosegiveitem TM_DREAM_EATER / iffalse .NoRoomForDreamEater / setevent EVENT_GOT_TM42_DREAM_EATER`. The walkthrough never mentions this; it is a free TM on the map the walkthrough tells you to fly to.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_FLYPOINT_VIRIDIAN` | `constants/engine_flags.asm:68` | set by `ViridianCityFlypointCallback` | Precondition for the walkthrough's "fly back to Viridian City" |
|
||||
| `EVENT_BLUE_IN_CINNABAR` | `constants/event_flags.asm:1303` | read by `ViridianCityGrampsNearGym` and `ViridianPokecenter1FCooltrainerMScript`; it is also `CINNABARISLAND_BLUE`'s object event flag | While clear, Blue is standing on Cinnabar Island and has not yet moved to the gym |
|
||||
| `EVENT_GOT_TM42_DREAM_EATER` | `constants/event_flags.asm:223` | `ViridianCityDreamEaterFisher` | One-time TM42 |
|
||||
|
||||
**Items**
|
||||
|
||||
| item | how obtained | source (label / bg_event / hidden) | one-time flag |
|
||||
|---|---|---|---|
|
||||
| `TM_DREAM_EATER` (TM42) | Talk to `VIRIDIANCITY_FISHER` at `(6, 23)` | `ViridianCityDreamEaterFisher` `verbosegiveitem` | `EVENT_GOT_TM42_DREAM_EATER` |
|
||||
|
||||
**Trainers** - none on the overworld map.
|
||||
|
||||
**Wild encounters** - no `kanto_grass.asm` or `kanto_water.asm` entry for `VIRIDIAN_CITY`. Fishing group is `FISHGROUP_POND` (`data/maps/maps.asm:457`).
|
||||
|
||||
### MAP_VIRIDIAN_POKECENTER_1F
|
||||
|
||||
- Script: `maps/ViridianPokecenter1F.asm`
|
||||
- Blocks: `data/maps/blocks.asm:361` `ViridianPokecenter1F_Blocks`
|
||||
- Header: `data/maps/maps.asm:463` -> `TILESET_POKECENTER`, `INDOOR`, `LANDMARK_VIRIDIAN_CITY`, `MUSIC_POKEMON_CENTER`, phone `FALSE`, `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:429` `map_const VIRIDIAN_POKECENTER_1F, 5, 4` (10x8 cells)
|
||||
- Connections: none
|
||||
- Respawn point: `data/maps/spawn_points.asm:12` `spawn VIRIDIAN_POKECENTER_1F, 5, 3`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3 | 7 | `VIRIDIAN_CITY` | 5 |
|
||||
| 2 | 4 | 7 | `VIRIDIAN_CITY` | 5 |
|
||||
| 3 | 0 | 7 | `POKECENTER_2F` | 1 |
|
||||
|
||||
**Coord events** - none. **BG events** - none.
|
||||
|
||||
**Object events**
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `VIRIDIANPOKECENTER1F_NURSE` | `SPRITE_NURSE` | 3 | 1 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `ViridianPokecenter1FNurseScript` | -1 |
|
||||
| `VIRIDIANPOKECENTER1F_COOLTRAINER_M` | `SPRITE_COOLTRAINER_M` | 8 | 4 | `SPRITEMOVEDATA_WALK_LEFT_RIGHT` (radius 1,0) | `OBJECTTYPE_SCRIPT`, `PAL_NPC_RED` | `ViridianPokecenter1FCooltrainerMScript` | -1 |
|
||||
| `VIRIDIANPOKECENTER1F_COOLTRAINER_F` | `SPRITE_COOLTRAINER_F` | 5 | 3 | `SPRITEMOVEDATA_STANDING_UP` | `OBJECTTYPE_SCRIPT`, `PAL_NPC_BLUE` | `ViridianPokecenter1FCooltrainerFScript` | -1 |
|
||||
| `VIRIDIANPOKECENTER1F_BUG_CATCHER` | `SPRITE_BUG_CATCHER` | 1 | 6 | `SPRITEMOVEDATA_STANDING_RIGHT` | `OBJECTTYPE_SCRIPT`, `PAL_NPC_GREEN` | `ViridianPokecenter1FBugCatcherScript` | -1 |
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `ViridianPokecenter1FNurseScript` (`maps/ViridianPokecenter1F.asm:12`): `jumpstd PokecenterNurseScript` (`engine/events/std_scripts.asm:54`). This is the "heal at the Pokémon Center" beat, both before and after Blue.
|
||||
- `ViridianPokecenter1FCooltrainerMScript` (`maps/ViridianPokecenter1F.asm:15`): another `checkevent EVENT_BLUE_IN_CINNABAR` read-out ("There are no GYM TRAINERS at the VIRIDIAN GYM"), which corroborates the walkthrough's "There aren't any other trainers in this gym".
|
||||
|
||||
### MAP_VIRIDIAN_GYM
|
||||
|
||||
- Script: `maps/ViridianGym.asm`
|
||||
- Blocks: `maps/ViridianGym.blk` (`data/maps/blocks.asm:877` `ViridianGym_Blocks`)
|
||||
- Header: `data/maps/maps.asm:458` -> `TILESET_TRAIN_STATION`, `INDOOR`, `LANDMARK_VIRIDIAN_CITY`, `MUSIC_GYM`, phone `TRUE`, `PALETTE_DAY`, `FISHGROUP_SHORE`
|
||||
- Dimensions: `constants/map_constants.asm:424` `map_const VIRIDIAN_GYM, 5, 9` (10x18 cells)
|
||||
- Connections: none (`data/maps/attributes.asm:657`)
|
||||
- Map events block: `ViridianGym_MapEvents`, sym `5f:43de`
|
||||
|
||||
**Warps** (`def_warp_events`)
|
||||
|
||||
| idx | x | y | destination map | dest warp |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 4 | 17 | `VIRIDIAN_CITY` | 1 |
|
||||
| 2 | 5 | 17 | `VIRIDIAN_CITY` | 1 |
|
||||
|
||||
**Coord events** (`def_coord_events`) - none. There is no trip-wire in this gym; Blue is a pure talk-to trigger.
|
||||
|
||||
**BG events** (`def_bg_events`)
|
||||
|
||||
| x | y | type | script/item |
|
||||
|---|---|---|---|
|
||||
| 3 | 13 | `BGEVENT_READ` | `ViridianGymStatue` (sym `5f:4047`) |
|
||||
| 6 | 13 | `BGEVENT_READ` | `ViridianGymStatue` |
|
||||
|
||||
**Object events** (`def_object_events`)
|
||||
|
||||
| const | sprite | x | y | movement | type | script label | event flag |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `VIRIDIANGYM_BLUE` | `SPRITE_BLUE` | 5 | 3 | `SPRITEMOVEDATA_STANDING_DOWN` (radius 0,0) | `OBJECTTYPE_SCRIPT`, palette 0 | `ViridianGymBlueScript` (sym `5f:4002`) | `EVENT_VIRIDIAN_GYM_BLUE` |
|
||||
| `VIRIDIANGYM_GYM_GUIDE` | `SPRITE_GYM_GUIDE` | 7 | 13 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT`, `PAL_NPC_BLUE` | `ViridianGymGuideScript` (sym `5f:4033`) | `EVENT_VIRIDIAN_GYM_BLUE` |
|
||||
|
||||
Both objects carry `EVENT_VIRIDIAN_GYM_BLUE` as their event flag, so while that flag is **set** the gym is completely empty. `EVENT_VIRIDIAN_GYM_BLUE` is set by `InitializeEventsScript` at new game (`engine/events/std_scripts.asm:438`, the `setevent` on line 552) and is cleared only by `CinnabarIslandBlue`.
|
||||
|
||||
**Scripts of interest**
|
||||
|
||||
- `ViridianGymBlueScript` (`maps/ViridianGym.asm:10`, sym `5f:4002`), the whole badge beat, in order:
|
||||
1. `faceplayer` / `opentext`
|
||||
2. `checkflag ENGINE_EARTHBADGE` / `iftrue .FightDone` (sym `5f:402d`) - repeat visits get `LeaderBlueEpilogueText` only
|
||||
3. `writetext LeaderBlueBeforeText` / `waitbutton` / `closetext`
|
||||
4. `winlosstext LeaderBlueWinText, 0` (loss text pointer 0 = use the default)
|
||||
5. `loadtrainer BLUE, BLUE1` / `startbattle` / `reloadmapafterbattle`
|
||||
6. `setevent EVENT_BEAT_BLUE`
|
||||
7. `opentext` / `writetext Text_ReceivedEarthBadge` / `playsound SFX_GET_BADGE` / `waitsfx`
|
||||
8. `setflag ENGINE_EARTHBADGE`
|
||||
9. `writetext LeaderBlueAfterText` / `waitbutton` / `closetext` / `end`
|
||||
|
||||
No `verbosegiveitem`: unlike Johto leaders, the Kanto leaders in GSC hand out no TM. The badge itself is the only reward besides money.
|
||||
- `ViridianGymGuideScript` (`maps/ViridianGym.asm:39`, sym `5f:4033`): `checkevent EVENT_BEAT_BLUE / iftrue .ViridianGymGuideWinScript`. Pure flavor, no `learnmove`, no items.
|
||||
- `ViridianGymStatue` (`maps/ViridianGym.asm:55`, sym `5f:4047`): `checkflag ENGINE_EARTHBADGE / iftrue .Beaten`; before the badge, `jumpstd GymStatue1Script` (`engine/events/std_scripts.asm:638`), after it `gettrainername STRING_BUFFER_4, BLUE, BLUE1` then `jumpstd GymStatue2Script` (`:646`). A cheap read-back a bot can use to confirm the badge landed.
|
||||
|
||||
**Flags and events**
|
||||
|
||||
| constant | defined in | read/written by | meaning for a bot |
|
||||
|---|---|---|---|
|
||||
| `ENGINE_EARTHBADGE` | `constants/engine_flags.asm:54` (in `wKantoBadges`) | read by `ViridianGymBlueScript` and `ViridianGymStatue`; set by `ViridianGymBlueScript` | The 16th badge. Post-condition of this section |
|
||||
| `EVENT_BEAT_BLUE` | `constants/event_flags.asm:722` | set by `ViridianGymBlueScript`, read by `ViridianGymGuideScript` | Set *before* the badge flag, between `reloadmapafterbattle` and `playsound` |
|
||||
| `EVENT_VIRIDIAN_GYM_BLUE` | `constants/event_flags.asm:1304` | set by `InitializeEventsScript` (`engine/events/std_scripts.asm:552`); cleared by `CinnabarIslandBlue` (`maps/CinnabarIsland.asm:23`) | While set, both gym objects are absent. This is the real gate on the whole section |
|
||||
| `EVENT_BLUE_IN_CINNABAR` | `constants/event_flags.asm:1303` | object flag of `CINNABARISLAND_BLUE` (`maps/CinnabarIsland.asm:143`), set by that script's `disappear` | Once set, Blue is gone from Cinnabar and (necessarily) present in the gym |
|
||||
|
||||
**Items** - none. No item balls, no `hiddenitem`, no TM reward.
|
||||
|
||||
**Trainers**
|
||||
|
||||
| const | class | id | party (`data/trainers/parties.asm` label) | script label | rematch/phone |
|
||||
|---|---|---|---|---|---|
|
||||
| `BLUE` / `BLUE1` | 40 (`constants/trainer_constants.asm:640`) | `BLUE1` (`:641`) | `data/trainers/parties.asm:3154` `BlueGroup` (sym `0e:75ff`), `TRAINERTYPE_MOVES` | `ViridianGymBlueScript` | none (no phone entry; the map header's phone column is `TRUE` but no `phone_call` targets him) |
|
||||
|
||||
`BlueGroup` party, verbatim:
|
||||
|
||||
| # | level | species | moves |
|
||||
|---|---|---|---|
|
||||
| 1 | 56 | PIDGEOT | QUICK_ATTACK, WHIRLWIND, WING_ATTACK, MIRROR_MOVE |
|
||||
| 2 | 54 | ALAKAZAM | DISABLE, RECOVER, PSYCHIC_M, REFLECT |
|
||||
| 3 | 56 | RHYDON | FURY_ATTACK, SANDSTORM, ROCK_SLIDE, EARTHQUAKE |
|
||||
| 4 | 58 | GYARADOS | TWISTER, HYDRO_PUMP, RAIN_DANCE, HYPER_BEAM |
|
||||
| 5 | 58 | EXEGGUTOR | SUNNY_DAY, LEECH_SEED, EGG_BOMB, SOLARBEAM |
|
||||
| 6 | 58 | ARCANINE | ROAR, SWIFT, FLAMETHROWER, EXTREMESPEED |
|
||||
|
||||
Class attributes (`data/trainers/attributes.asm:383`): items `FULL_RESTORE, FULL_RESTORE` (the AI will use two Full Restores), base reward 25, AI `AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY`, `CONTEXT_USE | SWITCH_SOMETIMES`. Payout `4 x 25 x 58 = 5800`, matching the FAQ. `BLUE` is listed in `KantoGymLeaders` (`data/trainers/leaders.asm:31`), which is what selects the gym-leader battle/victory music and awards `HAPPINESS_GYMBATTLE`.
|
||||
|
||||
Walkthrough claims worth flagging against the party data: Pidgeot's flying move is `WING_ATTACK`, not Air Slash (which does not exist in Gen 2); Exeggutor's moves are `SUNNY_DAY, LEECH_SEED, EGG_BOMB, SOLARBEAM`, so it has neither Psychic nor Hypnosis; Arcanine's switch move is `ROAR`, Pidgeot's is `WHIRLWIND`.
|
||||
|
||||
**Wild encounters** - none (indoor map).
|
||||
|
||||
## 3. Blockers and gates
|
||||
|
||||
| Gate | Enforced by (file:label) | Requirement | Unlock condition |
|
||||
|---|---|---|---|
|
||||
| Reaching Routes 19/20 at all | `engine/events/overworld.asm:322` `SurfFunction` -> `.TrySurf` (`ld de, ENGINE_FOGBADGE / call CheckBadge`), and the tile-facing path `engine/events/overworld.asm:469` `TrySurfOW` (`ENGINE_FOGBADGE` via `CheckEngineFlag`, then `CheckPartyMove` for `SURF`) | FOGBADGE + a party member knowing SURF | Both routes are open water; without Surf there is no approach |
|
||||
| Boulders across northern Route 19 | `maps/Route19.asm:15` `Route19ClearRocksCallback`, six `changeblock ..., $7a` under `checkevent EVENT_CINNABAR_ROCKS_CLEARED`; block `$7a` is `WALL` on all four quadrants (`data/tilesets/kanto_collision.asm:123`) | `EVENT_CINNABAR_ROCKS_CLEARED` | Set unconditionally by `maps/Route20.asm:12` `Route20ClearRocksCallback` on `MAPCALLBACK_NEWMAP`, i.e. by setting foot on Route 20 |
|
||||
| Blue is not in the Viridian Gym | Object event flag `EVENT_VIRIDIAN_GYM_BLUE` on both `maps/ViridianGym.asm:183-184` objects; set at new game by `engine/events/std_scripts.asm:552` inside `InitializeEventsScript` | Flag must be **clear** | `maps/CinnabarIsland.asm:14` `CinnabarIslandBlue`: talk to Blue on Cinnabar Island; the script runs `disappear CINNABARISLAND_BLUE` (setting `EVENT_BLUE_IN_CINNABAR`) then `clearevent EVENT_VIRIDIAN_GYM_BLUE`. This happens in an earlier section, but a bot that skipped it will find an empty gym |
|
||||
| Flying to Viridian City | `maps/ViridianCity.asm:13` `ViridianCityFlypointCallback` (`setflag ENGINE_FLYPOINT_VIRIDIAN`), plus `engine/events/overworld.asm:529` `FlyFunction` (`ld de, ENGINE_STORMBADGE`) and the landmark row `data/maps/flypoints.asm:19` | STORMBADGE + a party member knowing FLY + Viridian visited once on foot | Walk into Viridian City once (Route 1 north, Route 2 south, or Route 22 east). Otherwise reach the gym overland |
|
||||
| Re-fighting Blue | `maps/ViridianGym.asm:13` `checkflag ENGINE_EARTHBADGE / iftrue .FightDone` | - | Not possible; the battle is one-shot and there is no rematch or phone entry |
|
||||
| (Next section) Mt. Silver | `maps/OaksLab.asm:26` `readvar VAR_BADGES / ifequal NUM_BADGES, .OpenMtSilver` -> `setevent EVENT_OPENED_MT_SILVER` | All 16 badges, EARTHBADGE last | Beating Blue here is what satisfies it |
|
||||
|
||||
Note on the Route 19 approach: the officer in `maps/Route19FuchsiaGate.asm` never blocks a tile, and `maps/Route19.asm` has no coord events. There is no NPC blocker anywhere in this section - every obstacle is either terrain (`changeblock`) or a hidden object (`EVENT_VIRIDIAN_GYM_BLUE`).
|
||||
|
||||
## 4. Bot checklist
|
||||
|
||||
Preconditions for the whole section: `ENGINE_FOGBADGE` + SURF in party, `EVENT_VIRIDIAN_GYM_BLUE` clear, `ENGINE_FLYPOINT_VIRIDIAN` set (or an overland plan to Viridian).
|
||||
|
||||
| # | Map | Target | Input intent | Precondition | Postcondition |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `MAP_ROUTE_20` | anywhere on the map | enter the map (warp from Seafoam Gym warp 1 at `(38, 7)`, or the Cinnabar Island east connection) | - | `EVENT_CINNABAR_ROCKS_CLEARED` set by `Route20ClearRocksCallback` |
|
||||
| 2 | `MAP_ROUTE_20` | `ROUTE20_SWIMMER_GIRL2` at `(45, 13)` | surf into her sight cone (range 3) or talk | `EVENT_BEAT_SWIMMERF_LORI` clear | battle Lori (2x L32 STARMIE); `EVENT_BEAT_SWIMMERF_LORI` set, +640 |
|
||||
| 3 | `MAP_ROUTE_20` | `ROUTE20_SWIMMER_GIRL1` at `(52, 8)` | surf into sight (range 3) or talk | `EVENT_BEAT_SWIMMERF_NICOLE` clear | battle Nicole (L29 MARILL, L29 MARILL, L32 LAPRAS); flag set, +640 |
|
||||
| 3b | `MAP_ROUTE_20` | `ROUTE20_SWIMMER_GUY` at `(12, 13)` | optional, west end, skipped by the FAQ | `EVENT_BEAT_SWIMMERM_CAMERON` clear | battle Cameron (L34 MARILL); flag set, +272 |
|
||||
| 4 | `MAP_ROUTE_20` -> `MAP_ROUTE_19` | east map edge | surf east across the connection (offset -9) | - | now on `MAP_ROUTE_19` |
|
||||
| 5 | `MAP_ROUTE_19` | `ROUTE19_SWIMMER_GUY3` at `(8, 23)` | **talk** (sight 0, will not initiate) | `EVENT_BEAT_SWIMMERM_TUCKER` clear | battle Tucker (L30 SHELLDER, L34 CLOYSTER); flag set, +272 |
|
||||
| 6 | `MAP_ROUTE_19` | `ROUTE19_SWIMMER_GUY3` again | talk | `EVENT_BEAT_SWIMMERM_TUCKER` set | `SwimmermTuckerAfterBattleText` (the "drowning" line) |
|
||||
| 7 | `MAP_ROUTE_19` | `ROUTE19_SWIMMER_GIRL` at `(9, 23)` | **talk** (sight 0) | `EVENT_BEAT_SWIMMERF_DAWN` clear | battle Dawn (L34 SEAKING); flag set, +680 |
|
||||
| 8 | `MAP_ROUTE_19` | `ROUTE19_SWIMMER_GUY2` at `(11, 17)` | surf north into sight (range 3) or talk | `EVENT_BEAT_SWIMMERM_JEROME` clear | battle Jerome (L26 SEADRA, L28 TENTACOOL, L30 TENTACRUEL, L28 GOLDEEN); flag set, +224 |
|
||||
| 8b | `MAP_ROUTE_19` | `ROUTE19_SWIMMER_GUY1` at `(13, 28)` | optional, south-east, not in the FAQ | `EVENT_BEAT_SWIMMERM_HAROLD` clear | battle Harold (L32 REMORAID, L30 SEADRA); flag set, +240 |
|
||||
| 9 | `MAP_ROUTE_19` -> `MAP_VIRIDIAN_CITY` | Pokegear map -> Fly -> Viridian | use FLY | `ENGINE_STORMBADGE`, FLY in party, `ENGINE_FLYPOINT_VIRIDIAN` | player at `VIRIDIAN_CITY (23, 26)` (`data/maps/spawn_points.asm:15`) |
|
||||
| 10 | `MAP_VIRIDIAN_CITY` | warp 5 at `(23, 25)` | step onto the warp | - | in `MAP_VIRIDIAN_POKECENTER_1F` |
|
||||
| 11 | `MAP_VIRIDIAN_POKECENTER_1F` | `VIRIDIANPOKECENTER1F_NURSE` at `(3, 1)` | talk, accept heal | - | party healed (`PokecenterNurseScript`) |
|
||||
| 11b | `MAP_VIRIDIAN_CITY` | `VIRIDIANCITY_FISHER` at `(6, 23)` | talk (optional, not in the FAQ) | `EVENT_GOT_TM42_DREAM_EATER` clear, bag space | receive `TM_DREAM_EATER`; flag set |
|
||||
| 12 | `MAP_VIRIDIAN_CITY` | warp 1 at `(32, 7)` | step onto the warp | - | in `MAP_VIRIDIAN_GYM` at `(4, 17)` / `(5, 17)` |
|
||||
| 13 | `MAP_VIRIDIAN_GYM` | `VIRIDIANGYM_GYM_GUIDE` at `(7, 13)` | talk (optional) | `EVENT_VIRIDIAN_GYM_BLUE` clear | advice text only |
|
||||
| 14 | `MAP_VIRIDIAN_GYM` | save | save the game (the FAQ says so explicitly) | - | - |
|
||||
| 15 | `MAP_VIRIDIAN_GYM` | `VIRIDIANGYM_BLUE` at `(5, 3)`, approach from below | talk | `EVENT_VIRIDIAN_GYM_BLUE` clear, `ENGINE_EARTHBADGE` clear | battle `BLUE / BLUE1` (6 mons, L54-58, 2 Full Restores); on win `EVENT_BEAT_BLUE` set, `ENGINE_EARTHBADGE` set, +5800 |
|
||||
| 16 | `MAP_VIRIDIAN_GYM` | `VIRIDIANGYM_BLUE` again | talk | `ENGINE_EARTHBADGE` set | `LeaderBlueEpilogueText` ("You'd better not lose until I beat you") |
|
||||
| 17 | `MAP_VIRIDIAN_GYM` | `bg_event (3, 13)` or `(6, 13)` | read the statue | `ENGINE_EARTHBADGE` set | `GymStatue2Script` with Blue's name - a cheap badge assertion |
|
||||
| 18 | `MAP_VIRIDIAN_GYM` -> `MAP_VIRIDIAN_CITY` -> Pokecenter | warp 1/2 `(4, 17)` / `(5, 17)`, then Viridian warp 5 | walk, warp, heal | - | 16 badges, section complete |
|
||||
|
||||
## 5. Port coverage
|
||||
|
||||
| Beat | Port file | Status |
|
||||
|---|---|---|
|
||||
| Map header, blocks, warps, coord/bg/object events for all six maps | `src/import/RomExtractorGen2.lua:782` `readMapEvents`, `:899` (map assembly) | implemented - the tables are read out of the ROM generically, so no per-map data is hand-written |
|
||||
| `MAPCALLBACK_NEWMAP` / `MAPCALLBACK_TILES` dispatch (needed for both rock callbacks and the Viridian fly point) | `src/world/gen2/World.lua:5659`, `:5664` | implemented |
|
||||
| `changeblock` (Route 19 boulders) | `src/script/gen2/Vm.lua:1002` (halves x/y to block coords, calls `changeBlockFn`) | implemented |
|
||||
| `setevent` / `clearevent` / `checkevent`, object-event hide flags | `src/world/gen2/Events.lua`, `src/script/gen2/Vm.lua` | implemented |
|
||||
| `setflag` / `checkflag` on `ENGINE_*` (EARTHBADGE, FLYPOINT_VIRIDIAN) | `src/script/gen2/Vm.lua:212-223`, `:1584` | implemented |
|
||||
| Surf field move + FOGBADGE check | `src/world/gen2/FieldMoves.lua:101` (badge table), `:482` (`SurfFunction` port) | implemented |
|
||||
| Fly field move + STORMBADGE + flypoint gating | `src/world/gen2/FieldMoves.lua:501-505` | implemented |
|
||||
| Trainer sight cones / `OBJECTTYPE_TRAINER` (sight 3 swimmers, sight 0 Tucker and Dawn) | `src/world/gen2/Trainers.lua:98` `Trainers.sees` | implemented |
|
||||
| `SPRITEMOVEDATA_SPINRANDOM_FAST` swimmers | `src/world/gen2/Npc.lua:23`, `:37`, `:68` | implemented |
|
||||
| `loadtrainer` / `startbattle` / `winlosstext` / `reloadmapafterbattle` / `endifjustbattled` | `src/script/gen2/Vm.lua`, `src/script/gen2/Opcodes.lua` | implemented |
|
||||
| Prize money, including the 4x split and Bank of Mom | `src/battle/gen2/Prize.lua:82` `Prize.reward`, `:181` (four quarters) | implemented |
|
||||
| Gym-leader battle/victory music via `GymLeaders` / `KantoGymLeaders` | `src/battle/gen2/BattleMusic.lua:15-33` | implemented |
|
||||
| Kanto badge page on the trainer card (EARTHBADGE displayed) | `src/ui/gen2/TrainerCard.lua:90`, `:161` | implemented |
|
||||
| `verbosegiveitem` (TM42 in Viridian City) | `src/script/gen2/Vm.lua` (`verbosegiveitem`) | implemented |
|
||||
| A driver that actually plays this stretch (Routes 19/20 surf trainers, Blue) | none - `tests/drivers/` has `gold_trainer_smoke.lua`, `gold_water_moves.lua`, `gold_map_callbacks.lua`, but nothing that reaches Kanto Route 19/20 or Viridian Gym | missing |
|
||||
| Section-specific regression coverage for `EVENT_CINNABAR_ROCKS_CLEARED` sequencing | not found by grep in `tests/` | missing |
|
||||
|
||||
Every "implemented" row above is a generic engine capability, not a hand-port of these maps. Nothing in `src/` names `ROUTE_19`, `ROUTE_20` or `VIRIDIAN_GYM` (the only grep hits are `src/save_convert/data/event_flags_yellow.lua`, which is Gen 1 save conversion data and unrelated). So this section should work if the extractor and VM work, but it has never been exercised end to end.
|
||||
|
||||
## 6. Unresolved / verify by hand
|
||||
|
||||
1. **The walkthrough's boulder claim contradicts the asm.** "You can't go further north because you'll be blocked by those boulders south of Fuchsia City" is only true while `EVENT_CINNABAR_ROCKS_CLEARED` is clear. `maps/Route20.asm:12` `Route20ClearRocksCallback` sets that event on `MAPCALLBACK_NEWMAP` with no condition at all, and the walkthrough routes the player through Route 20 to reach Route 19. By the time the player is standing on Route 19 the six `changeblock` rocks are never placed, so northern Route 19 and the Fuchsia gate at `(7, 3)` are open. Treat the FAQ line as stale prose, not a gate. (What is genuinely true is that the FAQ's suggested Fly is faster than swimming back.)
|
||||
2. **Route 19's north connection vs. the gate warp.** `data/maps/attributes.asm:288` declares `connection north, FuchsiaCity, FUCHSIA_CITY, 0`, and there is *also* a warp to `ROUTE_19_FUCHSIA_GATE`. I did not decode `maps/Route19.blk` to determine whether the top block row is actually walkable, so I cannot say whether the north connection is reachable or purely decorative. Verify by hand before a bot relies on either route out.
|
||||
3. **Two trainers the walkthrough omits.** `TrainerSwimmermCameron` on Route 20 at `(12, 13)` (L34 MARILL) and `TrainerSwimmermHarold` on Route 19 at `(13, 28)` (L32 REMORAID, L30 SEADRA) both exist with sight range 3. The FAQ says "that's all the trainers" for Route 19, which is wrong. A bot surfing the full map will get pulled into both.
|
||||
4. **Blue's move set does not match the FAQ's strategy text.** The FAQ names Air Slash (Pidgeot) and Psychic + Hypnosis (Exeggutor); `data/trainers/parties.asm:3154` gives Pidgeot `QUICK_ATTACK, WHIRLWIND, WING_ATTACK, MIRROR_MOVE` and Exeggutor `SUNNY_DAY, LEECH_SEED, EGG_BOMB, SOLARBEAM`. Air Slash does not exist in Gen 2. Do not build AI expectations from the FAQ prose here.
|
||||
5. **The FAQ's "Blue is one of only two trainers tougher than Champion Lance"** is a subjective claim with no asm counterpart; nothing in `data/trainers/` ranks trainers.
|
||||
6. **`EVENT_VIRIDIAN_GYM_BLUE` is set in a prior section.** The walkthrough never mentions meeting Blue on Cinnabar Island, but `maps/CinnabarIsland.asm:23` is the only `clearevent` for that flag anywhere in the tree. If an earlier section's document does not cover `CinnabarIslandBlue`, this is an orphaned precondition and a bot will find an empty Viridian Gym with no in-game hint beyond `ViridianCityGrampsNearGym`.
|
||||
7. **Blue's map header sets the phone column to `TRUE`** (`data/maps/maps.asm:458`, the `ViridianGym` row). I found no `phone_call` or rematch entry naming `BLUE`, so I believe this is inert, but I did not exhaustively read `data/phone/`.
|
||||