benfrosh

Jungo · @benfrosh

17th Dec 2015 from TwitLonger

Twelve Days Of Romhacking: nATB pt 2: Nothing Is Good


Romhacking is a cruel, cruel companion. Things go wrong, unintended consequences happen, things break. When I first wrote the nATB patch (writeup here: http://www.twitlonger.com/show/n_1so1n7f), I expected things might become a little unbalanced. Boy, did I not expect what was going to happen.

1) Did you know about the command delay system? You probably notice it if you're early in FFVI and you cast a spell, it takes a while for it to actually fire even if no one is doing anything. Command delay is basically a second, shorter ATB gauge to fill up before you actually take your action. Monsters have no command delay on anything, but players have command delay on every single command. And since it behaves like another ATB gauge... it had traditionally always filled up during other character's animations, since they were so short. Lategame FFVI characters never ever really interacted with the command delay system, but now that pausing during animations was added, every character had to sit through the full delay every time. And it friggin HURT characters who were primarily spell based, who ended up having their turns take 120% of normal time compared to, say, Sabin spamming Blitzes or Gau doing Rages. And don't get me started on Cyan, who has to wait for his Bushido gauge to completely charge up!

This gets me started on another rant: there's a huge problem in games of this era and the PSX era where a lot of mechanics are added because they 'seem right' or 'feel cool', but no one ever tested or theorized to see if they ever actually contributed to the game. The ATB pausing issue is obvious to anyone who sits down and does the math on ATB turn patterns, the command delays no longer significantly mattering should've been obvious to anyone who analyzed a late-game battle. Cyan's Bushido eats your input while you're waiting for it to charge, and becomes nigh-useless as a result... it's not just FFVI, and I can talk about it for other games as well, but there's a rash of 'getting games out the door' and 'coming up with ideas' that permeates the era. It results in a lot of cool stuff and unique experiences, but is frustrating when I try to approach the games from the mechanical design angle. It makes far better experiences than games. It sounds weird coming from me, again, but I much prefer games that stick to a few core mechanics and iterate on them. The Persona games really nail this, making complex systems out of simple, obvious tools.

Anyways, this is a series about romhacking, not game design. (Though it's kind of about me designing the game the way I want it.) The solution was a two-parter. Part one was to find the numbers that 'felt right' for balance, and adjust the command delay table (located here http://datacrystal.romhacking.net/wiki/Final_Fantasy_VI:ROM_map/Assembly_C20#C2.2F067B_data_-_time_to_wait_for_commands). Finding the command delay table isn't too hard even without the ROM map, following from the code that compares your current ATB timer to things. With the ROM map it is, thankfully, trivial. There was some other number adjusting with FF3usME that I won't get into here.

The second part of the fix was to pause the display when Cyan's bushido was charging, so that Cyan's bushido was usable again and you aren't wasting the entire party's turns to use it. This was harder. I'd have to find the code that triggers Cyan's display, but not his command, and insert my pause code there... lots of ways for that to go wrong, and the animation code is generally poorly documented.

So, think backwards. What we know: in Wait mode, the ATB pauses when you're targeting an ability already. The only ones that don't are Fight and Bushido. Since those are the exceptional cases, and we know that commands share code paths for initializing the targeting code from experience, what if... we looked through and found all the points that cleared the 'is in menu' variable that pauses the ATB? If we remove them, then it'll never unpause the ATB after the main path pauses it, and if they all go to a shared part of the code after targeting is done that also unpauses the ATB, then it should just work.

TOTALLY DID fuck yeah I rock. I found exactly 2 instances of STZ $2F41 in the muddle of possible command initializations code, 2F41 being the variable that indicated 'am I in a menu' that pauses the ATB if set. STZ means Store 0 here, which would unset it and restart the ATB. Since we wanted the ATB to stay paused, I just replaced the STZ with NOPs and problem solved. Fight and Bushido kept the ATB paused during targeting/charging, and the day is saved.

Okay, now we get to deal with problem 2.
2) Running away is slooooooow. The problem is that running away used to be checked during animations, and work on only 1/8th of the ticks in combat. (It did this by doing AND #$07 with the current tick count, and only returning true if it was 0. Noodle on that one to figure out why that works.) So I changed it to work on 1/4th of the ticks. (AND #$03). This was just a loooot of digging to find the code that was called on attempting to run away, and was pretty procedural after that.

3) Regen and Poison aren't really working right anymore: they're ticking waaaay less than they used to. You guessed it- THEY USED TO TICK DURING ANIMATIONS BUT NOW THAT WAS PAUSED. It was tied to the ATB code to make sure you couldn't leave a menu open and regen your health to full, but now...

So I had two choices: reenable Regen and Poison and ONLY those to tick even during animations, or make them tick faster. I chose to make them tick faster outside of animations so there was net the same amount of ticks. There really shouldn't be anything happening during non-combat-relevant animations, as a design principle.

Okay, smartiepants. Go find out how to make Regen and Poison tick faster.

I'll wait.

Yeah, it's hell. This is something I honestly don't know if I could've figured out without the ROM map, so I'm once again grateful for whoever had the moxie over the last 15 years to map this shit out. The correct solution is to find this table:

C2/5AEA: 45 5B (Set bit 3 of $3E4C,X - check regen, seizure, phantasm)
C2/5AEC: E8 5A (RTS)
C2/5AEE: 3B 5B (Set bit 4 of $3E4C,X - check poison)
C2/5AF0: E8 5A (RTS)
C2/5AF2: 45 5B (Set bit 3 of $3E4C,X - check regen, seizure, phantasm)
C2/5AF4: E8 5A (RTS)
C2/5AF6: E8 5A (RTS)
C2/5AF8: E8 5A (RTS)

The game iterated through this table every couple of ticks, and did one in following each time. RTS means Return From Subroutine, meaning to go back to whoever called the original function that read through this table without doing anything. So Tick 1, everyone would regen, Tick 3 everyone would take poison damage, Tick 5 everyone would regen, Tick 9 everyone would regen, Tick 11 everyone would be poisoned...

The solution was to rewrite the table to look like this:
Regen/Seizure
Poison
Regen/Seizure
Nothing
Regen/Seizure
Poison
Regen/Seizure
Nothing

Collapsing it in half and taking out the every-other do-nothing command. Then I-

Wait.

Fuck me I just found a bug. Let me go fix it. NEVER TRUST ANYTHING

Reply · Report Post