Skip to content
Rangi edited this page Jul 21, 2018 · 18 revisions

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:

 add_tm: MACRO
 if !DEF(TM01)
 TM01 = const_value
 	enum_start 1
 endc
 	define _\@_1, "TM_\1"
 	const _\@_1
 	enum \1_TMNUM
 ENDM
 
 ; see data/moves/tmhm_moves.asm for moves
 	add_tm DYNAMICPUNCH ; bf
 	...
 	add_tm NIGHTMARE    ; f2
+	add_tm AEROBLAST
 NUM_TMS = const_value - TM01 - 2 ; discount ITEM_C3 and ITEM_DC

 add_hm: MACRO
 if !DEF(HM01)
 HM01 = const_value
 endc
 	define _\@_1, "HM_\1"
 	const _\@_1
 	enum \1_TMNUM
 ENDM
 
 	add_hm CUT          ; f3
 	...
 	add_hm WATERFALL    ; f9
+	add_hm SOFTBOILED
 NUM_HMS = const_value - HM01

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 data/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:

 	db "TM50@"
+	db "TM51@"
 	db "HM01@"
 	...
 	db "HM07@"
+	db "HM08@"
 	db "TERU-SAMA@"
-	db "TERU-SAMA@"
-	db "TERU-SAMA@"
 	db "TERU-SAMA@"
 	db "TERU-SAMA@"
 	db "TERU-SAMA@"
 	db "?@"

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
 ; 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
 ; 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

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

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/:

  • 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 learnable moves have to be in the same order as the add_tm and add_hm lines, since that's what they're referencing: the tmhm macro turns NIGHTMARE into NIGHTMARE_TMNUM, SOFTBOILED into SOFTBOILED_TMNUM, etc, and then processes them to efficiently store learnsets. (We'll get to the details of how it works next.)

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

There are 50 TMs, 7 HMs, and 3 tutor moves; they each have an associated *_TMNUM constant, from 1 to 60. Adding TM51 Aeroblast and HM08 Softboiled brings the total to 62. If you end up with more than 64 learnable moves, you'll have to start updating the tmhm macro in data/pokemon/base_stats.asm.

Here's the macro as-is:

tmhm: MACRO
; used in data/pokemon/base_stats/*.asm
tms1 = 0 ; TM01-TM24 (24)
tms2 = 0 ; TM25-TM48 (24)
tms3 = 0 ; TM49-TM50 + HM01-HM07 + MT01-MT03 (12/24)
rept _NARG
	if DEF(\1_TMNUM)
	if \1_TMNUM < 24 + 1
tms1 = tms1 | (1 << ((\1_TMNUM) - 1))
	elif \1_TMNUM < 48 + 1
tms2 = tms2 | (1 << ((\1_TMNUM) - 1 - 24))
	else
tms3 = tms3 | (1 << ((\1_TMNUM) - 1 - 48))
	endc
	else
		fail "\1 is not a TM, HM, or move tutor move"
	endc
	shift
endr
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)
	db tms3 & $ff
tms3 = tms3 >> 8
endr
ENDM

Basically it defines three variables tms1, tms2, and tms3, each of which can hold up to three bytes (that's 24 bits), and sets their bits according to which moves were listed; then it outputs those variables one byte at a time with db statements. Three variables with 24 bits each are sufficient for 72 learnable moves, but only eight dbs are output, so beyond 64 moves we already have to update tmhm.

Here's how to output a ninth byte, allowing up to 72 learnable moves:

 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-TM72 (24/24)
 	db tms3 & $ff
 tms3 = tms3 >> 8
 endr

Here's how to add a tenth byte (which also needs a fourth variable), allowing up to 80 learnable moves:

 tmhm: MACRO
 ; used in data/pokemon/base_stats/*.asm
 tms1 = 0 ; TM01-TM24 (24)
 tms2 = 0 ; TM25-TM48 (24)
 tms3 = 0 ; TM49-TM50 + HM01-HM07 + MT01-MT03 (12/24)
+tms4 = 0 ; TM73-TM80 (8/24)
 rept _NARG
 	if DEF(\1_TMNUM)
 	if \1_TMNUM < 24 + 1
 tms1 = tms1 | (1 << ((\1_TMNUM) - 1))
 	elif \1_TMNUM < 48 + 1
 tms2 = tms2 | (1 << ((\1_TMNUM) - 1 - 24))
-	else
+	elif \1_TMNUM < 72 + 1
 tms3 = tms3 | (1 << ((\1_TMNUM) - 1 - 48))
+	else
+tms4 = tms3 | (1 << ((\1_TMNUM) - 1 - 72))
 	endc
 	else
 		fail "\1 is not a TM, HM, or move tutor move"
 	endc
 	shift
 endr
 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-TM72 (24/24)
 	db tms3 & $ff
 tms3 = tms3 >> 8
 endr
+rept 1 ; TM73-TM80 (8/24)
+	db tms4 & $ff
+tms4 = tms4 >> 8
+endr
 ENDM

You can probably continue the pattern from here. The fourth variable tms4 will let you output 10, 11, or 12 bytes; then if you need more than 96 learnable moves, you can add a fifth variable and output 13, 14, or 15 bytes for up to 120 learnable moves; and so on.

Actually, as soon as you add that tenth byte, the base data will become too large to fit in a ROM bank. You'll get an error when you run make:

error: Section 'bank14' is too big (max size = 0x4000 bytes).

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

First edit all the base data files, removing these three lines from each:

  • db 100 ; unknown 1
  • db 5 ; unknown 2
  • db 0, 0, 0, 0 ; padding

Then edit wram.asm:

 ; corresponds to the data/pokemon/base_stats/*.asm contents
 wCurBaseData:: ; d236
 wBaseDexNo:: db ; d236
 wBaseStats:: ; d237
 wBaseHP:: db ; d237
 wBaseAttack:: db ; d238
 wBaseDefense:: db ; d239
 wBaseSpeed:: db ; d23a
 wBaseSpecialAttack:: db ; d23b
 wBaseSpecialDefense:: db ; d23c
 wBaseType:: ; d23d
 wBaseType1:: db ; d23d
 wBaseType2:: db ; d23e
 wBaseCatchRate:: db ; d23f
 wBaseExp:: db ; d240
 wBaseItems:: ; d241
 wBaseItem1:: db ; d241
 wBaseItem2:: db ; d242
 wBaseGender:: db ; d243
-wBaseUnknown1:: db ; d244
 wBaseEggSteps:: db ; d245
-wBaseUnknown2:: db ; d246
 wBasePicSize:: db ; d247
-wBasePadding:: ds 4 ; d248
 wBaseGrowthRate:: db ; d24c
 wBaseEggGroups:: db ; d24d
 wBaseTMHM:: flag_array NUM_TM_HM_TUTOR ; d24e
 wCurBaseDataEnd::

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.

Clone this wiki locally