Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Rosalie_A
Oct 30, 2011
I can probably guess the answer to this, but would anyone want me to write up some stuff about my adventures developing my own randomizers?

Adbot
ADBOT LOVES YOU

Rosalie_A
Oct 30, 2011
Making a randomizer, part 1

Background

My randomizer development experience is primarily with the Archipelago multiworld project (and we can talk about multiworlds another time). At the moment I have three randomizers for it under my belt in various stages of completion. Two of those are integrating existing randomizers: Final Fantasy 6 Worlds Collide, and Super Mario RPG. The third, however, had to be built from the ground up, and is for this obscure little game known as The Legend of Zelda for the NES.

Why make a randomizer?

Why do anything? It's fun.

How make a randomizer?

First off, lots of research. You need to understand the game you're working with at a fairly deep level. Not just in a technical sense, but also from a design perspective. Not all game randomizers are made the same, and it's pretty easy to see if you've dived into a few of them. Some games lend themselves more or less to randomization, and bringing out the full potential of your randomized game requires an understanding of what makes that game enjoyable in the first place.

That's not to say the technical element isn't at play. For Zelda, I decided to go with a straightforward item randomizer. No mucking about with entrance randomization or enemy layouts or whatever. Just find items in different spots. Even this "simple" approach requires figuring out answers to lots of complicated questions. What happens when you get certain items out of order? Are there parts that assume a particular loadout and break if that's not the case? Are there certain locations that assume only particular kinds of items will be present?

