Add a new TM or HM - nickjwilde/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.)
- Define constants with
add_tm
oradd_hm
- Define standard item data
- Update the TM/HM move table
- Add the TM or HM to base learnsets
- Make the HM move unforgettable
- Adding up to 120 new TMs or HMs
Edit constants/item_constants.asm:
...
; see data/moves/tmhm_moves.asm for moves
TM01 EQU const_value
add_tm DYNAMICPUNCH ; bf
...
add_tm NIGHTMARE ; f2
+ add_tm AEROBLAST
NUM_TMS EQU __tmhm_value__ - 1
...
add_hm CUT ; f3
...
add_hm WATERFALL ; f9
+ add_hm SOFTBOILED
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.)
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.
NOTE: This step is no longer required in current versions of pokecrystal. 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.
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 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.)
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:
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
:
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 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 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
BASE_DEX_NO rb
BASE_STATS rb NUM_STATS
rsset BASE_STATS
BASE_HP rb
BASE_ATK rb
BASE_DEF rb
BASE_SPD rb
BASE_SAT rb
BASE_SDF rb
BASE_TYPES rw
rsset BASE_TYPES
BASE_TYPE_1 rb
BASE_TYPE_2 rb
BASE_CATCH_RATE rb
BASE_EXP rb
BASE_ITEMS rw
rsset BASE_ITEMS
BASE_ITEM_1 rb
BASE_ITEM_2 rb
BASE_GENDER rb
- rb_skip
BASE_EGG_STEPS rb
- rb_skip
BASE_PIC_SIZE rb
-BASE_FRONTPIC rw
-BASE_BACKPIC rw
BASE_GROWTH_RATE rb
BASE_EGG_GROUPS rb
BASE_TMHM rb (NUM_TM_HM_TUTOR + 7) / 8
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.
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.