Display more information on the move screen - pret/pokecrystal GitHub Wiki
Hello! This tutorial will show how to edit the pokΓ©mon move screen to add more information about the moves. The information you want to show can depend on other tutorials, like if you used the new physical/special split for moves and want to show that, but it is not particularly necessary for this tutorial, and you'll be able (hopefully) to customize the screen as you like.
Adding new types or changing move's category should not impact this tutorial.
This tutorial was made after the Move Reminder (Variant 2). Thank you all for that tutorial, it helped me a lot! And I hope this one can help you too.
- The move screen UI
- Understand the code
- Display move's accuracy
- Display move's effect chance
- Display OLD move's category
- Display NEW move's category
- Improving UI
- Results
Basically, the move screen will show the PokΓ©mon's moves and some information about each one of them. This screen also allows change the move's order. The move screen can be accessed by selecting the pokΓ©mon and then choosing the option "MOVE".
That will display a screen with the PokΓ©mon's:
- Menu icon
- Nickname
- Level
- Four moves
- Current and max PP of each move
- Move's information
- Type
- Attack power
- Description
See:
Just looking at the screen we can already understand that it doesn't have much room for more information, so we'll have to work on that.
Another thing to have in mind is that some information is organized by other scripts that affects how UI functions in the game. That makes not an easy task to move the PP position for instance, as the game uses the same mechanic for listing items in the bag or in pokΓ©mon marts. So let's leave that for a future tutorial.
Basically, all information can be edited in one place: engine/pokemon/mon_menu.asm.
Also, there's information about the moves in game that is not displayed to the player, so we'll be adding that later.
Before anything else, open the file above to continue the tutorial from this point on.
PlaceMoveData
is where the most information we need is located.
Let's make the code easier to read adding some comments, and at the same time, understand where each part of the code displays the UI we saw before:
PlaceMoveData:
xor a
ldh [hBGMapMode], a
+
+; Print UI elements
hlcoord 0, 10
ld de, String_MoveType_Top
call PlaceString
hlcoord 0, 11
ld de, String_MoveType_Bottom
call PlaceString
hlcoord 12, 12
ld de, String_MoveAtk
call PlaceString
+
+; Print move type
ld a, [wCurSpecies]
ld b, a
hlcoord 2, 12
predef PrintMoveType
+
+; Print move power
ld a, [wCurSpecies]
dec a
ld hl, Moves + MOVE_POWER
ld bc, MOVE_LENGTH
call AddNTimes
ld a, BANK(Moves)
call GetFarByte
hlcoord 16, 12
cp 2
jr c, .no_power
ld [wTextDecimalByte], a
ld de, wTextDecimalByte
lb bc, 1, 3
call PrintNum
jr .description
.no_power
ld de, String_MoveNoPower
call PlaceString
+; Print move description
.description
hlcoord 1, 14
predef PrintMoveDescription
ld a, $1
ldh [hBGMapMode], a
ret
+; UI elements
String_MoveType_Top:
db "βββββββ@"
String_MoveType_Bottom:
db "βTYPE/β@"
String_MoveAtk:
db "ATK/@"
String_MoveNoPower:
db "---@"
We can display a text (UI element) on the screen by adding/editting the following:
hlcoord 0, 0
ld de, String
call PlaceString
String:
db "TEXT@"
Where:
-
hlcoord 0, 0
sets the screen position in "X, Y" coordinates (1 = 1 tile long). -
ld de, String
get the text to display, in this case comes fromString
. -
call PlaceString
is a function that display your text in the set position. -
String:
sets a new string. -
db "TEXT@"
sets the string text. It must end with "@". Text commands works, like<NEXT>
to break a line.
Aside from commands, there are others special characters (charmaps) it's possible to print within a string, which will help in customizing the UI. Strings like: "βββββββ@"
can seem a bit confusing at first, but that makes possible to print a border instead of a common letter. In this case, it will create that tab on the top-left corner of the description.
See:
The available charmaps and commands within a string can be found at constants/charmap.asm.
We can display to the player the accuracy of each move on the screen, as this information is already available in the game. But first, we need to find a suitable location on the screen for this feature. For now, I'll place it below the attack power of the move.
Here is the plan:
Next step is to create the string which will print "ACC/" before the accuracy value:
PlaceMoveData:
...
; UI elements
String_MoveType_Top:
db "βββββββ@"
String_MoveType_Bottom:
db "βTYPE/β@"
String_MoveAtk:
db "ATK/@"
+String_MoveAcc:
+ db "ACC/@"
String_MoveNoPower:
db "---@"
Next, we'll need to set the coordinates to position it in the intended place. In this case it will be 1 tile bellow the "ATK" string.
PlaceMoveData:
...
; Print UI elements
hlcoord 0, 10
ld de, String_MoveType_Top
call PlaceString
hlcoord 0, 11
ld de, String_MoveType_Bottom
call PlaceString
hlcoord 12, 12
ld de, String_MoveAtk
call PlaceString
+ hlcoord 12, 13
+ ld de, String_MoveAcc
+ call PlaceString
And the code that will actually print the accuracy value, also at 1 tile below the attack power value.
PlaceMoveData:
...
+
+; Print move accuracy
+ ld a, [wCurSpecies]
+ ld bc, MOVE_LENGTH
+ ld hl, (Moves + MOVE_ACC) - MOVE_LENGTH
+ call AddNTimes
+ ld a, BANK(Moves)
+ call GetFarByte
+ Call ConvertPercentages
+ ld [wBuffer1], a
+ ld de, wBuffer1
+ lb bc, 1, 3
+ hlcoord 16, 13
+ call PrintNum
; Print move type
The following three lines in the code calls a custom function to convert the accuracy value.
; Call ConvertPercentages
; ld [wBuffer1], a
; ld de, wBuffer1
The coordinates in hlcoord 16, 13
set the position.
Printing accuracy is tricky, as it's value comes in 256 format. We need to convert it to show in percentage. For that, we'll use the help of a code from the Move Reminder (Variant 2) tutorial to convert the value.
If Move Reminder (variant 2) is already implemented, there's no need to add the conversion code again, as it can run from the other script. However, if the code was not already implemented, here is how to do it:
- Copy the code:
Conversion code from Move Reminder (variant 2) tutorial:
+; This converts values out of 256 into a value
+; out of 100. It achieves this by multiplying
+; the value by 100 and dividing it by 256.
+ConvertPercentages:
+
+ ; Overwrite the "hl" register.
+ ld l, a
+ ld h, 0
+ push af
+
+ ; Multiplies the value of the "hl" register by 3.
+ add hl, hl
+ add a, l
+ ld l, a
+ adc h
+ sub l
+ ld h, a
+
+ ; Multiplies the value of the "hl" register
+ ; by 8. The value of the "hl" register
+ ; is now 24 times its original value.
+ add hl, hl
+ add hl, hl
+ add hl, hl
+
+ ; Add the original value of the "hl" value to itself,
+ ; making it 25 times its original value.
+ pop af
+ add a, l
+ ld l, a
+ adc h
+ sbc l
+ ld h, a
+
+ ; Multiply the value of the "hl" register by
+ ; 4, making it 100 times its original value.
+ add hl, hl
+ add hl, hl
+
+ ; Set the "l" register to 0.5, otherwise the rounded
+ ; value may be lower than expected. Round the
+ ; high byte to nearest and drop the low byte.
+ ld l, 0.5
+ sla l
+ sbc a
+ and 1
+ add a, h
+ ret
- Paste it in a good place (before the UI Elements is a good spot)
+ConvertPercentages:
+...
; UI elements
String_MoveType_Top:
db "βββββββ@"
- Make space in
wram
In any case, make sure to set a space in wram
, if already not, as mentioned in the Move Reminder (variant 2) tutorial, edit ram/wram.asm.
wBattleMenuCursorPosition:: db
- ds 1
+wBuffer1:: db
wCurBattleMon::
It should already be working as intended.
Another information present in moves that we can display is the chance of performing an additional effect. For instance, EMBER has 10% chance to burn opponents. So let's shows that in the move screen. Again, we'll need to find a place for that.
Here is the plan:
NOTE: It is important to note that displaying the accuracy is not necessary for displaying the effect chance. They are independent of each other.
Create a string which will print "EFF/" on the screen.
PlaceMoveData:
...
; UI elements
String_MoveType_Top:
db "βββββββ@"
String_MoveType_Bottom:
db "βTYPE/β@"
String_MoveAtk:
db "ATK/@"
String_MoveAcc:
db "ACC/@"
+String_MoveEff:
+ db "EFF/@"
String_MoveNoPower:
db "---@"
To position at the right place, we'll need to set the coordinates. In this case it will have the same height of the "ACC" string but with 8 tiles to the left.
PlaceMoveData:
...
; Print UI elements
hlcoord 0, 10
ld de, String_MoveType_Top
call PlaceString
hlcoord 0, 11
ld de, String_MoveType_Bottom
call PlaceString
hlcoord 12, 12
ld de, String_MoveAtk
call PlaceString
hlcoord 12, 13
ld de, String_MoveAcc
call PlaceString
+ hlcoord 4, 13
+ ld de, String_MoveEff
+ call PlaceString
And the code that will actually print the effect chance value.
PlaceMoveData:
...
hlcoord 4, 13
ld de, String_MoveEff
call PlaceString
+; Print move effect chance
+ ld a, [wCurSpecies]
+ ld bc, MOVE_LENGTH
+ ld hl, (Moves + MOVE_CHANCE) - MOVE_LENGTH
+ call AddNTimes
+ ld a, BANK(Moves)
+ call GetFarByte
+ cp 1
+ jr c, .if_null_chance
+ Call ConvertPercentages
+ ld [wBuffer1], a
+ ld de, wBuffer1
+ lb bc, 1, 3
+ hlcoord 8, 13
+ call PrintNum
+ jr .skip_null_chance
+
+.if_null_chance
+ ld de, String_MoveNoPower
+ ld bc, 3
+ hlcoord 8, 13
+ call PlaceString
+
+.skip_null_chance
The same value problem that happenned with the accuracy also happens with the effect chance, they both need its value converted in order do show as percentage in the screen. The following three lines in the code is what calls the custom function to convert the effect chance value.
; Call ConvertPercentages
; ld [wBuffer1], a
; ld de, wBuffer1
Go to the accuracy part of the tutorial to implement that if you already didn't.
The coordinates in hlcoord 8, 13
set the position.
For the effect chance, we'll print String_MoveNoPower
if the move has zero chances of adding an effect in the .if_null_chance
. But if there's a chance to add an effect, it should skip that, print the value, and continue the code as we see in .skip_null_chance
.
It should already be working as intended:
NOTE: The categories of a move refered here is PHYSICAL and SPECIAL categories from the PokΓ©mon Crystal, which are already present in the game. If the new system from the Physical/Special split tutorial was implemented, just skip to the next session.
Each move can be one of the two categories based of the move's type. We can see that in the file constants/type_constants.asm.
The moves of the following types are all physical:
DEF PHYSICAL EQU const_value
const NORMAL
const FIGHTING
const FLYING
const POISON
const GROUND
const ROCK
const BIRD
const BUG
const GHOST
const STEEL
The moves of the following types are all special:
DEF SPECIAL EQU const_value
const FIRE
const WATER
const GRASS
const ELECTRIC
const PSYCHIC_TYPE
const ICE
const DRAGON
const DARK
To display that, we'll do some magic, as it doesn't have a fast way to print the category, as well as no current way to tell if a move is a status moves (there's no such category). Found a way to make that work, but I'm sure there's a simpler way to do that. For now, let's stick with how I did it.
But first, the plan:
Let's replace the text TYPE/
with the category name. To do that we must consider that PHYSYCAL have 8 characters, so we'll need to expand the tab. Also, adding a slash before the category just feels right.
Change the String_MoveType_Top
and String_MoveType_Bottom
strings to make space for printing the category. Create the strings which will print "PHYSICAL", "SPECIAL", and "STATUS" on the screen.
PlaceMoveData:
...
; UI elements
String_MoveType_Top:
- db "βββββββ@"
+ db "ββββββββββ@"
String_MoveType_Bottom:
- db "βTYPE/β@"
+ db "β β@"
String_MoveAtk:
db "ATK/@"
String_MoveAcc:
db "ACC/@"
String_MoveEff:
db "EFF/@"
String_MoveNoPower:
db "---@"
+String_MovePhy:
+ db "PHYSICAL@"
+String_MoveSpe:
+ db "SPECIAL @"
+String_MoveSta:
+ db "STATUS @"
Note that we adjusted the strings to fit the bigger one (PHYSICAL). The minor ones are filled with blank spaces just to guarantee it won't render any other character in that place.
We can now go straight to the code that will print the category.
PlaceMoveData:
...
; Print UI elements
...
call PlaceString
+
+; Print move category
+
+; Verify if it has power
+ ld a, [wCurSpecies]
+ dec a
+ ld hl, Moves + MOVE_POWER
+ ld bc, MOVE_LENGTH
+ call AddNTimes
+ ld a, BANK(Moves)
+ call GetFarByte
+ hlcoord 16, 12
+ cp 2
+ jr c, .status_move
+
+; Verifify if physical or special
+ ld a, [wCurSpecies]
+ dec a
+ ld bc, MOVE_LENGTH
+ ld hl, Moves
+ call AddNTimes
+ ld de, wStringBuffer1
+ ld a, BANK(Moves)
+ call FarCopyBytes
+ ld a, [wStringBuffer1 + MOVE_TYPE]
+ cp SPECIAL
+ jr nc, .special_category
+
+; IF PHYSICAL
+ hlcoord 1, 11
+ ld de, String_MovePhy
+ call PlaceString
+ jr .printed_category
+
+; IF SPECIAL
+.special_category
+ hlcoord 1, 11
+ ld de, String_MoveSpe
+ call PlaceString
+ jr .printed_category
+
+; IF STATUS
+.status_move
+ hlcoord 1, 11
+ ld de, String_MoveSta
+ call PlaceString
+
+.printed_category
+ hlcoord 1, 12
+ ld [hl], "/"
+ call PlaceString
The code is very simple:
- Verify if the move has Attack Power
- If YES, verify if it's SPECIAL
- IF SPECIAL, print
String_MoveSpe
- IF NOT SPECIAL, print
String_MovePhy
- IF SPECIAL, print
- IF NOT, print
String_MoveSta
At the end, it prints a slash before the name of the type.
It should be working. Note that in the images below RAZOR LEAF is listed as special because in this game all GRASS-TYPE moves are in special category. REFLECT should also be listed as special because it is PSYCHIC-TYPE, however, our code identifies that REFLECT has no damage value, so instead of printing "special" it prints "status".
The categories of a move referred here is the system from the newer games. Any move, independently of its type, can be in PHYSICAL, SPECIAL, or STATUS category. That is NOT implemented in the base game, as these categories are from future generations, but they can be implemented following this tutorial: Physical/Special split.
If it is implemented, here is how to display that on the screen.
The plan:
Let's replace the text TYPE/
with the category name. To do that we must consider that PHYSYCAL have 8 characters, so we'll need to expand the tab. Also, adding a slash before the category just feels right.
Change the String_MoveType_Top
and String_MoveType_Bottom
strings to make space for printing the category.
PlaceMoveData:
...
; UI elements
String_MoveType_Top:
- db "βββββββ@"
+ db "ββββββββββ@"
String_MoveType_Bottom:
- db "βTYPE/β@"
+ db "β β@"
String_MoveAtk:
db "ATK/@"
...
Now we can go straight to the code that will print the category.
PlaceMoveData:
...
; Print UI elements
...
call PlaceString
+
+; Print move category
+ ld a, [wCurSpecies]
+ ld b, a
+ farcall GetMoveCategoryName
+ hlcoord 1, 11
+ ld de, wStringBuffer1
+ call PlaceString
+ hlcoord 1, 12
+ ld [hl], "/"
+ inc hl
The hlcoord 1, 11
coordinates set the position for the category text.
The hlcoord 1, 12
coordinates set the position for the slash before the name of the move's type.
That's it! It should be working.
Ok... We implement all of that. But now there's too much going on...
Let's make a new plan aiming a better experience for the player.
The thoughts here are:
- Remove the side lines to make it feel less heavy
- As we'll won't move PPs, we should work around it
- The left side of the description can fit 3 small data, good positions for ATK, ACC, and EFF
- The right side of the description can fit 2 bigger ones, good positions for category and type
- We'll make changes in the tab to better fit these information
- We'll make changes to not break a line in the move description
So, let's begin!
PlaceMoveData:
...
; Print UI elements
hlcoord 0, 10
ld de, String_MoveType_Top
call PlaceString
hlcoord 0, 11
ld de, String_MoveType_Bottom
call PlaceString
- hlcoord 12, 12
+ hlcoord 1, 11
ld de, String_MoveAtk
call PlaceString
- hlcoord 12, 13
+ hlcoord 1, 12
ld de, String_MoveAcc
call PlaceString
- hlcoord 4, 13
+ hlcoord 1, 13
ld de, String_MoveEff
call PlaceString
; Print move category
ld a, [wCurSpecies]
ld b, a
farcall GetMoveCategoryName
- hlcoord 1, 11
+ hlcoord 11, 13
ld de, wStringBuffer1
call PlaceString
- hlcoord 1, 12
+ hlcoord 10, 13
ld [hl], "/"
inc hl
; Print move effect chance
...
lb bc, 1, 3
- hlcoord 8, 13
+ hlcoord 5, 13
call PrintNum
jr .skip_null_chance
.if_null_chance
ld de, String_MoveNoPower
ld bc, 3
- hlcoord 8, 13
+ hlcoord 5, 13
call PlaceString
.skip_null_chance
; Print move accuracy
...
lb bc, 1, 3
- hlcoord 16, 13
+ hlcoord 5, 12
call PrintNum
; Print move type
ld a, [wCurSpecies]
ld b, a
- hlcoord 2, 12
+ hlcoord 10, 12
predef PrintMoveType
; Print move attack power
ld a, [wCurSpecies]
dec a
ld hl, Moves + MOVE_POWER
ld bc, MOVE_LENGTH
call AddNTimes
ld a, BANK(Moves)
call GetFarByte
- hlcoord 16, 12
+ hlcoord 5, 11
cp 2
jr c, .no_power
...
; Print move description
.description
- hlcoord 1, 14
+ hlcoord 1, 15
predef PrintMoveDescription
ld a, $1
ldh [hBGMapMode], a
ret
; UI elements
String_MoveType_Top:
- db "ββββββββββ@"
+ db "βββββββββ@"
String_MoveType_Bottom:
- db "β β@"
+ db "β β@"
String_MoveAtk:
db "ATK/@"
String_MoveAcc:
db "ACC/@"
String_MoveEff:
db "EFF/@"
String_MoveNoPower:
db "---@"
All elements are now in the right position. But there are more changes to make...
Edit the following function to change the side bar's position. Let's just move them upwards to keep visible at the top of the screen.
SetUpMoveScreenBG:
call ClearBGPalettes
call ClearTilemap
call ClearSprites
xor a
ldh [hBGMapMode], a
farcall LoadStatsScreenPageTilesGFX
farcall ClearSpriteAnims2
ld a, [wCurPartyMon]
ld e, a
ld d, 0
ld hl, wPartySpecies
add hl, de
ld a, [hl]
ld [wTempIconSpecies], a
ld e, MONICON_MOVES
farcall LoadMenuMonIcon
- hlcoord 0, 1
+ hlcoord 0, -1
- ld b, 9
+ ld b, 1
ld c, 18
call Textbox
To remove the break line in the move's description we'll need to edit each move description in another file. Open data/moves/descriptions.asm.
For each move description in the file, replace next
for line
. Example:
PoundDescription:
db "Pounds with fore-"
- next "legs or tail.@"
+ line "legs or tail.@"
And the last thing... When trying to swap the move order, the screen seems to be buggy. That we can fix with few changes inside the engine/pokemon/mon_menu.asm script:
.moving_move
ld a, " "
hlcoord 1, 11
ld bc, 5
call ByteFill
+ hlcoord 1, 11
+ lb bc, 5, 7
+ call ClearBox
hlcoord 1, 12
lb bc, 5, SCREEN_WIDTH - 2
call ClearBox
- hlcoord 1, 12
+ hlcoord 2, 13
ld de, String_MoveWhere
call PlaceString
jp .joy_loop
...
String_MoveWhere:
- db "Where?@"
+ db "Select a move<NEXT>to swap places.@"
The change in "Where" text is just to add some flavor.
And for bonus point: sound effects!
It will play when switching between PokΓ©mon.
.d_right
ld a, [wSwappingMove]
and a
jp nz, .joy_loop
ld a, [wCurPartyMon]
ld b, a
push bc
call .cycle_right
pop bc
ld a, [wCurPartyMon]
cp b
jp z, .joy_loop
+ ld de, SFX_SWITCH_POCKETS
+ call PlaySFX
jp MoveScreenLoop
.d_left
ld a, [wSwappingMove]
and a
jp nz, .joy_loop
ld a, [wCurPartyMon]
ld b, a
push bc
call .cycle_left
pop bc
ld a, [wCurPartyMon]
cp b
jp z, .joy_loop
+ ld de, SFX_SWITCH_POCKETS
+ call PlaySFX
jp MoveScreenLoop
That' all!