3
« on: January 23, 2009, 03:48:27 PM »
Hello all,
please let me make a quick introduction of myself, as is customary when making a first post anywhere. My name is Fredrik, I'm 26, from Sweden, and like all of you I'm hopelessly addicted to FFVI. :P
Since a few years back I started toying with the idea of modifying the game, randomly changing this and that with whatever editors I could find, in a scrambled attempt to understand how the game works. Recently I've been looking more and more into the inner workings of the game, thanks to the awesome commented disassemblies by Terii and assassin. At first it seemed intimidating, but with some tips gathered from lurking on these and other romhacking boards, I finally grabbed WindHex32 and tried some minor changes, like NOPing a few bytes here, or changing a value there.
So far I've managed to disable the inherent +20 speed in battle (thanks to Lenophis and Dragonsbrethren in a post somewhere describing exactly what to do). This was my first experience with hands on hex editing of the ROM's code.
Having cut my teeth on that one, I rummaged through the C2 disassembly and changed some bytes to make Haste double the rate at which the ATB bar fills, and make Safe/Shell halve damage (instead of cutting it by 1/3). Also successfully managed to prevent Dance giving you Dance status, as well as making the Rage command transform into Magitek while riding Magitek armor for every character (for a silly all-Gau proof-of-concept hack).
So far simple stuff. But last night I got the idea to make attacks that check for Stamina directly involve the hit rate of the spell as well. After a sleepless night I thought I had managed to make enough sense of the code to be able to copy-paste some code together in a manner which I had hoped would produce the wanted results.
I'll explain what I tried to do, but first let me paste the relevant code and section of Terii's algorithms doc, for reference.
Step 5. Check to hit for attacks that can be blocked by Stamina
Most attacks use step 4 instead of this step. Only Break, Doom, Demi,
Quartr, X-Zone, W Wind, Shoat, Odin, Raiden, Antlion, Snare, X-Fer, and
Grav Bomb use this step.
Step 5a. Chance to hit
1. BlockValue = (255 - MBlock * 2) + 1
2. If BlockValue > 255 then BlockValue = 255
If BlockValue < 1 then BlockValue = 1
3. If ((Hit Rate * BlockValue) / 256) > [0..99] then you hit, otherwise
you miss.
Step 5b. Check if Stamina blocks
If target's stamina >= [0..127] then the attack misses (even if it hit in
step 5a); otherwise, the attack hits as long as it hit in step 5a.
And the relevant code:
Check if hit if Stamina involved
C2/239C: B9 55 3B LDA $3B55,Y (MBlock)
C2/239F: EB XBA
C2/23A0: AD A8 11 LDA $11A8 (Hit Rate)
C2/23A3: 20 81 47 JSR $4781 (Multiplication Function)
C2/23A6: EB XBA
C2/23A7: 85 EE STA $EE (High byte of Mblock * Hit Rate)
C2/23A9: A9 64 LDA #$64
C2/23AB: 20 65 4B JSR $4B65 (Random Number 0 to 99)
C2/23AE: C5 EE CMP $EE
C2/23B0: B0 0C BCS $23BE (Attack misses, so exit)
C2/23B2: 20 5A 4B JSR $4B5A (Random Number 0 to 255)
C2/23B5: 29 7F AND #$7F (0 to 127)
C2/23B7: 85 EE STA $EE
C2/23B9: B9 40 3B LDA $3B40,Y (Stamina)
C2/23BC: C5 EE CMP $EE
C2/23BE: 60 RTS
Right. So as far as my understanding goes, what this code does is:
(If attack checks for Stamina, jump here)
1. Get BlockValue for Mblock, previously determined to be (255 - Mblock * 2) + 1 in some other section of the code
2. Store it in the top byte of a two-byte something something? I don't really understand most of the comments. :P
3. Get Hit Rate
4. Multiply high byte by low byte (ie. BlockValue * Hit Rate)
5. Take the top byte again (effectively dividing by 256?)
6. Store as $EE?
7. Get 100
8. Get random number [0..(100-1)], ie. [0..99]
9. See if it's higher than $EE?
10. If it is, attack misses, so skip C bytes ahead, to exit
11. If it isn't, continue and get a random number [0..255]
12. modify it somehow to make it [0..127]? I don't know what this does.
13. Store it as $EE
14. Get Stamina
15. Is Stamina higher than $EE?
16. don't know what RTS is, but I'm guessing it's something like, "ok, got our value, now let's go back to where we were and continue on".
Short version:
1. Check for Mblock. Does attack miss?
2. If not, check for Stamina.
Okay, so what I tried to do was to modify the Stamina check to take the hit rate of the spell into account while also checking for Stamina and Mblock.
Basically this:
1. StaminaValue = (255 - Stamina * 2) + 1
2. BlockValue = (255 - MBlock * 2) + 1
3. BlockValue = StaminaValue * BlockValue / 256
If BlockValue > 255 then BlockValue = 255
If BlockValue < 1 then BlockValue = 1
4. If ((Hit Rate * BlockValue) / 256) > [0..99] then you hit, otherwise
you miss.
And here is the block of code that I wrote/copypasted together:
C2/239C: B9 40 3B LDA $3B40,Y (Stamina)
C2/239F: 20 61 28 JSR $2861 (255 - Stamina * 2) + 1 , capped at low of 1 and high of 255)
C2/23A2: EB XBA
C2/23A3: B9 55 3B LDA $3B55,Y (MBlock)
C2/23A6: 20 81 47 JSR $4781 (Multiplication Function)
C2/23A9: EB XBA
C2/23AA: AD A8 11 LDA $11A8 (Hit Rate)
C2/23AD: 20 81 47 JSR $4781 (Multiplication Function)
C2/23B0: EB XBA
C2/23B1: 85 EE STA $EE (High byte of Mblock * Hit Rate)
C2/23B3: A9 64 LDA #$64
C2/23B5: 20 65 4B JSR $4B65 (Random Number 0 to 99)
C2/23B8: C5 EE CMP $EE
C2/23BA: 60 RTS
C2/23BB: EA NOP
C2/23BC: EA NOP
C2/23BD: EA NOP
C2/23BE: EA NOP
In my understanding, what this code does is:
1. Get Stamina
2. Get StaminaValue = (255 - Stamina * 2) + 1, capped at low of 1 and high of 255
3. XBA. Stash value in high byte of whatwhat?
4. Get MblockValue
5. Multiply high byte with low byte (ie. StaminaValue * Mblock), a 16-bit value
6. XBA again! Take the high byte of previous result, effectively dividing by 256?
(I know this doesn't work as intended, capping at low of 1 and high of 255. I don't know how to do it.)
7. Get Hit Rate
8. Multiply again
9. Again divide by 256
10. Store as $EE
11. Get 100
12. Random number between 0 to 99
13. Is it higher than $EE?
14. Exit, go do something else
15-19. Nothing! ;)
Tested against enemy at 40 Stamina and a spell with a Hit Rate of 147. According to my calculations, this should hit 100 % of the time. Didn't seem to be the case. Hm.
Tested against enemy at 1 Stamina, spell never hit. Wait what?
Tested against Terra at 128 Stamina, spell ALWAYS hit. At 74 hit rate (one half) it sometimes missed.
Tested against Vicks at 1 Stamina, both spells never hit.
Now, it doesn't crash the game or anything horrible like that, so I'm glad, but it seems to work backwards. I haven't done much testing, but it seems that this code makes it so that LOWER Stamina makes it harder to get hit, so obviously there's some parts of the code that I don't understand. That, or I made a design error, I was rather tired last night.
Sorry for the long post, but if you're still here reading this, I'd very much appreciate some help, comments or insights from more experienced hackers.
Specifically,
0. Is my code at all functional? ;)
1. WHY is it working backwards?
2. Also, if you spot any misunderstandings I've made in trying to understand HOW this works, please comment.
3. Oh, and also, if Stamina > 128, what happens when (StaminaValue * BlockValue) < 1?
Please bear in mind that I have absolutely NO assembly knowledge, apart from what I managed to grasp while looking through the disassembly last night and trying to make some sense of it. :)
Attached are my test calculations. If you spot any mistakes please comment.
Sincerely,
Fredrik