Skeetendo

’Cause all games were better on the GBC

You are not logged in.

  • Index
  • → Help/Question
  • → [PokeRed] Trainer Sprites Glitching After Separating Banks

#1 2017-09-04 23:11:26

Aria Hearts
New member
Registered: 2016-12-03
Post 8/9

[PokeRed] Trainer Sprites Glitching After Separating Banks

Hello Skeetendo community,

I tried to "make" my game and found out the bytes were too big, so I recently separated my trainer sprite banks into two banks because a section was too big for the allowed amount of bytes. However, because I did that, everything after Professor Oak's sprites (in the bank) have been glitching up in odd ways.

Here's how the Banks look:

Code%202_zpsjdajwnxk.png

Code1_zpspxcjy1b5.png

Here's what it looks like:

Rival, which is after Prof Oak looks like this:

Trainer%20Glitch%201_zpste3ft9gf.png

While Bug Catcher, before Prof Oak looks okay:

Trainer%20Not%20Glitched_zpswuv2yufl.png

But for some reason the professor is completely okay in the intro screen:

Prof%20not%20Glitched_zps3zmszl06.png

Everything from Professor Oak onward is in a separate bank from the other Trainers. And since showing Oak's Sprite on the Intro calls a different routine, I'm guessing it has to do something when the battle is being called? And here's how the get trainer info looks:

Code%203%20Trainer%20Info_zpsqdwwfaog.png


I'm still kinda new at this but this is how I interpreted it, please correct me if I make any mistakes:

Calls GetTrainerName
Loads wLinkState onto the accumulator
Logically combines the operand and the accumulator together to make a new value for A
Jump(What's r?) to .linkBattle if the Z-Flag is not set to Zero
Load wTrainerClass onto the Accumulator
Decriment the accumulator's value by 1.
Loads the TrainerPicAndMoneyPointers onto hl
loads value of 5? to register bc
call AddNTimes
Loads wTrainerPicPointer onto register de
HLI? What does that mean? Loads HLI to A
Loads A onto address in DE
Incriment DE's value by one
Load HLI to A
A is loaded onto DE
wTrainerBaseMoney is loaded onto DE
HLI is loaded onto the accumulator
a is loaded onto de
incriment de
load hli onto a
jump to BankswitchBack

Local label? LinkBattle defined as:
Loads wTrainerPicPointer (I'm actually not sure what the w means)
Loads RedPicFront to de
Load e to HL
incriment HL
Load d onto HL
Return the value

I'm not too sure why we specifically use DE, or why we increment the bytes?  Or why HL is commonly used? I also notice that the values are often loaded onto HL then HL loads onto another register, I was wondering why that is? Sorry for asking so many questions, I just want to understand as much as I can. Hahah. Thank you guys so much!

I've been looking into Mateo's Red++ and trying to dissect what's going on for reference, I'm a bit lost unfortunately.

Last edited by Aria Hearts (2017-09-04 23:15:43)

Offline

#2 2017-09-05 01:00:53

Rangi
Member
Registered: 2016-05-09
Post 803/870

Re: [PokeRed] Trainer Sprites Glitching After Separating Banks

I can't help with pokered specifically, but I can help with assembly language.

Both jr and de are ultimately for the sake of saving space.

"jp .SomeLabel" is a three-byte instruction, one byte for the jp opcode and two for the .SomeLabel address. "jr .SomeLabel" only takes up two bytes, because instead of an absolute address it stores a one-byte offset between −128 and 127. So you can't jump too far away with it.

"ld [de], a" is a one-byte instruction. "ld [SomeAddress], a" is three bytes, since it takes two for the address. So if you're doing a lot of operations with the same address, it's more space-efficient to store the address in one of the combined registers hl, bc, or de and use that. (And if you're using a sequence of addresses, you just say "inc de" and keep using it, which is what GetTrainerInformation is doing.)

You know that a is the accumulator register, which makes it special. Instructions like "add" and "and" take a as one input and use it as the output. You can't do "add b, c". (This is, once again, a space-saving decision on the part of the Z80 processor designers. Here's a table of how efficiently the possible instructions are encoded as bytes.)

Anyway, hl is similarly special. For one thing, there are instructions like "add hl, bc" and "add hl, de", but no "add bc, de". Also, there are instructions that increment or decrement hl after using it. That's what "ld a, [hli]" means—it's equivalent to "ld a, [hl]" and then "inc hl".

Your line-by-line interpretation is a good start to understanding assembly code, but the next step would be to identify common patterns and abstract them out. For example:

ld a, [wLinkState]
and a
jr nz, .linkBattle

In higher-level pseudocode this would be:

if [wLinkState] ≠ 0 then goto .linkBattle

Or this:

ld a, [wTrainerClass]
dec a
ld hl, TrainerPicAndMoneyPointers
ld bc, $5
call AddNTimes

would be:

hl = TrainerPicAndMoneyPointers + 5 × ([wTrainerClass] − 1)

It's actually like array access in C (if you're familiar with C). Basically TrainerPicAndMoneyPointers is an array of 5-byte entries:

TrainerPicAndMoneyPointers:
; trainer pic pointers and base money.
; money received after battle = base money × level of highest-level enemy mon
    dw YoungsterPic
    money 1500

    dw BugCatcherPic
    money 1000

    dw LassPic
    money 1500

    ...

Each entry has two bytes for the trainer pic pointer and three for the base prize money. The entries are sorted in order of the trainer class constants. wTrainerClass holds the current trainer's class, but that starts at 1 not 0, so it has to be decremented to access the array.

Also this pattern:

ld a, [hli]
ld [de], a
inc de
ld a, [hli]
ld [de], a
inc de
ld a, [hli]
ld [de], a

is used to copy an N-byte value from hl to de. hl can be read and incremented with a single instruction, but there's no "ld [dei], a" opcode, so it has to be incremented separately. And you do that N times.

Basically the GetTrainerInformation routine is called after wTrainerClass has been set. It looks up that trainer in the TrainerPicAndMoneyPointers table and stores the info from that table in the wTrainerPicPointer and wTrainerBaseMoney locations. It also apparently gets the trainer's name, judging by the subroutine name "GetTrainerName"; and if it's a link battle, i.e. you're fighting a real person not an NPC, it just loads the pointer to Red's pic into wTrainerPicPointer and doesn't bother setting wTrainerBaseMoney.

Last edited by Rangi (2017-09-05 01:02:00)


Pokémon Polished Crystal (GitHub) — version 2.2.0 released
Pokémon Red★ and Blue★: Space World Edition (GitHub) — updated August 19!
Polished Map: pokered+pokecrystal map, tileset, and palette editor — version 3.5.1 released!

Offline

#3 2017-09-05 01:12:37

Rangi
Member
Registered: 2016-05-09
Post 804/870

Re: [PokeRed] Trainer Sprites Glitching After Separating Banks

Actually, I can help you with this after all. Notice that the TrainerPicAndMoneyPointers table stores two-byte pointers. It has each pic's address, but not its bank. battle/core.asm:_LoadTrainerPic is where the pic is loaded. (Thankfully the pokered creators have given sensible names to everything so that's obvious.) Notice that it's hard-coded to use Bank(TrainerPics) (unless it's a link battle, then it uses Bank(RedPicFront)). So none of the pictures you moved to bank 9 will work, because it's trying to load the corresponding address in bank 8, and getting garbage.

There are various possible solutions. A cheap hacky one would be to make sure that (a) the trainer pics are stored in class order, and (b) only pics past a certain class ID are in bank 9. Which lets you do a simple test: "if [wTrainerClass] >= PROF_OAK then use Bank(TrainerPics2), else use Bank(TrainerPics)". Of course you'd have to put a "TrainerPics2::" label above your new trainer pics in bank 9, and translate that bit of pseudocode into appropriate asm.

Red++ probably has a more flexible solution by now, like storing banks along with pointers in the TrainerPicAndMoneyPointers table (thus bringing each entry up to 6 bytes). This is more challenging to code; for example, you'd have to remember to change that $5 in the GetTrainerInformation routine to a $6.

Edit: In fact Red++ has two separate tables, TrainerPicAndMoneyPointers for just the money (yeah, it should be renamed...), and TrainerPicPointers for the 3-byte pic banks and addresses. You can see how the GetTrainerInformation routine has been modified to deal with this, now that the pic-loading part has been factored out into LoadTrainerPicPointer.

Last edited by Rangi (2017-09-05 01:18:33)


Pokémon Polished Crystal (GitHub) — version 2.2.0 released
Pokémon Red★ and Blue★: Space World Edition (GitHub) — updated August 19!
Polished Map: pokered+pokecrystal map, tileset, and palette editor — version 3.5.1 released!

Offline

#4 2017-09-05 11:23:37

Aria Hearts
New member
Registered: 2016-12-03
Post 9/9

Re: [PokeRed] Trainer Sprites Glitching After Separating Banks

Oh wow, thank you so much! I'm overjoyed that I got this much useful and specific information! Uh is it okay if I reiterate every step in order to understand completely? Sorry I'm self-taught with this and the only programming I actually have knowledge up (to an extent) is Python. Thank you for being so patient with me. I'm guessing the AND is there for

ld a, [wLinkState]
and a
jr nz, .linkBattle

because they needed to have a Z flag checked. So:

loads the linkstate
AND sees the two (same) inputs (because AND always compares to a) and returns the z flag to check if the result (or value of a) is a zero or not
if a is not zero, that'll make it go to linkbattle

Since higher level like Python doesn't need an accumulator (I'm guessing), this means the linkstate can be compared to something by itself, the z-flag is not needed.  And then the nz asks if it isn't equal to zero, so basically != 0 so it becomes:

if [wLinkState] ≠ 0 then goto .linkBattle

So this I kinda understand more.

As for the second one:

ld a, [wTrainerClass]
dec a
ld hl, TrainerPicAndMoneyPointers
ld bc, $5
call AddNTimes

We load in wTrainerClass to the accumulator,
We subtract (or decrement) the value by 1, because decrement will always subtract by 1.
We load TrainerandMoneyPointers onto hl
We load 5 to bc
We call the function AddNTimes

To translate it to a higher level:
Since we decrimented first, we'll order of operations it and put [wTrainerClass] - 1 parenthesis, because we want that done first.

([wTrainerClass] − 1)

then we load TrainerandMoneyPointers onto HL

HL = [TrainerandMoneyPointers]

Load 5 onto bc, and now here's where I get lost.
bc is arbitrary from the others, and I'm not quite sure what AddNTimes does.

So this is my thought process so far

hl = TrainerPicAndMoneyPointers, 5 = bc, (wTrainerClass] - 1)

I think AddNTimes tells the last operand to multiply with the accumulator then add hl to it right? Or add the result of A the operand amount of times.
So assuming TrainerPicAndMoneyPointers is already HL, it'll be


5 x ([wTrainerClass] - 1) + oldHL aka TrainerPicAndMoneyPointers value = newHL

or

([wTrainerClass] - 1) +  ([wTrainerClass] - 1) +  ([wTrainerClass] - 1) +  ([wTrainerClass] - 1) +  ([wTrainerClass] - 1) + oldHL aka TrainerPicAndMoneyPointers

meaning

hl = TrainerPicAndMoneyPointers + 5 × ([wTrainerClass] − 1)

right?

Thank you so much for the JR lesson! It means it's not stored in a definite address but rather stores one byte somewhere random between -128 and 127? And we use this whenever we want to save space, gotcha! Sorry for all these questions I'm learning so much right now.

Oh I see the specific use of HL now, since it's like a, we can't add two specific operands like de and bc in one instruction, we have to make it like

add hl, bc
add hl, de

to get something like

(hl) = bc + de

in higher level languages.

I'm guessing the $5 used for the above code is used to define how many bytes an entry on the Trainer Pic and Money Pointers should have right?
For instance this means

dw YoungsterPic
    money 1500

should be 5 bytes each because we defined bc as $5 in the function above?

Rangi wrote:

"ld a, [hli]" means—it's equivalent to "ld a, [hl]" and then "inc hl"

The "i" after hli means incriment, would that mean hld means decrement the result?

Also uh, something weird to ask but why do they decrement the values and increment them? I've been seeing this a lot, I get what it does, but I don't understand why we do it in practice if that makes sense? Like I get the definition, but in practice, I'm kinda lost on where would the appropriate time be to inc or dec. I'm guessing it's when we want to define how many bytes we should give a label like TrainerPicAndMoneyPointers?

ld a, [hli]
ld [de], a
inc de
ld a, [hli]
ld [de], a
inc de
ld a, [hli]
ld [de], a

This is my thought process on this pattern.

loads hl onto a, increments it. This means HL + 1 = a
Loads a onto de, now a = de
increments the de by one so now it's de + 1.

Then we load HL onto a again, increment it by 1, so HL + 1 = a again,
And since A is already incremented, it's basically HL + 2 = a

Now here's where I get confused.

Didn't we increment de after we put a's value onto it? So if the initial value of a would be 0, wouldn't de be 2?
We go onto the next part where we load a onto de, which I think is the same value now.

So doing it like this:

