PART 23

So for this one I decided I wanted to do something like Part 17 again where I set up a webpage that the player must use to help them get the Ice Arrow. I decided to theme it again with one of the Minigames in Majora's Mask and this time I used the Treasure Chest Shop.

At the end of the game an image is revealed to the player. The image is suppose to be in the style of a scanned instruction manual page with some notes scralled on it. I got this idea from a game that came out recently that I've been playing. It's called TUNIC and it is a lot of fun and I highly recommend it especially if you like the Zelda series. In the game the player can collect instruction manual pages and look at them to get a better understanding of the game.

I am by no means a graphic artist but I think the image turned out pretty well. I drew the map myself, though I had some help by using the collision model of Termina Field as a reference. Clock Town has some areas that I simply filled in since they are not really used. As per usual though I'm somewhat of a perfectionist and probably spent to much time adding a lot of detail that wasn't necessary.

The Image

Through careful observation it should become apparent to the player where they need to go by looking at the image. There's an extra grotto marked on the pillar outside East Clock Town near the Path to Ikana Canyon. Without the aid of glitches the player will need to use the Magic Bean plant to reach it! From there they access a custom map that I made for my Majora's Mask: Beta Quest ROM Hack. The map is called the "Chamber of Fate" and the player can possibly get a good item out of one of the three chests in it… assuming they can defeat the Iron Knuckles!

The Ice Arrow: Chamber of Fate

Accessiable by a hidden grotto added in Termina Field. Here the three Iron Knuckles must be defeated to get the Ice Arrow. All three of the chests that spawn have Ice Arrows in them. I would never troll the player! 😏

My Treasure Chest Shop Game

The game is more or less an exploration bingo. Which, funny enough, I didn't even know was a thing until shortly after starting to make this. I saw jenslang, an Ocarina of Time Speedrunner/Bingo Player, was doing one a day or two after I came up with this idea and started working on it. Ever since I came back to making more IAIMs in late 2021 I wanted to do something special again like part 17 and this is it! The original idea was actually going to just be a 3x3 bingo card. Completion of a row, column, or diagonal would reveal a clue to the player. Then I decided I wanted to do something similar to part 17 where I themed it around a mini-game from Majora's Mask. The Treasure Chest Shop seemed very fitting since the maze area is sort of like a bingo card… but larger.

How it works

The game selects a predetermined maze and then randomly selects tasks for the pathway though. In Majora's Mask the Treasure Chest Shop game actually decides where to place the chest first then works it's way backwards to generate the path. I thought about doing this myself as well but then got lazy and decided I didn't want to try and program that.

So instead I decided I'd just let the game create the layouts for me. For this I created a Lua script I could run in the BizHawk emulator. The script makes use of a savestate that is setup right before the game starts. It then starts the game and then grabs the generated layout of the maze and stores it in a text file. After that it reloads the savestate and repeats. I then simply let the script run for around an hour or so while I did other stuff. The script is listed befow for anyone that is interested in seeing it. (click here to scroll to it)

I let the script run long enough to get 200 layouts. I did not check for any duplicates but I would assume it's probably not that likely to get any but possibly a few. I then went though and picked out and only kept 3 layouts for each ending position. Which means there are a total of 24 possible layouts and one is randomly selected when the game is started. They do differ in how many tiles are required to reach the end but the average is around 30 assuming the player only chooses the correct pathway through.

The tasks assigned to each tile are also randomly selected. I separated the tasks into groups by area of the game. These areas I defined as the following: Clock Town, Swamp, Mountain, Ranch, Ocean, and Canyon. In total there are 84 possible tasks that can be selected from.

I also decided to set up a few different game modes for different types of players. There are 3 modes in total: Casual, Glitched, and RNG-Only. For Casual mode tasks are placed such that there will be a sort of progressive order as the player plays through the game. This helps to reduce backtracking. Most players at large would agree some backtracking is fine but a lot of it is bad and especially when it's forced with no way around it. Glitched mode still has a some progression to it however the order of areas gets randomized. And last but not least is the RNG-Only mode which doesn't have any kind of progression to it at all. Any task can be placed anywhere. If you love backtracking and constantly going all around the game this is the mode for you!

Now you might be wondering exactly how does the progression work? It works simply based on columns and it is done starting at the left and going to the right. Each of the 6 areas gets two columns worth of tasks with the exception of the Ranch which only gets one column. With Casual mode columns 1 and 2 will have Clock Town tasks, 3 and 4 will have Swamp, etc… With Glitched mode the order will just be shifted around at random but it will still be set up the same such that 2 of the 11 columns will only consist of Clock Town tasks and so on.

Casual
Glitched Example

Cheating

I decided to add in cheat codes myself just because I could (they also helped with testing). There are a few of them and they are listed below. At any time, unless otherwise specified, if you type out the code with the browser window in focus it will activate.

  • Reveal all the rising tiles.
  • Reveal all task icons.
  • Reveal all the rising tiles and task icons.
  • Auto select correct icon. (only when the icon menu is open)
  • Reveal the end image.
  • Clears revealed rising tiles, revealed icons, and tiles cleared by icon auto select.
lasers - All of the rising tiles are colored red.
iconic - All of the task tiles have their icon shown.

Useless Box