Well, for those first two questions, the answer was easy. Zelda 1 is already an incredibly open game, with you generally being able to get anything at any time. Have the Magical Sword already but find a lower level sword? That's alright, the game already handles that. Finish using a potion but there's no item to switch to? Already accounted for. Zelda 1 is so open they included a second layout of items and locations and needed to make zero adjustments to the engine. (There's a reason the game is so well regarded!)

The third question I posed is a bit trickier. This is going to require diving into some technical bits, pun intended. See, every room in Zelda has two bytes associated with it known as room flags. This controls all sorts of stuff: enemies present, secrets present, whether the room is dark, and so forth. You know that roaring that occurs when you're next to a boss room? That's part of those flags. Specifically, two bits, leading to three types of roars on top of the default silence. Five bits, however, are reserved for if there's an item in the room. This means there can be up to thirty two different items in a room: thirty one and a "Nothing", in fact.

Three swords, two rings, two arrows, two potions and a letter, two boomerangs, a wand, a book, a ladder, a raft, a bracelet, a recorder...that's eighteen items. So far so good, right? Except bombs are a thing, and so is the magical key. Rupees, clocks, fairies, and keys are all items too. Oh, and heart containers. Shame if I were to forget the bow and candles. Maps and compasses are things as well. Combine that with a nothing and we get thirty two! Perfect!

Oh wait, the Triforce is a thing too. Combined with the fact that some of those thirty-two item slots are actually dummied out slots, but due to how the game's structured you can't just go and move other items into those slots...yeah. So, in short, a conundrum: how can I have every item be available everywhere if there's not enough room in the data for dungeons (you know, the majority of the item locations in the game!) to have all the items.

This is where I got a bit lucky. Zelda 1 is one of the rare games with a full disassembly, complete with comments. And I mean full: take the disassembly and an original rom to supply the graphics and such, and you can compile a usable rom. With no changes, it's byte for byte the original, but you could make changes to the code. Assembly code is, compared to modern friendly languages, very tough to parse. You're dealing with individual bits and bytes at a time. However, it's a world above that of trying to parse the machine code directly.

This meant I could directly edit the code for parsing room flags. Remember those boss roars? I stole a bit from them. In my randomizer, there's now only one type of boss roar alongside the normal silence. Everything that used the second bit now uses the first. In exchange, I get an extra bit for items. This doubles the number of slots I have to work with, from thirty-two to sixty-four -- easily enough to represent any item in Zelda 1.

Perfect, right? Well, we've solved the problem of not being able to fit all the items into dungeons, but what about the inciting question ("Are there certain locations that assume only particular kinds of items will be present?") from the other side? Are there items that assume they'll be in dungeons that break if not?

As it happens, there are. Maps and compasses use the ID of the dungeon they're found in to set their UI elements. Outside of the nine dungeons, they do nothing. Well, that's okay. That's maps and compasses. I just made them picked up from the start and used their locations for more spots to find items. Problem solved, and I can call it a quality of life feature to boot.

Oh wait, the Triforce is a thing too. Again. As it happens, this also uses the ID of the current dungeon to set whether it's been obtained or not. There's a separate bit for each of the eight fragments, corresponding to the first eight levels. This controls not just what pieces appear on the menu, but also representing what dungeons you've cleared for the purposes of the Recorder. In other words, if I'm on the overworld -- which is dungeon ID 0 -- then collecting a Triforce piece will do nothing. Additionally, it also limits me to one Triforce per dungeon. I can move it around that dungeon, but you'll always know there'll be one in every dungeon. This is why every Zelda 1 randomizer leaves Triforces in their vanilla positions, because there's no easy way around this limitation.

Next time: I don't let that stop me.

Rosalie_A
Oct 30, 2011

Geemer posted:

If you want to play randomizers with friends, consider multiworld randomizers like Archipelago.

funny you should say that

Rosalie_A
Oct 30, 2011
Making a randomizer, part 2

So, that was that. Triforce pieces were limited to one per dungeon. Picking one up outside a dungeon did nothing, and if you didn't have one for each level, the game was unbeatable.

I didn't want this. Triforce pieces are an item like any other, and I wanted to randomize them if I could. But how to get around the game's coding on this?

As mentioned, I was working with a full decompilation of Zelda 1. I could make changes in assembly and recompile the game. So, that's what I did.

But before that, we have to understand another problem this was intersecting with. See, I was making this randomizer for the Archipelago multiworld project. This meant that my game would be shuffling items not just from itself but also from other games, and conversely items from Zelda 1 could end up in other games. In other words, I needed to take into account receiving an item remotely. A remote item is any that isn't located in your particular instance of a game -- your world. So even if a multiworld game were just two Zelda 1 games, there would still be "local" items and "remote" items for each.

So I had to come up with a way to receive items. "Simple!", a naive thought might begin, "Merely run the routine that grants a particular item ingame! Eureka, I'm a genius, subscribe to my Patreon!".

As my subtle portrayal of this strawman shows, it's not that easy. See, with the right programs (such as an emulator with Lua scripting capabilities), you can easily read and write memory on the fly for a running game. Now, what memory do you write to in order to run a particular routine? Well, you don't. You need the program to arrive there organically, as just yanking the program counter around to god knows what is, at best, going to lead to entertaining crashes. If a game branches based on a particular memory value, you can utilize that, but I wasn't so lucky. Z1's code tends to involve checking for collision between objects, and branching based on that. You can't really simulate that with a simple write to a couple of variables.

This is a long winded way of saying I had to write in inventory items directly. So, if you're sent a sword, write 1, 2, or 3 (depending on the sword) to the sword variable. If you're sent bombs, add 4 to the number of bombs you have, and so on. I wasn't satisfied with that, so I did some digging, and found out that when Link picks up an item, two variables are set. One is the item sprite to be drawn being held in the air, and the other is a timer for how long to keep that pose before resuming normal operation. Those I could easily write to.

So...that's what I did. I'd write in the necessary bytes directly, but I'd also write to the variables for Link holding an item up so it looked like you were picking up an item normally. While I was at it, I found out that Z1 uses has a "queued sound effect" byte that I could write to. While the familiar "item get" tune from Zelda is a music track (and thus would require me to resume the previous music after queueing up), the "secret found" tune is just as iconic and is a sound effect, and so would play with no problems.

So the bulk of this occurred in a Lua script that would be executed by the running emulator (support for other emulators and hardware being something that I'd let future me/other people tackle). Now, if I were to receive a Triforce piece, how would I write that in? The Triforce byte used one bit per piece, so I could just find the next bit in line and set that. Still, this would interact weirdly with the Recorder.

The Recorder is one of Z1's two fast-travel items. The way it works is it keeps track of a number in a byte. Use the Recorder while facing up or left, we decrement the byte. Face right or down, and we increment. We wrap around so the values range from 0-7, and we skip any dungeons we don't have the Triforce for. When the tornado hits Link, we go to the screen of the dungeon the byte is set to. This lets you easily warp between all of your cleared dungeons.

That would work if I was setting Triforce pieces arbitrarily, though. Sure, you clear Level 2, set the Level 2 Triforce bit, you can now warp to Level 2. That was unchanged. But what if you were sent a piece from someone else? In that case, we just set Level 1...but then what if Level 1 had a Triforce piece itself?

It all became too many edge cases to be worth considering. All because of one simple item that had to be special in how it transported the player.

So I changed the Recorder. Instead of it warping you between cleared dungeons, I allowed it to warp between all eight dungeons from the start. Now I could decouple Triforce pieces from their dungeons and not break another item. As a bonus, it becomes a fun convenient item to pick up and is an alternate route to Level 4, where you normally would need the otherwise-useless Raft.

Still, we weren't quite there yet. How do I handle multiple Triforce pieces in the same dungeon? Well, now I could change the Triforce pickup code. I made it so receiving a Triforce (either locally or remotely) would not just increment a counter, but also run code to update the "canon" Triforce byte for the two things left that I knew looked for it (Level 9 and the status screen) and anything I had missed.

And that's the tale of how I am, to my knowledge, the first Z1 randomizer developer to allow Triforce pieces to be anything other than one per dungeon. I'm rather proud of that.

So that was the easy part.

Next time: Rupees can be exchanged for goods and services.

Rosalie_A
Oct 30, 2011
Fun fact: those are two completely different maps and two completely different chests in the code. They just use the same RAM bit to reference if they've been opened or not, creating the illusion that the item gets better later.

Rosalie_A
Oct 30, 2011

NGDBSS posted:

Does Speed do something more substantial in this hack? In OG all I remember it doing was setting the turn order, but here or in a prior update low speed seems to have given enemies multiple turns compared to you.

Speed is only turn order.

All actors get one turn per round, but some enemies have counterattack scripts which makes it seem like they go multiple times.

Adbot
ADBOT LOVES YOU

Rosalie_A
Oct 30, 2011

CptWedgie posted:

The others may have skipped turns in the write-up, though; if my action is just "hit it with a hammer again" I don't bother.

It's this. SMRPG's battle system is extensively documented down to the byte: if there's randomness in turn order, it'd be news to everyone hacking the game.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply