1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
|
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)
8. [Remove text references to TMs being single-use](#8-remove-text-references-to-tms-being-single-use)
## 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, [wTempTMHM]
- 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, [wNamedObjectIndex]
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 [wTempTMHM], a
- ld de, wTempTMHM
- 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 [wBuySellItemPrice + 0], a
+ ld a, e
+ ld [wBuySellItemPrice + 1], a
+ ld a, 1
+ ld [wItemQuantityChange], a
+ ld a, 99
+ ld [wItemQuantity], a
+ farcall BuySell_MultiplyPrice
+ push hl
+ ld hl, hMoneyTemp
+ ldh a, [hProduct + 1]
+ ld [hli], a
+ ldh a, [hProduct + 2]
+ ld [hli], a
+ ldh a, [hProduct + 3]
+ ld [hl], a
+ pop hl
+ ret
+
+.AlreadyHaveTM:
+ ld hl, .AlreadyHaveTMText
+ call PrintText
+ call JoyWaitAorB
+ scf
+ ret
+
+.AlreadyHaveTMText:
+ text_far AlreadyHaveTMText
+ text_end
```
And edit [data/text/common_3.asm](../blob/master/data/text/common_3.asm):
```diff
_MartHowManyText::
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
checkcoins GOLDENRODGAMECORNER_TM25_COINS
ifequal HAVE_LESS, GoldenrodGameCornerPrizeVendor_NotEnoughCoinsScript
...
.Blizzard:
+ checkitem TM_BLIZZARD
+ iftrue GoldenrodGameCornerPrizeVendor_AlreadyHaveTMScript
checkcoins GOLDENRODGAMECORNER_TM14_COINS
ifequal HAVE_LESS, GoldenrodGameCornerPrizeVendor_NotEnoughCoinsScript
...
.FireBlast:
+ checkitem TM_FIRE_BLAST
+ iftrue GoldenrodGameCornerPrizeVendor_AlreadyHaveTMScript
checkcoins GOLDENRODGAMECORNER_TM38_COINS
ifequal HAVE_LESS, GoldenrodGameCornerPrizeVendor_NotEnoughCoinsScript
...
...
GoldenrodGameCornerTMVendor_FinishScript:
waitsfx
playsound SFX_TRANSACTION
writetext GoldenrodGameCornerPrizeVendorHereYouGoText
waitbutton
sjump GoldenrodGameCornerTMVendor_LoopScript
+
+GoldenrodGameCornerPrizeVendor_AlreadyHaveTMScript:
+ writetext GoldenrodGameCornerPrizeVendorAlreadyHaveTMText
+ waitbutton
+ sjump 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
checkcoins CELADONGAMECORNERPRIZEROOM_TM32_COINS
ifequal HAVE_LESS, CeladonPrizeRoom_notenoughcoins
...
.Psychic:
+ checkitem TM_PSYCHIC_M
+ iftrue CeladonPrizeRoom_alreadyhavetm
checkcoins CELADONGAMECORNERPRIZEROOM_TM29_COINS
ifequal HAVE_LESS, CeladonPrizeRoom_notenoughcoins
...
.HyperBeam:
+ checkitem TM_HYPER_BEAM
+ iftrue CeladonPrizeRoom_alreadyhavetm
checkcoins CELADONGAMECORNERPRIZEROOM_TM15_COINS
ifequal HAVE_LESS, CeladonPrizeRoom_notenoughcoins
...
...
CeladonPrizeRoom_purchased:
waitsfx
playsound SFX_TRANSACTION
writetext CeladonPrizeRoom_HereYouGoText
waitbutton
sjump CeladonPrizeRoom_tmcounterloop
+
+CeladonPrizeRoom_alreadyhavetm:
+ writetext CeladonPrizeRoom_AlreadyHaveTMText
+ waitbutton
+ sjump 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.
Eleven 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)
- TM29 Psychic: Mr Psychic (gift); Celadon Game Corner Prize (3500 coins)
- TM37 Sandstorm: Route 27 (gift); Celadon Dept. Store (¥2000)
- TM47 Steel Wing: Route 28 (gift); Rock Tunnel (item ball)
First, edit [maps/MrPsychicsHouse.asm](..blobs/master/maps/MrPsychicsHouse.asm):
```diff
MrPsychic:
faceplayer
opentext
- checkevent EVENT_GOT_TM29_PSYCHIC
+ checkitem TM_PSYCHIC_M
iftrue .AlreadyGotItem
writetext MrPsychicText1
buttonsound
verbosegiveitem TM_PSYCHIC_M
iffalse .Done
- setevent EVENT_GOT_TM29_PSYCHIC
...
```
This change just makes the check an item check instead of event check, so that if TM29 has been purchased from the Celadon Game Corner, Mr. Psychic won't give the player another one.
Now 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
- sjump .neither
-
-.headbutt
- checkevent EVENT_GOT_TM08_ROCK_SMASH
- iftrue .both
- sjump .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
...
GoldenrodDeptStore5FReceptionistThereAreTMsPerfectForMonText:
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
```
## 8. Remove text references to TMs being single-use
Edit [maps/VioletGym.asm](../blob/master/maps/VioletGym.asm):
```diff
FalknerTMMudSlapText:
text "By using a TM, a"
line "#MON will"
para "instantly learn a"
line "new move."
- para "Think before you"
- line "act--a TM can be"
- cont "used only once."
+ para "A TM can be used"
+ line "as many times as"
+ cont "you like."
para "TM31 contains"
line "MUD-SLAP."
para "It reduces the"
line "enemy's accuracy"
para "while it causes"
line "damage."
para "In other words, it"
line "is both defensive"
cont "and offensive."
done
```
And edit [maps/CeladonDeptStore3F.asm](../blob/master/maps/CeladonDeptStore3F.asm):
```diff
CeladonDeptStore3FYoungsterText:
text "I can't decide"
line "which #MON I"
para "should use this TM"
line "on…"
+
+ para "Lucky for me,"
+ line "it's reusable!"
done
```
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.)
|