You probably will have noticed the Treasure Chest Shop sign that kind of pops up for a bit then goes away along the top of the game right? Well I have good news for you. It's completely useless! It doesn't serve any purpose other than I decided to add it there for fun. It will randomly pop up with a random amount of how much should be visible and it will hide again after a short random amount of time. It can also only appear in the outer 3 most columns on either side.

The design behind it is inspired by the "useless box". If you don't know what that is here is a video of what I'm talking about. Some are very basic while others are designed to have quite the personality! When you If you manage to hover your cursor over it then it will quickly hide! It does keep track of how many times you do manage to hover over it but this doesn't get shown or displayed anywhere.

Lua Script

Below is the Lua script I used to get generated tile layouts from the game itself. ActorViewer is something I made awhile ago for reading the actor list and "core" is the main code behind it with several functions for different things, you can see it on Github. One of it's functions is to find an actor by a specific ID which I will use to find the maze tile actor and the chest actor.

core=require "ActorViewer.core";
 
-- get the generated layout of the maze.
function getLayout()
	local file=io.open("layout.txt","a");
	-- get actors.
	-- core.getActorsByID returns an array of pointers.
	-- in this case there will only be one instance of each actor so I use the first one.
	local tile=core.getActorsByID(0x01BB)[1];
	local chest=core.getActorsByID(0x0006)[1];
	local bytes={};
	for i=0, 0x57 do
		bytes[i]=memory.read_u8(tile-0x1D0+i);
		if (bytes[i]==0) then		-- if not a tile that blocks the player.
			bytes[i]=1;
		elseif(bytes[i]==2) then	-- if a tile that blocks the player.
			bytes[i]=0;
		end
	end
	-- check where the chest was placed.
	-- then set which tile it's on.
	local chest_z=memory.readfloat(chest+0x2C, true);
	if (chest_z==60.0) then
		bytes[0]="e";
	elseif(chest_z==180.0) then
		bytes[1]="e";
	elseif(chest_z==300.0) then
		bytes[2]="e";
	elseif(chest_z==420.0) then
		bytes[3]="e";
	elseif(chest_z==540.0) then
		bytes[4]="e";
	elseif(chest_z==660.0) then
		bytes[5]="e";
	elseif(chest_z==780.0) then
		bytes[6]="e";
	elseif(chest_z==900.0) then
		bytes[7]="e";
	end
	file:write(string.format("%1s %1s %1s %1s %1s %1s %1s %1s %1s %1s %1s\n", bytes[87], bytes[79], bytes[71], bytes[63], bytes[55], bytes[47], bytes[39], bytes[31], bytes[23], bytes[15], bytes[7]));
	file:write(string.format("%1s %1s %1s %1s %1s %1s %1s %1s %1s %1s %1s\n", bytes[86], bytes[78], bytes[70], bytes[62], bytes[54], bytes[46], bytes[38], bytes[30], bytes[22], bytes[14], bytes[6]));
	file:write(string.format("%1s %1s %1s %1s %1s %1s %1s %1s %1s %1s %1s\n", bytes[85], bytes[77], bytes[69], bytes[61], bytes[53], bytes[45], bytes[37], bytes[29], bytes[21], bytes[13], bytes[5]));
	file:write(string.format("%1s %1s %1s %1s %1s %1s %1s %1s %1s %1s %1s\n", bytes[84], bytes[76], bytes[68], bytes[60], bytes[52], bytes[44], bytes[36], bytes[28], bytes[20], bytes[12], bytes[4]));
	file:write(string.format("%1s %1s %1s %1s %1s %1s %1s %1s %1s %1s %1s\n", bytes[83], bytes[75], bytes[67], bytes[59], bytes[51], bytes[43], bytes[35], bytes[27], bytes[19], bytes[11], bytes[3]));
	file:write(string.format("%1s %1s %1s %1s %1s %1s %1s %1s %1s %1s %1s\n", bytes[82], bytes[74], bytes[66], bytes[58], bytes[50], bytes[42], bytes[34], bytes[26], bytes[18], bytes[10], bytes[2]));
	file:write(string.format("%1s %1s %1s %1s %1s %1s %1s %1s %1s %1s %1s\n", bytes[81], bytes[73], bytes[65], bytes[57], bytes[49], bytes[41], bytes[33], bytes[25], bytes[17], bytes[9], bytes[1]));
	file:write(string.format("%1s %1s %1s %1s %1s %1s %1s %1s %1s %1s %1s\n\n", bytes[80], bytes[72], bytes[64], bytes[56], bytes[48], bytes[40], bytes[32], bytes[24], bytes[16], bytes[8], bytes[0]));
	file:close(file);
end
 
-- start the treasure chest game.
function play()
	wait(math.random(20,400));
	pressA();
	wait(200);
	pressA();
	wait(50);
	pressA();
	wait(250);
	pressA();
	wait(100);
	pressA();
	wait(100);
	pressA();
	wait(100);
end
 
-- advance an amount of frames.
function wait(frames)
	for i=0, frames, 1 do
		emu.frameadvance();
	end
end
 
-- press and release the A button.
function pressA()
	joypad.set({A=1}, 1);
	emu.frameadvance();
	joypad.set({A=0}, 1);
end
 
-- main function that is called repeatedly.
function init()
	play();
	getLayout();
	for i=0, 20, 1 do
		emu.frameadvance();
	end
	savestate.loadslot(1);
end
 
-- if majora's mask and known version.
if (core.init())then
	while true do
		init();
		emu.frameadvance();
	end
end