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/item_data_constants.asm](../blob/master/constants/item_data_constants.asm): ```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! ![Screenshot](screenshots/infinite-tms.png) 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.)