Add a new TM or HM - pret/pokecrystal GitHub Wiki

This tutorial is for how to add a new TM or HM. As an example, we'll add TM51 Aeroblast and HM08 Softboiled.

(I'm using Softboiled as an HM example because it already has a field effect outside of battle. Creating new field-effect moves is beyond the scope of this tutorial.)

Contents

  1. Define constants with add_tm or add_hm
  2. Define standard item data
  3. Update the TM/HM move table
  4. Add the TM or HM to base learnsets
  5. Make the HM move unforgettable
  6. Adding up to 120 new TMs or HMs

1. Define constants with add_tm or add_hm

Edit constants/item_constants.asm:

 ...
 ; see data/moves/tmhm_moves.asm for moves
 DEF TM01 EQU const_value
 	add_tm DYNAMICPUNCH ; bf
 	...
 	add_tm NIGHTMARE    ; f2
+	add_tm AEROBLAST
 DEF NUM_TMS EQU __tmhm_value__ - 1

 ...

 	add_hm CUT          ; f3
 	...
 	add_hm WATERFALL    ; f9
+	add_hm SOFTBOILED
 DEF NUM_HMS EQU __tmhm_value__ - NUM_TMS - 1

The add_tm and add_hm macros simultaneously define the next item constant (TM_AEROBLAST and HM_SOFTBOILED respectively) and the next TMNUM constant (AEROBLAST_TMNUM and SOFTBOILED_TMNUM). The item constants are used for giveitem scripts, in Mart inventories, etc. The TMNUM constants are not used directly, but get referred to by the tmhm learnsets in Pokémon base data. (We'll see how that works later.)

(This also demonstrates why Rock Smash would be an inconvenient example for adding a new HM. TM08 is already Rock Smash, and we can't define ROCK_SMASH_TMNUM twice, so we would have to do the extra work of replacing TM08 with some other move.)

2. Define standard item data

First of all, unlike regular items, we don't need to edit data/items/descriptions.asm or engine/items/item_effects.asm. The ItemDescriptions table already has dummy "?" descriptions for all the items from TM01 and up; and the ItemEffects table ends right before TM01 (since TMs and HMs don't use those effects anyway).

Edit data/items/names.asm:

ItemNames::
	list_start ItemNames
	li "MASTER BALL"
	...
	li "TM50"
+	li "TM51"
	assert_list_length NUM_ITEMS + NUM_TMS + 2 ; count ITEM_C3 and ITEM_DC
	li "HM01"
	...
	li "HM07"
+	li "HM08"
	assert_list_length NUM_ITEMS + NUM_TMS + 2 + NUM_HMS ; count ITEM_C3 and ITEM_DC
	li "TERU-SAMA"
-	li "TERU-SAMA"
-	li "TERU-SAMA"
	li "TERU-SAMA"
	li "TERU-SAMA"
	li "TERU-SAMA"
	li "?"

And edit data/items/attributes.asm:

 ; TM50
 	item_attribute 2000, HELD_NONE, 0, CANT_SELECT, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE
+; TM51
+	item_attribute 3000, HELD_NONE, 0, CANT_SELECT, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE
	assert_table_length NUM_ITEMS + NUM_TMS + 2 ; count ITEM_C3 and ITEM_DC
 ; HM01
 	item_attribute 0, HELD_NONE, 0, CANT_SELECT | CANT_TOSS, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE
 ...
 ; HM07
 	item_attribute 0, HELD_NONE, 0, CANT_SELECT | CANT_TOSS, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE
+; HM08
+	item_attribute 0, HELD_NONE, 0, CANT_SELECT | CANT_TOSS, TM_HM, ITEMMENU_PARTY, ITEMMENU_NOUSE
	assert_table_length NUM_ITEMS + NUM_TMS + 2 + NUM_HMS ; count ITEM_C3 and ITEM_DC
 ; ITEM_FA
 	item_attribute $9999, HELD_NONE, 0, NO_LIMITS, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
-; $fb
-	item_attribute $9999, HELD_NONE, 0, NO_LIMITS, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
-; $fc
-	item_attribute $9999, HELD_NONE, 0, NO_LIMITS, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
 ; $fd
 	item_attribute $9999, HELD_NONE, 0, NO_LIMITS, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
 ; $fe
 	item_attribute $9999, HELD_NONE, 0, NO_LIMITS, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
 ; $ff
 	item_attribute $9999, HELD_NONE, 0, NO_LIMITS, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
 ; $00
 	item_attribute $9999, HELD_NONE, 0, NO_LIMITS, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
	assert_table_length $100

Notice how the ItemNames and ItemAttributes both already had the maximum 256 entries, so we had to remove dummy entries to fit TM51 and HM08. And there aren't many dummy entries; 251 items are defined, from $00 to $FA. If you want a lot of new TMs or HMs, you'll have to remove some unused items. There are 26 unused ITEM_XX constants, counting ITEM_C3 and ITEM_DC, which interrupt the sequence of TMs and need a bit of special handling to remove.

3. Update the TM/HM move table

NOTE: This only applies to older versions of pokecrystal made before July 6th, 2020. DO NOT FOLLOW THIS STEP if your copy is more recent.

Edit data/moves/tmhm_moves.asm:

 TMHMMoves:
 ; entries correspond to *_TMNUM enums (see constants/item_constants.asm)
 ; TMs
 	db DYNAMICPUNCH
 	...
 	db NIGHTMARE
+	db AEROBLAST
 ; HMs
 	db CUT
 	...
 	db WATERFALL
+	db SOFTBOILED

This associates the AEROBLAST_TMNUM TM/HM constant with the AEROBLAST move constant, and the SOFTBOILED_TMNUM TM/HM constant with the SOFTBOILED move constant.

4. Add the TM or HM to base learnsets

So far we've created items for TM51 and HM08 and assigned their moves, but the items aren't compatible with any Pokémon. So edit the tmhm entries in data/pokemon/base_stats/*.asm:

  • chansey.asm: ..., FLASH, FLAMETHROWER, ......, FLASH, SOFTBOILED, FLAMETHROWER, ...
  • blissey.asm: ..., FLASH, FLAMETHROWER, ......, FLASH, SOFTBOILED, FLAMETHROWER, ...
  • lugia.asm: ..., NIGHTMARE, FLY, ......, NIGHTMARE, AEROBLAST, FLY, ...
  • mew.asm: ..., NIGHTMARE, CUT, ..., WATERFALL, FLAMETHROWER, ......, NIGHTMARE, AEROBLAST, CUT, ..., WATERFALL, SOFTBOILED, FLAMETHROWER, ...

The new moves are listed here in the middle of their lists to match the formatting of the base game, but due to the way that the tmhm macro works, the order in which moves are declared in the Pokémon's files doesn't actually matter as long as they're all valid TM/HM moves! You can just add your new move(s) to the end of the list if you like.

5. Make the HM move unforgettable

Edit home/hm_moves.asm:

 .HMMoves:
 	db CUT
 	db FLY
 	db SURF
 	db STRENGTH
 	db FLASH
 	db WATERFALL
 	db WHIRLPOOL
+	db SOFTBOILED
 	db -1 ; end

Now Softboiled can't be forgotten except via the Move Deleter in Blackthorn City.

Anyway, that's all:

Screenshot

6. Adding up to 120 new TMs or HMs

As you keep adding new TMs or HMs, at one point the base data will become too large to fit in a ROM bank and you'll get an error when you run make.

One way to fix this is to remove the six unknown/padding bytes from all the base data.

First edit all the data/pokemon/base_stats/*.asm files, removing these three lines from each:

  • db 100 ; unknown 1
  • db 5 ; unknown 2
  • dw NULL, NULL ; unused (beta front/back pics)

Then edit ram/wram.asm:

 ; corresponds to the data/pokemon/base_stats/*.asm contents
 wCurBaseData::
 wBaseDexNo:: db
 wBaseStats::
 wBaseHP:: db
 wBaseAttack:: db
 wBaseDefense:: db
 wBaseSpeed:: db
 wBaseSpecialAttack:: db
 wBaseSpecialDefense:: db
 wBaseType::
 wBaseType1:: db
 wBaseType2:: db
 wBaseCatchRate:: db
 wBaseExp:: db
 wBaseItems::
 wBaseItem1:: db
 wBaseItem2:: db
 wBaseGender:: db
-wBaseUnknown1:: db
 wBaseEggSteps:: db
-wBaseUnknown2:: db
 wBasePicSize:: db
-wBaseUnusedFrontpic:: dw
-wBaseUnusedBackpic:: dw
 wBaseGrowthRate:: db
 wBaseEggGroups:: db
 wBaseTMHM:: flag_array NUM_TM_HM_TUTOR
 wCurBaseDataEnd::
	assert wCurBaseDataEnd - wCurBaseData == BASE_DATA_SIZE

+	ds 6
 ...

And also constants/pokemon_data_constants.asm:

 ; base data struct members (see data/pokemon/base_stats/*.asm)
 rsreset
 DEF BASE_DEX_NO      rb
 DEF BASE_STATS       rb NUM_STATS
 rsset BASE_STATS
 DEF BASE_HP          rb
 DEF BASE_ATK         rb
 DEF BASE_DEF         rb
 DEF BASE_SPD         rb
 DEF BASE_SAT         rb
 DEF BASE_SDF         rb
 DEF BASE_TYPES       rw
 rsset BASE_TYPES
 DEF BASE_TYPE_1      rb
 DEF BASE_TYPE_2      rb
 DEF BASE_CATCH_RATE  rb
 DEF BASE_EXP         rb
 DEF BASE_ITEMS       rw
 rsset BASE_ITEMS
 DEF BASE_ITEM_1      rb
 DEF BASE_ITEM_2      rb
 DEF BASE_GENDER      rb
-                     rb_skip
 DEF BASE_EGG_STEPS   rb
-                     rb_skip
 DEF BASE_PIC_SIZE    rb
-DEF BASE_FRONTPIC    rw
-DEF BASE_BACKPIC     rw
 DEF BASE_GROWTH_RATE rb
 DEF BASE_EGG_GROUPS  rb
 DEF BASE_TMHM        rb (NUM_TM_HM_TUTOR + 7) / 8
 DEF BASE_DATA_SIZE EQU _RS

Additionally, the label wBaseUnusedFrontpic is used once in home/pokemon.asm, but this reference is safe to comment out or delete entirely as it is only used in the Spaceworld Demo for Pokemon Gold.

-; Beta front and back sprites
-; (see pokegold-spaceworld's data/pokemon/base_stats/*)
-	ld hl, wBaseUnusedFrontpic
-	ld [hl], e
-	inc hl
-	ld [hl], d
-	inc hl
-	ld [hl], e
-	inc hl
-	ld [hl], d
-	jr .end ; useless

This functionality is unused in the final game, so deleting or commenting out the entire function should create no issues.

In older versions of pokecrystal, one additional step is needed. Head over to data/pokemon/base_stats.asm. The bytes needed for learnable moves are defined here, so we need to manually allocate the additional bytes.

 rept 3 ; TM01-TM24 (24/24)
	db _tms1 & $ff
 _tms1 = _tms1 >> 8
 endr
 rept 3 ; TM25-TM48 (24/24)
	db _tms2 & $ff
 _tms2 = _tms2 >> 8
 endr
-rept 2 ; TM49-TM50 + HM01-HM07 + MT01-MT03 (12/16)
+rept 3 ; TM49-TM50 + HM01-HM07 + MT01-MT03 (12/24)
 	db _tms3 & $ff
 _tms3 = _tms3 >> 8
 endr

This gives you room for eight moves. If you need more room you'll have to add a _tms4 segment above this section.

That gives you enough free space for seven extra base data bytes per Pokémon. Plus the eight that are already used for learnable moves, that's up to 15 tmhm bytes, which would allow 120 learnable moves: easily enough for the 100 TMs of Gen 7, plus HMs and tutors. If you somehow need even more than that, you can figure out how to save more bytes by packing the base data more compactly.

⚠️ **GitHub.com Fallback** ⚠️