-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Longer Move Names (13 or 14 characters)
Greetings. This tutorial will cover the modifications necessary to expand the maximum length of a move name from the vanilla 12 characters to 13 or 14 characters.
If you only need 13 characters, you can follow the first section and stop. For 14 characters, first follow Section 1, and then apply the additional changes of Section 2 on top.
If you are interested in understanding the full background of what makes longer move names a challenge, read on, otherwise, feel free to jump to Section 1.
Move names are described in data/moves/names.asm where you will see them defines as follows:
li "SKY ATTACK"
Here, li is a compiler side macro that simply appends an "@" to the end of the move name as a terminal.
In otherwords, the name of the move is stored in the game more like this:
SKY ATTACK@
(This is simplifying it slightly, but good enough for this explanation)
When the game needs to display a move, the GetMoveName function is called. This function will copy 20 bytes of data from the list of names, into wNameBuffer.
In the original game, move names are never more than 12 characters. Therefore, including the final "@", move names never take up more than 13 bytes. This means that wNameBuffer is already more than big enough to support longer names.
In the case of using TMs, a different buffer is used: wTempMoveNameBuffer this one is only designed to hold 14 bytes, so we'll take care with this one later in the tutorial.
The text box is able to display 18 characters in the first row, and (typically) 17 characters in subsequent rows. The reason for the difference here is the downward facing arrow icon we see at the end of line 2. In some cases, the arrow is not displayed, which allows for 18 characters on line 2.
With this in mind, as we increase the maximum length of move names, we need to account for every case of move names being displayed in the text box.
| Scenario | Image | Problem for 13 Characters? | Problem for 14 Characters? |
|---|---|---|---|
| Booting up TM | ![]() |
X | X |
| Use TM? | ![]() |
✔ | ✔ |
| Pokemon incompatible with TM | ![]() |
X | X |
| Can't learn the move | ![]() |
X | X |
| Move already known | ![]() |
X | X |
| Trying to learn move | ![]() |
X | X |
| Make room for move? | ![]() |
X | ✔ |
| Abandon learning move? | ![]() |
X | X |
| Did not learn move | ![]() |
X | X |
| Learned move | ![]() |
X | X |
| Player's Pokemon used move | ![]() |
✔ | ✔ |
| Opponents's Pokemon used move | ![]() |
✔ | ✔ |
| Move was disabled | ![]() |
X | ✔ |
| Move is disabled | ![]() |
X | X |
| Temporarily learned a move via Mimic | ![]() |
X | X |
Aside from text boxes, we can see move names displayed on the following screens:
| Scenario | Image | Problem for 13 Characters? | Problem for 14 Characters? |
|---|---|---|---|
| Status screen | ![]() |
X | X |
| Fight menu | ![]() |
X | ✔ |
| Select a move to Mimic screen | ![]() |
X | ✔ |
| PP UP or restore PP with an Ether | ![]() |
X | ✔ |
| Select a move to forget screen | ![]() |
X | ✔ |
Interestingly, we can see that move selection boxes always have space for 13 character move names. When we consider that the smallest buffer used for a move name is 14 bytes (13 characters + "@"), we might imagine that at some point during development, Game Freak were planning to have at least one more with a 13 character name. This is purely speculation, but if I had to guess, I think POISONPOWDER may have been POISON POWDER at one point.
The game is already broadly able to deal with 13 character move names. We only need to make changes to deal with two scenarios:
For this one, I recommend simply tweaking the text. To do that, modify data/text/text_6.asm
_TeachMachineMoveText::
text "It contained"
line "@"
text_ram wStringBuffer
text "!"
- para "Teach @"
+ para "Teach a #MON"
+ line "@"
text_ram wStringBuffer
+ text "?"
- text_start
- line "to a #MON?"
doneThe result in game should look like this:
As you can see, this also makes plenty of space for the additional increase to 14 characters.
I chose the title for this section deliberately to highlight a point.
If we look at the example I showed above, it appears we could easily move the "used" portion of the text onto the above line. However, when an enemy uses an attack, "Enemy" is added before the Pokemon's name. The Pokemon's name is up to 10 characters, meaning the first line is pretty full.
There is no one single correct answer to this problem, but before showing what I did, I'll mention all the alternatives that I came up with. One of these options may be more to your taste.
- Split the text over 3 lines
- I personally feel this would make the battles feel more sluggish
- Remove the "Enemy" part of the text, and place "used" on the top line
- This may be a little confusing during a classic battle of METAPOD vs METAPOD.
- "Foe VICTREEBEL used"
- Unfortunately, this is still 19 characters, so no good.
- Change the text, for example to:
Enemy VICTREEBEL:
MATCHA GOTCHA
or
Enemy VICTREEBEL's
MATCHA GOTCHA
I just didn't really like these, but there's nothing wrong with this approach.
For my implementation, I ultimately opted for a hybrid approach. If a move name is 12 characters or less, everything will display as per the original game. If the move name exceeds 12 characters, "Enemy" will be omitted and "used" will be appended to line 1.
Modify constant/charmap.asm
...
charmap "<ROCKET>", $5e ; "ROCKET"
charmap "<DEXEND>", $5f
+ charmap "<USER2>", $78 ; Same as <USER> but appends "Enemy" conditionally
...
charmap "ぁ", $76 ; hiragana small a, unused
charmap "ぇ", $77 ; hiragana small e, unused
- charmap "ぉ", $78 ; hiragana small o, unusedModify home/text.asm
...
dict "<TARGET>", PlaceMoveTargetsName
dict "<USER>", PlaceMoveUsersName
+ dict "<USER2>", PlaceMoveUsersName2
...
PlaceMoveTargetsName::
ldh a, [hWhoseTurn]
xor 1
jr PlaceMoveUsersName.place
+PlaceMoveUsersName2::
+ ldh a, [hWhoseTurn]
+
+.place:
+ push de
+ and a
+ jr z, PlaceMoveUsersName.jumpInFrom2
+; enemy: determine if move name is 13 or more characters or not
+ push hl
+ ld hl, wStringBuffer
+ ld a, 13
+ ld d, a
+.loop
+ ld a, [hli]
+ cp $50 ;@
+ jr z, .twelveOrLess
+ dec d
+ jr nz, .loop
+.thirteenOrMore
+ pop hl
+ jr PlaceMoveUsersName.enemy2
+
+.twelveOrLess
+ pop hl
+ jr PlaceMoveUsersName.enemy
PlaceMoveUsersName::
ldh a, [hWhoseTurn]
.place:
push de
and a
jr nz, .enemy
+.jumpInFrom2
ld de, wBattleMonNick
jr PlaceCommandCharacter
.enemy
ld de, EnemyText
call PlaceString
+.enemy2
ld h, b
ld l, c
ld de, wEnemyMonNick
; fallthrough
PlaceCommandCharacter::
call PlaceString
ld h, bModify data/text/text_2.asm
...
_MonName1Text::
- text "<USER>@"
+ text "<USER2>@"
text_end
_Used1Text::
text_start
line "used @"
text_end
_Used2Text::
text_start
line "used @"
text_end
+_UsedSameLineText::
+ text " used"
+ line "@"
+ text_endModify engine/battle/core.asm
...
MonName1Text:
text_far _MonName1Text
text_asm
ldh a, [hWhoseTurn]
and a
ld a, [wPlayerMoveNum]
ld hl, wPlayerUsedMove
jr z, .playerTurn
ld a, [wEnemyMoveNum]
ld hl, wEnemyUsedMove
.playerTurn
ld [hl], a
ld [wMoveGrammar], a
+ push bc
+ ld hl, wStringBuffer
+ ld a, 13
+ ld c, a
+.loop
+ ld a, [hli]
+ cp $50 ;@
+ jr z, .twelveOrLess
+ dec c
+ jr nz, .loop
+.thirteenOrMore
+ pop bc
+ call DetermineExclamationPointTextNum ; useless, eliminate it later
+ ld hl, UsedSameLineText
+ ret
+.twelveOrLess
+ pop bc
call DetermineExclamationPointTextNum
ld a, [wMonIsDisobedient]
and a
ld hl, Used2Text
ret nz
ld a, [wMoveGrammar]
cp 3
ld hl, Used2Text
ret c
ld hl, Used1Text
ret
+UsedSameLineText:
+ text_far _UsedSameLineText
+ text_asm
+ jr PrintInsteadText
Used1Text:
text_far _Used1Text
text_asm
jr PrintInsteadTextThe new <USER2> text function we just created works identically to <USER> when the move user is the player. However, when the enemy is attacking, it checks the length of the move name by searching for the "@" symbol. If "@" if not found in the first 13 characters, the move name is determined to be longer than 12 characters. In this case, it displays the enemy Pokemon name without appending "Enemy" to the beginning.
The second part of what we did is to use alternative "used" text when the move name exceeds 12 characters. The logic is similar, but this one runs in the battle core. Additionally, this is applied for both the player and the opponent.
To test this new functionality, I recommend temporaily changing the name of LEECH LIFE, first to an 11 character name, then 12 and then 13. Go to Mt.Moon and battle some Zubats. In the case of 11 characters it should display:
Enemy ZUBAT
used ELEVEN CHAR!
For 12 characters, it should display like this:
Enemy ZUBAT
used TWELVE CHARA!
And for 13 characters it should display like this:
ZUBAT used
THIRTEEN CHAR!
For this part of the tutorial, I will be using MOONGEIST BEAM as my go-to 14 character move.
Before we return to the topic of making things fit on the screen, we need to look at that 14 byte buffer I mentioned in the introduction: wTempMoveNameBuffer.
This buffer is only used when learning a move via TM/HM. (Caution: if you have implemented move tutors, it may also be used there)
Because only 14 bytes are copied to the buffer, when we try to teach a TM with a 14 character name, we get a result like this:
This is likely because the "@" terminator is missing.
Fortunately, there are two unused bytes after the buffer in the game, so it is a simple case of increasing the buffer size, and changing the code to copy 15 bytes instead of 14.
Modify ram/wram.asm
...
UNION
-wTempMoveNameBuffer:: ds 14
+wTempMoveNameBuffer:: ds 15
NEXTU
; The name of the mon that is learning a move.
wLearnMoveMonName:: ds NAME_LENGTH
ENDU
- ds 2
+ ds 1
...Modify engine/items/item_effects.asm
...
.useMachine
ld a, [wWhichPokemon]
push af
ld a, [wCurItem]
push af
.chooseMon
ld hl, wStringBuffer
ld de, wTempMoveNameBuffer
- ld bc, 14
+ ld bc, 15
call CopyData ; save the move name because DisplayPartyMenu will overwrite it
ld a, $ff
ld [wUpdateSpritesEnabled], a
ld a, TMHM_PARTY_MENU
ld [wPartyMenuTypeOrMessageID], a
call DisplayPartyMenu
push af
ld hl, wTempMoveNameBuffer
ld de, wStringBuffer
- ld bc, 14
+ ld bc, 15
call CopyData
...That should have solved the issue as shown below, and we can now move onto the adjustments.
This mod deals with the issue we see in the last image of the previous section. My solution for this is to modify the text as follows:
Before:
"Delete an older"
"move to make room"
"for MOONGEIST BEAM?"
After:
"Delete a move to"
"make room for"
"MOONGEIST BEAM?"
Of course, tweak this to your personal taste, but the point is to remove "for" from the final line.
Modify data/text/text_4.asm
...
- para "Delete an older"
- line "move to make room"
- cont "for @"
+ para "Delete a move"
+ line "to make room for"
+ cont "@"
text_ram wStringBuffer
text "?"
done
...The result should look something like this:
For this one, we simple need to move "was" to the following line.
Modify data/text/text_3.asm
_MoveWasDisabledText::
text "<TARGET>'s"
line "@"
text_ram wNameBuffer
- text " was"
- cont "disabled!"
+ text_start
+ cont "was disabled!"
promptResult:
With the text box scenarios out of the way, we just need to deal with the move selection boxes, we'll start by doing three at once, the fight menu, the Mimic menu, and the PP restore/PP UP menu.
Modify engine/battle/core.asm
...
.regularmenu
call AnyMoveToSelect
ret z
ld hl, wBattleMonMoves
call .loadmoves
- hlcoord 4, 12
+ hlcoord 3, 12
ld b, 4
- ld c, 14
+ ld c, 15
di ; out of pure coincidence, it is possible for vblank to occur between the di and ei
; so it is necessary to put the di ei block to not cause tearing
call TextBoxBorder
- hlcoord 4, 12
+ hlcoord 3, 12
ld [hl], "─"
hlcoord 10, 12
ld [hl], "┘"
ei
- hlcoord 6, 13
+ hlcoord 5, 13
call .writemoves
- ld b, $5
+ ld b, $4
ld a, $c
jr .menuset
.mimicmenu
ld hl, wEnemyMonMoves
call .loadmoves
hlcoord 0, 7
ld b, 4
- ld c, 14
+ ld c, 15
call TextBoxBorder
hlcoord 2, 8
call .writemoves
ld b, $1
ld a, $7
jr .menuset
.relearnmenu
ld a, [wWhichPokemon]
ld hl, wPartyMon1Moves
ld bc, wPartyMon2 - wPartyMon1
call AddNTimes
call .loadmoves
- hlcoord 4, 7
+ hlcoord 3, 7
ld b, 4
- ld c, 14
ld c, 15
call TextBoxBorder
- hlcoord 6, 8
+ hlcoord 5, 8
call .writemoves
- ld b, $5
+ ld b, $4
ld a, $7
...Result:
| Scenario | Image |
|---|---|
| Fight Menu | ![]() |
| Mimic Menu | ![]() |
| PP UP or PP Restore | ![]() |
Although the box for selecting a move to forget (when learning a new move) looks identical to the three boxes in the previous section, it is coded separately.
Modify engine/pokemon/learn_move.asm
...
.loop
push hl
ld hl, WhichMoveToForgetText
call PrintText
- hlcoord 4, 7
+ hlcoord 3, 7
ld b, 4
- ld c, 14
+ ld c, 15
call TextBoxBorder
- hlcoord 6, 8
+ hlcoord 5, 8
ld de, wMovesString
ldh a, [hUILayoutFlags]
set BIT_SINGLE_SPACED_LINES, a
ldh [hUILayoutFlags], a
call PlaceString
ldh a, [hUILayoutFlags]
res BIT_SINGLE_SPACED_LINES, a
ldh [hUILayoutFlags], a
ld hl, wTopMenuItemY
ld a, 8
ld [hli], a ; wTopMenuItemY
- ld a, 5
+ ld a, 4
ld [hli], a ; wTopMenuItemXResult:
And with that, you have successfully increased the character limit to 14.
This tutorial does not cover even longer move names, but if you want to attempt it, here are some things to consider:
- The move selection box on the party screen will grow even larger, meaning that some party icons will need to be hidden. Otherwise, they will show up on top of the box.
- The in-battle text string "is disabled" will become problematic, so care needs to be taken there.
Since the number of moves with more than 14 characters is relatively small, you might consider the ILLUSIYELLOW approach.
This involves creating some narrow font characters and including them in the empty spaces of: gfx/font/font.png
The same technique can also be used to deal with Pokemon names that exceed 10 characters, and here is some video covering that topic if it is of interest: LEASTPOLTEISPHALLTER
Thank you for reading this tutorial. I was careful to ensure that I covered all of the scenarios, but if you find something which I didn't cover, please let me know by messaging my on discord, in the pret server @porygondolier.
Finally, I'd like to wish you a wonderful day!






















