diff options
author | Rangi <remy.oukaour+rangi42@gmail.com> | 2018-07-21 11:43:13 -0400 |
---|---|---|
committer | Rangi <remy.oukaour+rangi42@gmail.com> | 2018-07-21 11:43:13 -0400 |
commit | de8d08bfa60cf2dafa7d976814dbd456ecd89ee9 (patch) | |
tree | b93b5a413ca910f00fc3296448a6cf098a901d5a | |
parent | 4b6071fa18653b172df78b0e5410790215f282f9 (diff) |
Don't delete files!
-rw-r--r-- | Infinitely-reusable-TMs.md | 533 |
1 files changed, 533 insertions, 0 deletions
diff --git a/Infinitely-reusable-TMs.md b/Infinitely-reusable-TMs.md new file mode 100644 index 0000000..a89482d --- /dev/null +++ b/Infinitely-reusable-TMs.md @@ -0,0 +1,533 @@ +Starting in Gen 5, TMs became infinitely reusable, just like HMs. Let's review what would have to change to implement this in Gen 2: + +- Don't consume TMs when they're used (obviously) +- Don't allow TMs to be held or tossed (just like HMs) +- Don't show quantities next to TMs (just like HMs) +- Don't let the player buy TMs they already have +- Don't have redundant ways of acquiring the same TM + +All of those are relatively simple to do. + + +## Contents + +1. [Don't consume TMs when they're used](#1-dont-consume-tms-when-theyre-used) +2. [Don't allow TMs to be held or tossed](#2-dont-allow-tms-to-be-held-or-tossed) +3. [Don't show quantities next to TMs](#3-dont-show-quantities-next-to-tms) +4. [Don't let the player buy TMs they already have](#4-dont-let-the-player-buy-tms-they-already-have) +5. [Do the same thing for Game Corner prize TMs](#5-do-the-same-thing-for-game-corner-prize-tms) +6. [Don't have redundant ways of acquiring the same TM](#6-dont-have-redundant-ways-of-acquiring-the-same-tm) +7. [Don't let Time Capsule traded Pokémon hold TMs](#7-dont-let-time-capsule-traded-pokémon-hold-tms) + + +## 1. Don't consume TMs when they're used + +Edit [engine/items/tmhm.asm](../blob/master/engine/items/tmhm.asm): + +```diff + ld c, HAPPINESS_LEARNMOVE + callfar ChangeHappiness +- call ConsumeTM + jr .learned_move + + ... + +-ConsumeTM: +- call ConvertCurItemIntoCurTMHM +- ld a, [wd265] +- dec a +- ld hl, wTMsHMs +- ld b, 0 +- ld c, a +- add hl, bc +- ld a, [hl] +- and a +- ret z +- dec a +- ld [hl], a +- ret nz +- ld a, [wTMHMPocketScrollPosition] +- and a +- ret z +- dec a +- ld [wTMHMPocketScrollPosition], a +- ret +``` + +That was easy! Technically we're already done… you could just tell the player to ignore the TM quantities, and don't throw them away, and don't waste money on duplicates. But that would be sloppy, so let's keep going. + + +## 2. Don't allow TMs to be held or tossed + +Edit [data/items/attributes.asm](../blob/master/data/items/attributes.asm): + +```diff +-; TM01 +- item_attribute 3000, HELD_NONE, 0, CANT_SELECT, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE +-... +-; TM50 +- item_attribute 2000, HELD_NONE, 0, CANT_SELECT, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE ++; TM01 ++ item_attribute 3000, HELD_NONE, 0, CANT_SELECT | CANT_TOSS, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE ++... ++; TM50 ++ item_attribute 2000, HELD_NONE, 0, CANT_SELECT | CANT_TOSS, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE +``` + +We just changed `CANT_SELECT` to `CANT_SELECT | CANT_TOSS` for all 50 TMs, just like the HMs already had. + + +## 3. Don't show quantities next to TMs + +Edit [engine/items/tmhm.asm](../blob/master/engine/items/tmhm.asm) again: + +```diff + .okay + predef GetTMHMMove + ld a, [wd265] + ld [wPutativeTMHMMove], a + call GetMoveName + pop hl + ld bc, 3 + add hl, bc +- push hl + call PlaceString +- pop hl + pop bc +- ld a, c +- push bc +- cp NUM_TMS + 1 +- jr nc, .hm2 +- ld bc, SCREEN_WIDTH + 9 +- add hl, bc +- ld [hl], "×" +- inc hl +- ld a, "0" ; why are we doing this? +- pop bc +- push bc +- ld a, b +- ld [wd265], a +- ld de, wd265 +- lb bc, 1, 2 +- call PrintNum +-.hm2 +- pop bc + pop de + pop hl + dec d + jr nz, .loop2 + jr .done +``` + +The `cp NUM_TMS + 1` and `jr nc, .hm2` in there skipped the quantity-printing code for HMs, so we just deleted the quantity-printing code along with the now-redundant HM check. + + +## 4. Don't let the player buy TMs they already have + +If you didn't know how to do this, you could get away with just not selling TMs in Marts. But in fact, we *do* know how to do this. ;) + +Edit [engine/items/mart.asm](../blob/master/engine/items/mart.asm): + +```diff + MartAskPurchaseQuantity: ++ ld a, [wCurItem] ++ cp TM01 ++ jr nc, .PurchaseQuantityOfTM + call GetMartDialogGroup ; gets a pointer from GetMartDialogGroup.MartTextFunctionPointers + inc hl + inc hl + ld a, [hl] + and a + jp z, StandardMartAskPurchaseQuantity + cp 1 + jp z, BargainShopAskPurchaseQuantity + jp RooftopSaleAskPurchaseQuantity ++ ++.PurchaseQuantityOfTM: ++ push de ++ ld hl, wNumItems ++ call CheckItem ++ pop de ++ jp c, .AlreadyHaveTM ++ farcall GetItemPrice ++ ld a, d ++ ld [wBuffer1], a ++ ld a, e ++ ld [wBuffer2], a ++ ld a, 1 ++ ld [wItemQuantityChangeBuffer], a ++ ld a, 99 ++ ld [wItemQuantityBuffer], a ++ farcall BuySell_MultiplyPrice ++ push hl ++ ld hl, hMoneyTemp ++ ld a, [hProduct + 1] ++ ld [hli], a ++ ld a, [hProduct + 2] ++ ld [hli], a ++ ld a, [hProduct + 3] ++ ld [hl], a ++ pop hl ++ ret ++ ++.AlreadyHaveTM: ++ ld hl, .AlreadyHaveTMText ++ call PrintText ++ call JoyWaitAorB ++ scf ++ ret ++ ++.AlreadyHaveTMText: ++ text_jump AlreadyHaveTMText ++ db "@" +``` + +And edit [data/text/common_3.asm](../blob/master/data/text/common_3.asm): + +```diff +UnknownText_0x1c4bfd:: + text "How many?" + done ++ ++AlreadyHaveTMText:: ++ text "You already have" ++ line "that TM." ++ done +``` + +If you already have a TM, you'll just get a message saying so; if not, there's no need to pick a quantity to buy, so it just finds the cost of buying one. + + +## 5. Do the same thing for Game Corner prize TMs + +We just took care of buying TMs in Marts for cash; but what about in the Game Corners for coins? Those are implemented as event scripts, not assembly code, so they'll be easier to fix. + +Edit [maps/GoldenrodGameCorner.asm](../blob/master/maps/GoldenrodGameCorner.asm): + +```diff + .Thunder: ++ checkitem TM_THUNDER ++ iftrue GoldenrodGameCornerPrizeVendor_AlreadyHaveTMScript + ... + + .Blizzard: ++ checkitem TM_BLIZZARD ++ iftrue GoldenrodGameCornerPrizeVendor_AlreadyHaveTMScript + ... + + .FireBlast: ++ checkitem TM_FIRE_BLAST ++ iftrue GoldenrodGameCornerPrizeVendor_AlreadyHaveTMScript + ... + + ... + + GoldenrodGameCornerTMVendor_FinishScript: + waitsfx + playsound SFX_TRANSACTION + writetext GoldenrodGameCornerPrizeVendorHereYouGoText + waitbutton + jump GoldenrodGameCornerTMVendor_LoopScript ++ ++GoldenrodGameCornerPrizeVendor_AlreadyHaveTMScript: ++ writetext GoldenrodGameCornerPrizeVendorAlreadyHaveTMText ++ waitbutton ++ jump GoldenrodGameCornerTMVendor_LoopScript + + ... + + GoldenrodGameCornerPrizeVendorHereYouGoText: + text "Here you go!" + done ++ ++GoldenrodGameCornerPrizeVendorAlreadyHaveTMText: ++ text "But you already" ++ line "have that TM!" ++ done +``` + +And edit [maps/CeladonGameCornerPrizeRoom.asm](../blob/master/maps/CeladonGameCornerPrizeRoom.asm): + +```diff + .doubleteam ++ checkitem TM_DOUBLE_TEAM ++ iftrue CeladonPrizeRoom_alreadyhavetm + ... + + .psychic ++ checkitem TM_PSYCHIC_M ++ iftrue CeladonPrizeRoom_alreadyhavetm + ... + + .hyperbeam ++ checkitem TM_HYPER_BEAM ++ iftrue CeladonPrizeRoom_alreadyhavetm + ... + + ... + + CeladonPrizeRoom_purchased: + waitsfx + playsound SFX_TRANSACTION + writetext CeladonPrizeRoom_HereYouGoText + waitbutton + jump CeladonPrizeRoom_tmcounterloop ++ ++CeladonPrizeRoom_alreadyhavetm: ++ writetext CeladonPrizeRoom_AlreadyHaveTMText ++ waitbutton ++ jump CeladonPrizeRoom_tmcounterloop + + ... + + CeladonPrizeRoom_HereYouGoText: + text "Here you go!" + done ++ ++CeladonPrizeRoom_AlreadyHaveTMText: ++ text "You already have" ++ line "that TM." ++ done +``` + +Pretty self-explanatory. + + +## 6. Don't have redundant ways of acquiring the same TM + +If TMs are untossable and infinite-use, it's wasteful to have multiple ways of getting the same TM. + +Ten TMs can be acquired more than once in Pokémon Crystal: + +- TM02 Headbutt: Ilex Forest (gift); Goldenrod Dept. Store (¥2000 after receiving the gift) +- TM08 Rock Smash: Route 36 (gift); Goldenrod Dept. Store (¥2000 after receiving the gift) +- TM10 Hidden Power: Lake of Rage (gift); Celadon Dept. Store (¥3000) +- TM11 Sunny Day: Radio Tower (gift); Celadon Dept. Store (¥2000) +- TM13 Snore: Route 39 (gift); Dark Cave (item ball) +- TM18 Rain Dance: Slowpoke Well (item ball); Celadon Dept. Store (¥2000) +- TM21 Frustration: Goldenrod Dept. Store (weekly gift) +- TM27 Return: Goldenrod Dept. Store (weekly gift) +- TM37 Sandstorm: Route 27 (gift); Celadon Dept. Store (¥2000) +- TM47 Steel Wing: Route 28 (gift); Rock Tunnel (item ball) + +First, edit [maps/DarkCaveBlackthornEntrance.asm](../blob/master/maps/DarkCaveBlackthornEntrance.asm): + +```diff + DarkCaveBlackthornEntranceTMSnore: +- itemball TM_SNORE ++ itemball AWAKENING +``` + +And edit [maps/RockTunnel1F.asm](../blob/master/maps/RockTunnel1F.asm): + +```diff + RockTunnel1FTMSteelWing: +- itemball TM_STEEL_WING ++ itemball METAL_COAT +``` + +(I'm not bothering to update all the label and constant names from "Snore" and "Steel Wing" to their new items.) + +Now edit [maps/GoldenrodDeptStore5F.asm](../blob/master/maps/GoldenrodDeptStore5F.asm): + +```diff + GoldenrodDeptStore5FClerkScript: + faceplayer + opentext +- checkevent EVENT_GOT_TM02_HEADBUTT +- iftrue .headbutt +- checkevent EVENT_GOT_TM08_ROCK_SMASH +- iftrue .onlyrocksmash +- jump .neither +- +-.headbutt +- checkevent EVENT_GOT_TM08_ROCK_SMASH +- iftrue .both +- jump .onlyheadbutt +- +-.neither +- pokemart MARTTYPE_STANDARD, MART_GOLDENROD_5F_1 +- closetext +- end +- +-.onlyheadbutt +- pokemart MARTTYPE_STANDARD, MART_GOLDENROD_5F_2 +- closetext +- end +- +-.onlyrocksmash +- pokemart MARTTYPE_STANDARD, MART_GOLDENROD_5F_3 +- closetext +- end +- +-.both +- pokemart MARTTYPE_STANDARD, MART_GOLDENROD_5F_4 ++ pokemart MARTTYPE_STANDARD, MART_GOLDENROD_5F + closetext + end + + ... + + .VeryHappy: + writetext UnknownText_0x5615a + buttonsound ++ checkitem TM_RETURN ++ iftrue .AlreadyGotTM + verbosegiveitem TM_RETURN +- iffalse .Done + setflag ENGINE_GOLDENROD_DEPT_STORE_TM27_RETURN + closetext + end + + ... + + .NotVeryHappy: + writetext UnknownText_0x561d8 + buttonsound ++ checkitem TM_FRUSTRATION ++ iftrue .AlreadyGotTM + verbosegiveitem TM_FRUSTRATION +- iffalse .Done + setflag ENGINE_GOLDENROD_DEPT_STORE_TM27_RETURN + closetext + end ++ ++.AlreadyGotTM: ++ writetext GoldenrodDeptStore5FAlreadyGotTMText ++ waitbutton ++ closetext ++ end + + ... + + UnknownText_0x56202: + text "There are sure to" + line "be TMs that are" + + para "just perfect for" + line "your #MON." + done ++ ++GoldenrodDeptStore5FAlreadyGotTMText: ++ text "Oh, you already" ++ line "have this TM…" ++ done +``` + +We did two things there: simplify the clerk script to just use a single Mart which won't have either redundant TM; and add clauses to the happiness-check lady that only allow one of each TM. + +That introduced the single `MART_GOLDENROD_5F` constant, so edit [constants/mart_constants.asm](../blob/master/constants/mart_constants.asm) (or [constants/item_data_constants.asm](../blob/master/constants/item_data_constants.asm) in older versions of pokecrystal): + +```diff +- const MART_GOLDENROD_5F_1 +- const MART_GOLDENROD_5F_2 +- const MART_GOLDENROD_5F_3 +- const MART_GOLDENROD_5F_4 ++ const MART_GOLDENROD_5F +``` + +Finally, edit [data/items/marts.asm](../blob/master/data/items/marts.asm): + +```diff +- dw MartGoldenrod5F1 +- dw MartGoldenrod5F2 +- dw MartGoldenrod5F3 +- dw MartGoldenrod5F4 ++ dw MartGoldenrod5F + ... + +-MartGoldenrod5F1: ++MartGoldenrod5F: + db 3 ; # items + db TM_THUNDERPUNCH + db TM_FIRE_PUNCH + db TM_ICE_PUNCH + db -1 ; end +- +-MartGoldenrod5F2: +- db 4 ; # items +- db TM_THUNDERPUNCH +- db TM_FIRE_PUNCH +- db TM_ICE_PUNCH +- db TM_HEADBUTT +- db -1 ; end +- +-MartGoldenrod5F3: +- db 4 ; # items +- db TM_THUNDERPUNCH +- db TM_FIRE_PUNCH +- db TM_ICE_PUNCH +- db TM_ROCK_SMASH +- db -1 ; end +- +-MartGoldenrod5F4: +- db 5 ; # items +- db TM_THUNDERPUNCH +- db TM_FIRE_PUNCH +- db TM_ICE_PUNCH +- db TM_HEADBUTT +- db TM_ROCK_SMASH +- db -1 ; end + + ... + + MartCeladon3F: + db 5 ; # items +- db TM_HIDDEN_POWER +- db TM_SUNNY_DAY ++ db TM_PSYCH_UP + db TM_PROTECT +- db TM_RAIN_DANCE +- db TM_SANDSTORM ++ db TM_THUNDERPUNCH ++ db TM_FIRE_PUNCH ++ db TM_ICE_PUNCH + db -1 ; end +``` + +We simplified the Goldenrod Mart data, and revised the Celadon Mart's inventory: they still sell TM17 Protect, but also the same three Punch TMs as Goldenrod, as well as TM09 Psych Up (which was only available held by Abra or Kadabra traded from Gen 1). + +Speaking of trading from Gen 1… + + +## 7. Don't let Time Capsule traded Pokémon hold TMs + +This is a minor issue that you might not even care about, but it's worth knowing about and simple to fix. + +Pokémon traded from Gen 1 via the Time Capsule hold items based on their "catch rate" byte. Seven Pokémon have catch rates equivalent to TMs: + +- Abra and Kadabra: 200 = $C8 = `TM_PSYCH_UP` (although Kadabra's catch rate in Yellow is 96 = $60 = `TWISTEDSPOON`) +- Krabby, Horsea, Goldeen, and Staryu: 225 = $E1 = `TM_ICE_PUNCH` +- Nidoran♀ and Nidoran♂: 235 = $EB = `TM_DETECT` + +Some other Pokémon have catch rates equivalent to unused item slots, so the `TimeCapsule_CatchRateItems` table exists to convert those catch rate values into valid items. We can reuse that mechanism to convert held TMs into ordinary items. + +Edit [data/items/catch_rate_items.asm](../blob/master/data/items/catch_rate_items.asm): + +```diff + TimeCapsule_CatchRateItems: + db ITEM_19, LEFTOVERS + db ITEM_2D, BITTER_BERRY + db ITEM_32, GOLD_BERRY + db ITEM_5A, BERRY + db ITEM_64, BERRY + db ITEM_78, BERRY + db ITEM_87, BERRY + db ITEM_BE, BERRY + db ITEM_C3, BERRY + db ITEM_DC, BERRY + db ITEM_FA, BERRY ++ db TM_PSYCH_UP, BERRY ++ db TM_ICE_PUNCH, BERRY ++ db TM_DETECT, BERRY + db -1, BERRY + db 0 ; end +``` + +That's everything! + + + +There's still room for improvement here. The TM Pocket still uses a byte to store each TM's quantity, even though they should all be 0 or 1. It could be rewritten to work like the Key Items pocket, with just the item IDs stored, thus saving 57 bytes of WRAM. + +(Actually, since TMs also have a fixed order from TM01 to HM07, the pocket could use a `flag_array` with `NUM_TMS + NUM_HMS` bits. But that would be even more complex to implement than a Key Items−style byte array.) |