ld a, [hli]    ; HL + 1 = A (Assuming HL is 0 and A was 0, A is now 1)
ld [de], a     ; A = DE (1 = DE)
inc de     ; DE + 1 (DE + 1 = 2)
ld a, [hli]    ; HL + 1 = A (Loads the value of HL into A, increments it. Assuming HL is 0 and it loads onto A, wouldn't it mean (HL = A) + 1? Or is it HL+ oldA +1 = new A?). If former A = 1, assuming HL is 0, if latter A = 2.
ld [de], a ; loads A onto DE, which is used to be 2. If it's (HL = A) + 1, assuming HL is zero, the A would be 1, wouldn't that cancel the increment that we put on DE because DE is 2? Meaning since we loaded A onto DE, the 1 value would become the DE value right? For the former (HL = A) + 1, this version loads the hl to the a first then increment. Why do we put a lower value onto DE if we want to increment it? So I'm guessing it can be seen as HL+oldA + 1 that way A would be 2 and DE would be 2, and could be translated as 2 = 2 (A = DE)? If this is the case, it's loading the same value into itself and I'm not sure why it's done like this. Is it to keep track? 
inc de ; if former DE + 1 (DE + 1 = 2) if latter (DE + 1 = 3)
ld a, [hli]; if former A = 1, if latter A = 3
ld [de], a; load a onto de. If former DE becomes 1, if latter the DE has same value as A even before loading, assuming HL was 0. DE = A, 3 = 3.

Since we load HL first onto A, I think it can be (HL -> A) + 1. However, because there's no ld [dei], a code, I'm assuming it can be the latter, where a goes into de and gets increased by every step.
I'm so sorry, I'm very new at this. Thank you for being amazingly patient!


In Red ++, I see that TrainerPicPointer has been taken out, so I'm guessing that's why $5 became a $3. I'm guessing this is why the original Pokered code has

ld a, [hli]
    ld [de], a
    inc de
    ld a, [hli]
    ld [de], a

under the ld de, wTrainerPicPointer.

I'm going to use this new knowledge to translate both of the codes to the best of my ability.

GetTrainerInformation::
    call GetTrainerName ; Calls GetTrainerName
    ld a, [wLinkState] 
    and a
    jr nz, .linkBattle ; If the LinkState is not a zero, go to Link State
    ld a, Bank(TrainerPicAndMoneyPointers) ; Load the TrainerPicAndMoneyPointers
    call BankswitchHome (Call BankSwitchHome)
    ld a, [wTrainerClass] (Loads wTrainerClass onto A)
    dec a (Decrement it A [I don't know why we subtract it by 1.])
    ld hl, TrainerPicAndMoneyPointers
    ld bc, $5 ; We want 5 bytes for this label
    call AddNTimes ; hl = TrainerPicAndMoneyPointers, 5 = bc, (wTrainerClass] - 1)
    ld de, wTrainerPicPointer ; load wTrainerPicPointer onto DE
    ld a, [hli]
    ld [de], a
    inc de
    ld a, [hli]
    ld [de], a ; ld [dei], a, right?
    ld de, wTrainerBaseMoney ; Loads the money into DE
    ld a, [hli]
    ld [de], a
    inc de
    ld a, [hli]
    ld [de], a ; ld [dei], a
    jp BankswitchBack
.linkBattle
    ld hl, wTrainerPicPointer
    ld de, RedPicFront ; If link battle use Red
    ld [hl], e
    inc hl
    ld [hl], d ; Why separate e and d I wonder? Do we increment hl after e, then load d onto hl?
    ret

vs Red++ and how Mateo did it, the GetTrainerInformation is shortened and the "if linkBattle" routine is sent to Get_Trainer_Pic_Pointers.asm, thus new routine also has a new instruction called "add_pic" that's uses MACRO in data_macros.asm:

LoadTrainerPicPointer:: ; New function to load the pointer for the trainer's pic
    ld a, [wLinkState]
    and a
    ld a, PLAYER_M
    jr nz, .linkBattle ; If it is a link battle, skip checking wTrainerPicID and load Red's sprite
    ld a, [wTrainerPicID] ; If it's not link battle, check wTrainerPicID
.linkBattle
    dec a
    ld hl, TrainerPicPointers ; Uses TrainerPicPointers instead of TrainerPicAndMoneyPointers
    ld bc, $3 ; length of each entry, similar to that of old GetTrainerInformation label
    call AddNTimes
    ld de, wTrainerPicBank
    ld a, [hli] ; get pic bank
    ld [de], a
    inc de
    ld a, [hli] ; get first byte of pointer - Mateo wrote this, I don't know what this means.
    ld [de], a
    inc de
    ld a, [hl] ; get last byte of pointer? - Mateo wrote this, I don't know what this means.
    ld [de], a
    ret

TrainerPicPointers:
    add_pic YoungsterPic ; Uses the add_pic macro from data macros.
    add_pic BugCatcherPic
    add_pic LassPic
    add_pic SailorPic
    add_pic JrTrainerMPic
    add_pic JrTrainerFPic
    add_pic PokemaniacPic
    add_pic SuperNerdPic
    add_pic HikerPic
    add_pic BikerPic
...

I was also wondering what data macros do and what they're useful for. I think it's probably something that pastes the function or code of what's written down onto where the code calls for the macro right? It's faster but takes more bytes or something? A close friend told me that's what macros do in other languages, but it works the same way in Assembly right?


I do want to try out a safe way that Mateo has done to have the pointers reworked that's kinda flexible, so that way I don't run into too many bugs if I were to put say, more trainer classes in. However, if I use the TrainerPics2 method, and if I hypothetically were to make sprites that were bigger than 2 banks, would I also the TrainerPics 3 label along with it? Would that cause problems along the way? If so, how would I prevent it? Aw man, I hope I'm not asking too much. I really appreciate your help!

Here's the best of my ability for the conversion, please correct me if I'm wrong. So I have added a label "TrainerPics2" onto Main.asm, the TrainerPicsMoneyPointers also match to the order in the two TrainerPics section in Home.asm. Now I'm in core and here's what it looks like before the edit:

_LoadTrainerPic:
; wd033-wd034 contain pointer to pic
    ld a, [wTrainerPicPointer] ; Load wTrainerPicPointer in
    ld e, a ; Put the value in e
    ld a, [wTrainerPicPointer + 1] ; Plus one onto the Pic Pointer?, load it into the accumulator
    ld d, a ; de contains pointer to trainer pic, - written by the author of Pokered, I'm guessing d is the first byte that's why there's a plus 1?
    ld a, [wLinkState] ; Puts wLinkState onto a
    and a ; and looks at both the a values, return zero
    ld a, Bank(TrainerPics) ; this is where all the trainer pics are (not counting Red's)
    jr z, .loadSprite ; If it's a zero, not in link state, we will load the sprite.
    ld a, Bank(RedPicFront) ; Otherwise use RedPicFront
.loadSprite
    call UncompressSpriteFromDE
    ld de, vFrontPic
    ld a, $77
    ld c, a
    jp LoadUncompressedSpriteData

I just added a Bank(TrainerPics2) after the first one to check if adding TrainerPic2 would do the trick:

_LoadTrainerPic:
; wd033-wd034 contain pointer to pic
    ld a, [wTrainerPicPointer] ; Load wTrainerPicPointer in
    ld e, a ; Put the value in e
    ld a, [wTrainerPicPointer + 1] ; Plus one onto the Pic Pointer?, load it into the accumulator
    ld d, a ; de contains pointer to trainer pic, - written by the author of Pokered, I'm guessing d is the first byte that's why there's a plus 1?
    ld a, [wLinkState] ; Puts wLinkState onto a
    and a ; and looks at both the a values, return zero
    ld a, Bank(TrainerPics) ; this is where all the trainer pics are (not counting Red's)
    ld a, Bank (TrainerPics2) ; new
    jr z, .loadSprite ; If it's a zero, not in link state, we will load the sprite.
    ld a, Bank(RedPicFront) ; Otherwise use RedPicFront

.loadSprite
    call UncompressSpriteFromDE
    ld de, vFrontPic
    ld a, $77
    ld c, a
    jp LoadUncompressedSpriteData

It did! However, doing this made this happen:

Trainer%20Not%20Glitched%201_zps9nio61hd.png

Trainer%20Now%20Glitched_zpskbyjl7hd.png

(Ignore the Graveler, I was testing if the item evolution and trade evolution combo would work~)

Now TrainerPics2 is working fine and TrainerPics1 is not, I'm guessing this has to do with the accumulator having two values at once. Now I'm having a little bit of trouble trying to write an if statement with a zero since checking if it's a link battle or not is using the z register already. How would I successfully convert it to an if statement in Assembly? What I want to do is:

Load the pic pointer
Check if it's a link state
If it's not a link state, go to ClassSeperationUpload

SpriteSeperation
0x1 to 0x17 Lower than Oak
0x18 + Oak or Greater

ClassSeperationUpload
Using SpriteSeperation, If sprite class is lower than Prof Oak, use TrainerPic
LoadSprite
Using SpriteSeperation, If sprite class is equal or greater than Oak, use TrainerPic2
Load Sprite

But I'm not really sure where to start to approach this. I really appreciate your tutelage Rangi! Seriously, thank you so much. This is really helping me understand more about ASM.

Last edited by Aria Hearts (2017-09-05 11:25:55)

Offline

#5 2017-09-05 16:30:22

Rangi
Member
Registered: 2016-05-09
Post 805/870

Re: [PokeRed] Trainer Sprites Glitching After Separating Banks

Aria Hearts wrote:

I'm guessing the AND is there because they needed to have a Z flag checked.

Exactly!

Load 5 onto bc, and now here's where I get lost.
bc is arbitrary from the others, and I'm not quite sure what AddNTimes does.

Then look at the definition of AddNTimes in home.asm.

AddNTimes::
; add bc to hl a times
    and a
    ret z
.loop
    add hl,bc
    dec a
    jr nz,.loop
    ret

(Which also explains why they used bc instead of de. Although sometimes the choice is arbitrary; but there are only three of those paired two-byte registers anyway, hl, bc, and de.)

So assuming TrainerPicAndMoneyPointers is already HL, it'll be

5 x ([wTrainerClass] - 1) + oldHL aka TrainerPicAndMoneyPointers value = newHL

or

([wTrainerClass] - 1) +  ([wTrainerClass] - 1) +  ([wTrainerClass] - 1) +  ([wTrainerClass] - 1) +  ([wTrainerClass] - 1) + oldHL aka TrainerPicAndMoneyPointers

meaning

hl = TrainerPicAndMoneyPointers + 5 × ([wTrainerClass] − 1)

right?

Your last line is correct, and matches what I gave as the pseudocode equivalent. But you sound confused here:

5 x ([wTrainerClass] - 1) + oldHL aka TrainerPicAndMoneyPointers value = newHL

The value of TrainerPicAndMoneyPointers is not being modified. hl is being modified. When you see

ld hl, TrainerPicAndMoneyPointers

that's setting hl to the address labeled TrainerPicAndMoneyPointers. Then since the data after that address is in chunks of 5 bytes, and we want the chunk of the ([wTrainerClass] − 1)th trainer, we add 5 to hl ([wTrainerClass] − 1) times.

It might help to rephrase things, Don't say "we load in wTrainerClass to the accumulator," say "we set a to the value at wTrainerClass". Or instead of "we load TrainerandMoneyPointers onto hl", say "we set hl to TrainerandMoneyPointers". (Note the difference between loading the value at an address into a one-byte register, and loading the address itself into a two-byte register.) Personally when reading code to myself I prefer shorter names like "a" instead of "accumulator", since we have finite working memory, and phrasing like "set this register to that value" echoes how modern programming languages work.

Thank you so much for the JR lesson! It means it's not stored in a definite address but rather stores one byte somewhere random between -128 and 127?

Well, it's not random, it's a precise value. Say that the jr instruction itself is at address 1230 and you want to jump to SomeLabel which happens to be at address 1240. Then 1240 − 1230 = 10, so it would be encoded as $18 $0A: $18 is the opcode for "jr N", and $0A is hexadecimal 10.

I'm guessing the $5 used for the above code is used to define how many bytes an entry on the Trainer Pic and Money Pointers should have right?

For instance this means

dw YoungsterPic
    money 1500

should be 5 bytes each because we defined bc as $5 in the function above?

Correct. But careful with that "because". The $5 in this one routine has no effect on the "dw YoungsterPic, money 1500" declaration. Rather, they chose to use $5 there because "dw YoungsterPic, money 1500" was already 5 bytes. "dw" declares a two-byte word, and "money" is a macro that ends up declaring three bytes to encode the money value in an odd way.

(Aside: that "odd way" is binary-coded decimal. Three bytes means six hexadecimal digits, and in-game money can be up to ¥999,999, i.e. six decimal digits. They decided to store it digit-by-digit, so if you have ¥123,000, it's stored as the three bytes $12 $30 $00—that's in hex, in decimal it's 18 48 0.)

The "i" after hli means incriment, would that mean hld means decrement the result?

Right.

Also uh, something weird to ask but why do they decrement the values and increment them? I've been seeing this a lot, I get what it does, but I don't understand why we do it in practice if that makes sense? Like I get the definition, but in practice, I'm kinda lost on where would the appropriate time be to inc or dec. I'm guessing it's when we want to define how many bytes we should give a label like TrainerPicAndMoneyPointers?

I'm not sure what you're talking about here. They don't decrement hl or de in GetTrainerInformation. In general, they might inc and then dec if they need to get the value at the next address and then keep using the previous address.

Remember this is not like Python, where you set variables equal to values all the time and let the interpreter handle the memory for you. Here you have a fixed amount of memory and a handful of registers for dealing with it. Imagine writing a Python program where you have these variables available:

memory = [0] * 0x8000
a = f = 0
bc = de = hl = 0

and that's it. And you can only access memory in certain ways, like "a = memory[h*16+l]". (Oh, and for convenience you can define "labels" like "TrainerPicAndMoneyPointers = 0x5914" just so you can say "a = memory[wTrainerClass]" or "hl = TrainerPicAndMoneyPointers" instead of "a = memory[0xD031]" or "hl = memory[0x5914]".) And you also have a limited file size for the program itself.

Then you can probably see why they would do tricks like inc and then dec. This:

ld hl, SomeAddress
...
ld a, [hl]
; do stuff with a...
inc hl
ld a, [hl]
; do more stuff with a
dec hl
; keep going
...

Is shorter than this:

ld hl, SomeAddress
...
ld a, [hl]
; do stuff with a...
ld hl, SomeAddress + 1
ld a, [hl]
; do more stuff with a
ld hl, SomeAddress
; keep going
...

C would be a good language to learn if you want some convenience of a higher-level language with the low-level memory model of assembly.

ld a, [hli]
ld [de], a
inc de
ld a, [hli]
ld [de], a
inc de
ld a, [hli]
ld [de], a

This is my thought process on this pattern.

loads hl onto a, increments it. This means HL + 1 = a

No. "ld a, [hli]" is equivalent to "ld a, [hl]; inc hl", which in pseudocode is "a = [hl]; hl = hl + 1". Don't confuse the order of operands in ld. The first one is the one being modified.

In Red ++, I see that TrainerPicPointer has been taken out, so I'm guessing that's why $5 became a $3.

Right.

I'm going to use this new knowledge to translate both of the codes to the best of my ability.

Sounds good. You may want to redo it based on some corrections here. A few comments on you understanding of particular lines:

call GetTrainerName ; Calls GetTrainerName

Does a comment like this really help you understand it better? I mean, fine if it does, but focusing on individual lines like this might only make things more confusing. Try to understand groups of lines together, since a simple one-line operation in a language like Python probably ends up as a common sequence of four or five lines in assembly.

ld a, [wLinkState]
and a
jr nz, .linkBattle ; If the LinkState is not a zero, go to Link State

This is more the kind of comment I'd recommend. But be careful with names. "If wLinkState is not zero, go to .linkBattle".

ld a, [wTrainerClass] (Loads wTrainerClass onto A)
dec a (Decrement it A [I don't know why we subtract it by 1.])

As I said before, "wTrainerClass holds the current trainer's class, but that starts at 1 not 0". The first entry in TrainerPicAndMoneyPointers is for Youngster, but YOUNGSTER == 1, not 0. Just like in Python, "arrays" start at 0. Because if hl = TrainerPicAndMoneyPointers and you want the first entry in that array, you don't need to add anything to hl.

ld de, wTrainerPicPointer ; load wTrainerPicPointer onto DE
ld a, [hli]
ld [de], a
inc de
ld a, [hli]
ld [de], a ; ld [dei], a, right?
ld de, wTrainerBaseMoney ; Loads the money into DE

First, there is no "[dei]" shorthand. Second, as you can see, that "ld [de], a" is not followed by "inc de", because it's not necessary. First that code sets de to wTrainerPicPointer, and copies the next two bytes from hl to de. But then it sets de to wTrainerBaseMoney and copies the next to bytes from hl to this new de.

ld [hl], e
inc hl
ld [hl], d ; Why separate e and d I wonder? Do we increment hl after e, then load d onto hl?

Yes, as you can see by the "inc hl" instruction.

This is a concept you would also need in C: pointers/addresses versus literal values. "hl" is a two-byte combined register that is often used to store an address, i.e. hl points to some location in memory. "[hl]" dereferences the pointer and reads the one-byte value at that address. In the code above, likely de is a pointer, and they want to store that poiner as a literal value at hl. The limited instruction set means they have to copy it one byte at a time.

I was also wondering what data macros do and what they're useful for. I think it's probably something that pastes the function or code of what's written down onto where the code calls for the macro right? It's faster but takes more bytes or something? A close friend told me that's what macros do in other languages, but it works the same way in Assembly right?

Macros in assembly are like #define macros in C. (Serious suggestion: learn C. It will teach you a lot of the same concepts as Z80 assembly, but it's a lot more practically useful.) Look at the add_pic definition in Red++'s macros/data_macros.asm:

add_pic: MACRO
    db BANK(\1)
    dw \1
ENDM

"\1" stands for the first argument given to each use of the macro. So "add_pic YoungsterPic" turns into:

db BANK(YoungsterPic)
dw YoungsterPic

Macros are purely for typing convenience. If you replaced every instance of the add_pic macro with that equivalent code, the ROM would be exactly the same. You'd just be doing the macro preprocessor's job for it. Macros let you write a common pattern once and then keep reusing it. Compare that with functions: factoring repeated code into a function does save space, since you replace multiple identical blocks of code with a single "call MyFunction" operation.

However, if I use the TrainerPics2 method, and if I hypothetically were to make sprites that were bigger than 2 banks, would I also the TrainerPics 3 label along with it? Would that cause problems along the way?

Right. You just have to make sure that every trainer class's pic is accessed using the correct bank. For two or three banks, or even four, it's probably easiest to have the pics in order of trainer ID and get the right bank that way. Beyond that, you'll want to copy Mateo's method, so if you need help ask him.

I just added a Bank(TrainerPics2) after the first one to check if adding TrainerPic2 would do the trick:

This is incorrect. You put "ld a, Bank(TrainerPics2)" right after "ld a, Bank(TrainerPics)" so it's just overwriting the old value. Your new code is going to try and load ever picture from Bank(TrainerPics2) instead of Bank(TrainerPics). So now the pics in TrainerPics2 will work but the ones in TrainerPics won't. It's just the opposite problem.

I'm a bit confused as to how you thought it would work. Like, how do you imagine the code deciding which value to use?

Now TrainerPics2 is working fine and TrainerPics1 is not, I'm guessing this has to do with the accumulator having two values at once.

It can't have two values at once. If you do this in Python:

a = 1
a = 2

you've just overwritten 1 with 2 in a, not given it a new dual value.

Now I'm having a little bit of trouble trying to write an if statement with a zero since checking if it's a link battle or not is using the z register already.

It's funny, I'll occasionally see an error in your code and explain it, and then it turns out you already noticed and know what needs to be done next.

Basically there are three cases you need to handle:

• If [wLinkState] is nonzero, use Bank(RedPicFront).
• Else if [wTrainerClass] is less than PROF_OAK, use Bank(TrainerPics).
• Else use Bank(TrainerPics2).

What's convenient here is that if [wLinkState] is nonzero, it doesn't matter what [wTrainerClass]. You don't have to have nested conditionals.

Here's a less efficient but more obvious way to write it:

_LoadTrainerPic:
    ; de = [wTrainerPicPointer]
    ld a, [wTrainerPicPointer]
    ld e, a
    ld a, [wTrainerPicPointer + 1]
    ld d, a

    ; if [wLinkState] is nonzero, go to .linkbattle
    ld a, [wLinkState]
    and a
    jr nz, .linkbattle
    ; else if [wTrainerClass] is less than PROF_OAK, go to .firstbank
    ld a, [wTrainerClass]
    cp PROF_OAK
    jr c, .firstbank
    ; else use Bank(TrainerPics2)
    ld a, Bank(TrainerPics2)
    jr .loadSprite

.linkbattle
    ; use Bank(RedPicFront)
    ld a, Bank(RedPicFront)
    jr .loadSprite

.firstbank
    ; use Bank(TrainerPics)
    ld a, Bank(TrainerPics)
    ;jr .loadSprite ; not necessary, since it's right after this

.loadSprite
    call UncompressSpriteFromDE
    ld de, vFrontPic
    ld a, $77
    ld c, a
    jp LoadUncompressedSpriteData

This could be optimized like the original code, but at the expense of clarity. For example, this:

; if [wLinkState] is nonzero, go to .linkbattle
    ld a, [wLinkState]
    and a
    jr nz, .linkbattle

...

.linkbattle
    ; use Bank(RedPicFront)
    ld a, Bank(RedPicFront)
    jr .loadSprite

could just be this:

; if [wLinkState] is nonzero, go to .linkbattle
    ld a, [wLinkState]
    and a
    ld a, Bank(RedPicFront)
    jr nz, .loadSprite

but that mixes up checking the condition with doing the result, and relies on "ld a, Bank(RedPicFront)" not messing up the nz flag set by "and a".

Last edited by Rangi (2017-09-05 16:37:15)


Pokémon Polished Crystal (GitHub) — version 2.2.0 released
Pokémon Red★ and Blue★: Space World Edition (GitHub) — updated August 19!
Polished Map: pokered+pokecrystal map, tileset, and palette editor — version 3.5.1 released!

Offline

#6 2017-09-07 04:14:10

Danny-E 33
Administrator
Registered: 2012-06-09
Post 1,031/1,119

Re: [PokeRed] Trainer Sprites Glitching After Separating Banks

Aria Hearts wrote:

Oh I see the specific use of HL now, since it's like a, we can't add two specific operands like de and bc in one instruction, we have to make it like

add hl, bc
add hl, de

to get something like

(hl) = bc + de

in higher level languages.

Be careful. Those two assembly instructions would give you:

hl = hl + bc + de

Not:

hl = bc + de

Also keep in mind that the notation "[hl]" or "(hl)" refers to the 8 bit value at the memory address pointed to by the 16 bit value in hl.
Instructions like "add hl, bc" add the 16 bit value in bc to the 16 bit value in hl and stores the result in the 16 bit register pair hl.
It is incorrect to use notation like "(hl) = bc + de" because "bc + de" is 16 bits, while "(hl)" is 8 bits.



Aria Hearts wrote:

The "i" after hli means incriment, would that mean hld means decrement the result?

It does not decrement the result. It decrements the pointer hl.
When hl is being used like this, it is a pointer to a ram address. If hl contains the value $4321 and you perform:

ld a, [hld]

Then the byte at memory address $4321 gets loaded into a and that memory address doesn't change value. Then the value in hl gets decremented to become $4320.
The reason this is done is so that you can quickly do 'ld a, [hl]' again to get the next sequential byte in memory without having to waste space with extra instructions for incrementing or decrementing the hl pointer.
Here is a practical example. Let's say hypothetically, you want to fill 4 bytes of memory with the value $FF. You could do this:

ld a, $ff
ld hl, wStartOfMemoryToBeFilled
ld [hl], a
inc hl
ld [hl], a
inc hl
ld [hl], a
inc hl
ld [hl], a

But instead you can do:

ld a, $ff
ld hl, wStartOfMemoryToBeFilled
ld [hli], a
ld [hli], a
ld [hli], a
ld [hl], a

The end results of both of these pieces of code are identical, but the second assembly routine is 3 bytes shorter.

Aria Hearts wrote:

I was also wondering what data macros do and what they're useful for. I think it's probably something that pastes the function or code of what's written down onto where the code calls for the macro right? It's faster but takes more bytes or something? A close friend told me that's what macros do in other languages, but it works the same way in Assembly right?

Macros don't take up any more or less space than using strictly plain assembly instructions. Creating and using a macro simply gives you a quick and convenient way to do the same boring work over and over in your source code.
For example, consider the RGB macro. Here is its definition:

RGB: MACRO
    dw (\3 << 10 | \2 << 5 | \1)
ENDM

It takes three arguments (\1, \2, and \3, the rgb values) and dumps a 16-bit value based on the three arguments you give it.
This macro is used in places like data/super_palettes.asm:

SuperPalettes:
    RGB 31,29,31 ; PAL_ROUTE
    RGB 21,28,11
    RGB 20,26,31
    RGB 3,2,2

By creating a macro, we can make sure we get the correct data put into the rom at the end of the day, but the source code we are dealing with looks much nicer. If we never created that macro, we would have to change the super palettes file to look like:

SuperPalettes:
    dw (31 << 10 | 29 << 5 | 31) ; PAL_ROUTE
    dw (11 << 10 | 28 << 5 | 21)
    dw (31 << 10 | 26 << 5 | 20)
    dw (2 << 10 | 2 << 5 | 3)

You can see that both with and without the macro, the output rom is going to be identical, but it's much more handy to think of macros we can come up with that make dealing with the source code a little bit nicer.

Last edited by Danny-E 33 (2017-09-12 17:07:18)

Offline

  • Index
  • → Help/Question
  • → [PokeRed] Trainer Sprites Glitching After Separating Banks

Board footer

Powered by FluxBB