Merge remote-tracking branch 'origin/OverworldShuffle' into OverworldShuffle

This commit is contained in:
2021-09-21 12:37:57 -07:00
43 changed files with 2615 additions and 1704 deletions

184
Rom.py
View File

@@ -29,9 +29,11 @@ from Items import ItemFactory
from EntranceShuffle import door_addresses, exit_ids
from OverworldShuffle import default_flute_connections, flute_data
from source.classes.SFX import randomize_sfx
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'b9914f4de5a16b9a8bb94363fec0ac15'
RANDOMIZERBASEHASH = '2233f0ed58291daf0a4aaac58826ac95'
class JsonRom(object):
@@ -101,8 +103,7 @@ class LocalRom(object):
self.buffer[address] = value
def write_bytes(self, startaddress, values):
for i, value in enumerate(values):
self.write_byte(startaddress + i, value)
self.buffer[startaddress:startaddress + len(values)] = values
def write_to_file(self, file):
with open(file, 'wb') as outfile:
@@ -625,71 +626,20 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
if world.pot_contents[player]:
write_pots_to_rom(rom, world.pot_contents[player])
# patch overworld edges
inverted_buffer = [0] * 0x82
if world.owShuffle[player] != 'vanilla' or world.owSwap[player] != 'vanilla':
owMode = 0
if world.owShuffle[player] == 'parallel':
owMode = 1
elif world.owShuffle[player] == 'full':
owMode = 2
if world.owSwap[player] == 'mixed':
owMode |= 0x100
world.fix_fake_world[player] = True
elif world.owSwap[player] == 'crossed':
owMode |= 0x200
world.fix_fake_world[player] = True
write_int16(rom, 0x150002, owMode)
owFlags = 0
if world.owKeepSimilar[player]:
owFlags |= 0x1
if world.owFluteShuffle[player] != 'vanilla':
owFlags |= 0x100
write_int16(rom, 0x150004, owFlags)
rom.write_byte(0x18004C, 0x01) # patch for allowing Frogsmith to enter multi-entrance caves
# patches map data specific for OW Shuffle
#inverted_buffer[0x03] = inverted_buffer[0x03] | 0x2 # convenient portal on WDM
inverted_buffer[0x1A] = inverted_buffer[0x1A] | 0x2 # rocks added to prevent OWG hardlock
inverted_buffer[0x1B] = inverted_buffer[0x1B] | 0x2 # rocks added to prevent OWG hardlock
inverted_buffer[0x22] = inverted_buffer[0x22] | 0x2 # rocks added to prevent OWG hardlock
inverted_buffer[0x3F] = inverted_buffer[0x3F] | 0x2 # added C to terrain
#inverted_buffer[0x43] = inverted_buffer[0x43] | 0x2 # convenient portal on WDDM
inverted_buffer[0x5A] = inverted_buffer[0x5A] | 0x2 # rocks added to prevent OWG hardlock
inverted_buffer[0x5B] = inverted_buffer[0x5B] | 0x2 # rocks added to prevent OWG hardlock
inverted_buffer[0x62] = inverted_buffer[0x62] | 0x2 # rocks added to prevent OWG hardlock
inverted_buffer[0x7F] = inverted_buffer[0x7F] | 0x2 # added C to terrain
if world.owSwap[player] == 'mixed':
for b in world.owswaps[player][0]:
# load inverted maps
inverted_buffer[b] = (inverted_buffer[b] & 0xFE) | ((inverted_buffer[b] + 1) % 2)
# set world flag
rom.write_byte(0x153A00 + b, 0x00 if b >= 0x40 else 0x40)
for edge in world.owedges:
if edge.dest is not None and isinstance(edge.dest, OWEdge) and edge.player == player:
write_int16(rom, edge.getAddress() + 0x0a, edge.vramLoc)
write_int16(rom, edge.getAddress() + 0x0e, edge.getTarget())
# patch flute spots
owFlags = 0
if world.owFluteShuffle[player] == 'vanilla':
flute_spots = default_flute_connections
else:
flute_spots = world.owflutespots[player]
owFlags |= 0x100
for o in range(0, len(flute_spots)):
owslot = flute_spots[o]
offset = 0
data = flute_data[owslot]
if (world.mode[player] == 'inverted') != (data[1] in world.owswaps[player][0] and world.owSwap[player] == 'mixed'):
if (world.mode[player] == 'inverted') != (data[1] in world.owswaps[player][0] and world.owMixed[player]):
offset = 0x40
write_int16(rom, snes_to_pc(0x02E849 + (o * 2)), data[1] + offset) # owid
@@ -709,6 +659,54 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_byte(snes_to_pc(0x0AB793 + o), data[11] & 0xff) # Y low byte
rom.write_byte(snes_to_pc(0x0AB79B + o), data[11] // 0x100) # Y high byte
# patch overworld edges
inverted_buffer = [0] * 0x82
if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] != 'none' or world.owMixed[player]:
owMode = 0
if world.owShuffle[player] == 'parallel':
owMode = 1
elif world.owShuffle[player] == 'full':
owMode = 2
if world.owKeepSimilar[player] and (world.owShuffle[player] != 'vanilla' or world.owCrossed[player] in ['limited', 'chaos']):
owMode |= 0x100
if world.owCrossed[player] != 'none' and (world.owCrossed[player] != 'polar' or world.owMixed[player]):
owMode |= 0x200
world.fix_fake_world[player] = True
if world.owMixed[player]:
owMode |= 0x400
write_int16(rom, 0x150002, owMode)
write_int16(rom, 0x150004, owFlags)
rom.write_byte(0x18004C, 0x01) # patch for allowing Frogsmith to enter multi-entrance caves
# patches map data specific for OW Shuffle
#inverted_buffer[0x03] = inverted_buffer[0x03] | 0x2 # convenient portal on WDM
inverted_buffer[0x1A] = inverted_buffer[0x1A] | 0x2 # rocks added to prevent OWG hardlock
inverted_buffer[0x1B] = inverted_buffer[0x1B] | 0x2 # rocks added to prevent OWG hardlock
inverted_buffer[0x22] = inverted_buffer[0x22] | 0x2 # rocks added to prevent OWG hardlock
inverted_buffer[0x3F] = inverted_buffer[0x3F] | 0x2 # added C to terrain
#inverted_buffer[0x43] = inverted_buffer[0x43] | 0x2 # convenient portal on WDDM
inverted_buffer[0x5A] = inverted_buffer[0x5A] | 0x2 # rocks added to prevent OWG hardlock
inverted_buffer[0x5B] = inverted_buffer[0x5B] | 0x2 # rocks added to prevent OWG hardlock
inverted_buffer[0x62] = inverted_buffer[0x62] | 0x2 # rocks added to prevent OWG hardlock
inverted_buffer[0x7F] = inverted_buffer[0x7F] | 0x2 # added C to terrain
if world.owMixed[player]:
for b in world.owswaps[player][0]:
# load inverted maps
inverted_buffer[b] = (inverted_buffer[b] & 0xFE) | ((inverted_buffer[b] + 1) % 2)
# set world flag
rom.write_byte(0x153A00 + b, 0x00 if b >= 0x40 else 0x40)
for edge in world.owedges:
if edge.dest is not None and isinstance(edge.dest, OWEdge) and edge.player == player:
write_int16(rom, edge.getAddress() + 0x0a, edge.vramLoc)
write_int16(rom, edge.getAddress() + 0x0e, edge.getTarget())
# patch entrance/exits/holes
for region in world.regions:
@@ -1165,7 +1163,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_bytes(0x184000, [
# original_item, limit, replacement_item, filler
0x12, 0x01, 0x35, 0xFF, # lamp -> 5 rupees
0x51, 0x00 if world.bomblogic[player] else 0x06, 0x31 if world.bomblogic[player] else 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade. If bomblogic -> turns into Bombs (10)
0x51, 0x00 if world.bombbag[player] else 0x06, 0x31 if world.bombbag[player] else 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade. If bombbag -> turns into Bombs (10)
0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade
0x58, 0x01, 0x36 if world.retro[player] else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode)
0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20
@@ -1346,7 +1344,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
equip[0x36C] = 0x18
equip[0x36D] = 0x18
equip[0x379] = 0x68
if world.bomblogic[player]:
if world.bombbag[player]:
starting_max_bombs = 0
else:
starting_max_bombs = 10
@@ -1673,7 +1671,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_bytes(0x180188, [0, 0, 10]) # Zelda respawn refills (magic, bombs, arrows)
rom.write_bytes(0x18018B, [0, 0, 10]) # Mantle respawn refills (magic, bombs, arrows)
bow_max, bow_small = 70, 10
elif uncle_location.item is not None and uncle_location.item.name in ['Bomb Upgrade (+10)' if world.bomblogic[player] else 'Bombs (10)']:
elif uncle_location.item is not None and uncle_location.item.name in ['Bomb Upgrade (+10)' if world.bombbag[player] else 'Bombs (10)']:
rom.write_byte(0x18004E, 2) # Escape Fill (bombs)
rom.write_bytes(0x180185, [0, 50, 0]) # Uncle respawn refills (magic, bombs, arrows)
rom.write_bytes(0x180188, [0, 3, 0]) # Zelda respawn refills (magic, bombs, arrows)
@@ -1837,7 +1835,7 @@ def hud_format_text(text):
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite,
ow_palettes, uw_palettes, reduce_flashing):
ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx):
if not os.path.exists("data/sprites/official/001.link.1.zspr") and rom.orig_buffer:
dump_zspr(rom.orig_buffer[0x80000:0x87000], rom.orig_buffer[0xdd308:0xdd380],
@@ -1926,6 +1924,9 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr
elif uw_palettes == 'blackout':
blackout_uw_palettes(rom)
if shuffle_sfx:
randomize_sfx(rom)
if isinstance(rom, LocalRom):
rom.write_crc()
@@ -2264,10 +2265,10 @@ def write_strings(rom, world, player, team):
if world.shuffle[player] in ['insanity', 'madness_legacy', 'insanity_legacy']:
entrances_to_hint.update(InsanityEntrances)
if world.shuffle_ganon:
if world.mode[player] == 'inverted':
if world.mode[player] == 'inverted' != (0x1b in world.owswaps[player][0] and world.owMixed[player]):
entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'})
else:
entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'})
entrances_to_hint.update({'Pyramid Entrance': 'The pyramid ledge'})
hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 0
for entrance in all_entrances:
if entrance.name in entrances_to_hint:
@@ -2334,7 +2335,7 @@ def write_strings(rom, world, player, team):
tt[hint_locations.pop(0)] = this_hint
# Adding a guaranteed hint for the Flute in overworld shuffle.
if world.owShuffle[player] in ['parallel','full']:
if world.owShuffle[player] != 'vanilla' or world.owMixed[player]:
this_location = world.find_items_not_key_only('Ocarina', player)
if this_location:
this_hint = this_location[0].item.hint_text + ' can be found ' + hint_text(this_location[0]) + '.'
@@ -2342,7 +2343,7 @@ def write_strings(rom, world, player, team):
# Lastly we write hints to show where certain interesting items are. It is done the way it is to re-use the silver code and also to give one hint per each type of item regardless of how many exist. This supports many settings well.
items_to_hint = RelevantItems.copy()
if world.owShuffle[player] in ['parallel','full']:
if world.owShuffle[player] != 'vanilla' or world.owMixed[player]:
items_to_hint.remove('Ocarina')
if world.keyshuffle[player]:
items_to_hint.extend(SmallKeys)
@@ -2351,7 +2352,7 @@ def write_strings(rom, world, player, team):
random.shuffle(items_to_hint)
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8
hint_count += 2 if world.doorShuffle[player] == 'crossed' else 0
hint_count += 1 if world.owShuffle[player] in ['parallel', 'full'] else 0
hint_count += 1 if world.owShuffle[player] != 'vanilla' or world.owMixed[player] else 0
while hint_count > 0:
this_item = items_to_hint.pop(0)
this_location = world.find_items_not_key_only(this_item, player)
@@ -2563,7 +2564,7 @@ def set_inverted_mode(world, player, rom, inverted_buffer):
# load inverted maps
for b in range(0x00, len(inverted_buffer)):
inverted_buffer[b] = (inverted_buffer[b] & 0xFE) | ((inverted_buffer[b] + 1) % 2)
rom.write_byte(snes_to_pc(0x0283E0), 0xF0) # residual portals
rom.write_byte(snes_to_pc(0x02B34D), 0xF0)
rom.write_byte(snes_to_pc(0x06DB78), 0x8B) # dark-style portal
@@ -2574,7 +2575,7 @@ def set_inverted_mode(world, player, rom, inverted_buffer):
rom.write_byte(snes_to_pc(0x08D40C), 0xD0) # morph poof
rom.write_byte(snes_to_pc(0x0ABFBB), 0x90) # move mirror portal indicator to correct map (0xB0 normally)
rom.write_byte(snes_to_pc(0x0280A6), 0xD0) # use starting point prompt instead of start at pyramid
write_int16(rom, snes_to_pc(0x02D8D4), 0x112) # change sanctuary spawn point to dark sanc
rom.write_bytes(snes_to_pc(0x02D8E8), [0x22, 0x22, 0x22, 0x23, 0x04, 0x04, 0x04, 0x05])
write_int16(rom, snes_to_pc(0x02D91A), 0x0400)
@@ -2599,10 +2600,11 @@ def set_inverted_mode(world, player, rom, inverted_buffer):
if world.doorShuffle[player] == 'vanilla' or world.intensity[player] < 3:
write_int16(rom, 0x15AEE + 2*0x38, 0x00E0)
write_int16(rom, 0x15AEE + 2*0x25, 0x000C)
if (world.mode[player] == 'inverted') != (0x03 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'):
if (world.mode[player] == 'inverted') != (0x03 in world.owswaps[player][0] and world.owMixed[player]):
if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple']:
rom.write_bytes(snes_to_pc(0x308350), [0x00, 0x00, 0x01]) # mountain cave starts on OW
write_int16(rom, snes_to_pc(0x02D8DE), 0x00F1) # change mountain cave spawn point to just outside old man cave
rom.write_bytes(snes_to_pc(0x02D910), [0x1F, 0x1E, 0x1F, 0x1F, 0x03, 0x02, 0x03, 0x03])
write_int16(rom, snes_to_pc(0x02D924), 0x0300)
@@ -2622,18 +2624,18 @@ def set_inverted_mode(world, player, rom, inverted_buffer):
rom.write_byte(snes_to_pc(0x02D9B8), 0x12)
rom.write_bytes(0x180247, [0x00, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00]) #indicates the overworld door being used for the single entrance spawn point
if (world.mode[player] == 'inverted') != (0x05 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'):
if (world.mode[player] == 'inverted') != (0x05 in world.owswaps[player][0] and world.owMixed[player]):
rom.write_bytes(snes_to_pc(0x1BC655), [0x4A, 0x1D, 0x82]) # add warp under rock
if (world.mode[player] == 'inverted') != (0x07 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'):
if (world.mode[player] == 'inverted') != (0x07 in world.owswaps[player][0] and world.owMixed[player]):
rom.write_bytes(snes_to_pc(0x1BC387), [0xDD, 0xD1]) # add warps under rocks
rom.write_bytes(snes_to_pc(0x1BD1DD), [0xA4, 0x06, 0x82, 0x9E, 0x06, 0x82, 0xFF, 0xFF]) # add warps under rocks
rom.write_byte(0x180089, 0x01) # open TR after exit
rom.write_bytes(0x0086E, [0x5C, 0x00, 0xA0, 0xA1]) # TR tail
if world.shuffle[player] in ['vanilla']:
world.fix_trock_doors[player] = True
if (world.mode[player] == 'inverted') != (0x10 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'):
if (world.mode[player] == 'inverted') != (0x10 in world.owswaps[player][0] and world.owMixed[player]):
rom.write_bytes(snes_to_pc(0x1BC67A), [0x2E, 0x0B, 0x82]) # add warp under rock
if (world.mode[player] == 'inverted') != (0x1B in world.owswaps[player][0] and world.owSwap[player] == 'mixed'):
if (world.mode[player] == 'inverted') != (0x1B in world.owswaps[player][0] and world.owMixed[player]):
write_int16(rom, 0x15AEE + 2 * 0x06, 0x0020) # post aga hyrule castle spawn
rom.write_byte(0x15B8C + 0x06, 0x1B)
write_int16(rom, 0x15BDB + 2 * 0x06, 0x00AE)
@@ -2647,7 +2649,7 @@ def set_inverted_mode(world, player, rom, inverted_buffer):
rom.write_byte(0x1607C + 0x06, 0xF2)
write_int16(rom, 0x160CB + 2 * 0x06, 0x0000)
write_int16(rom, 0x16169 + 2 * 0x06, 0x0000)
write_int16(rom, snes_to_pc(0x02E859), 0x001B) # move flute spot 9
write_int16(rom, snes_to_pc(0x02E87B), 0x00AE)
write_int16(rom, snes_to_pc(0x02E89D), 0x0610)
@@ -2665,12 +2667,12 @@ def set_inverted_mode(world, player, rom, inverted_buffer):
0x0C, 0x00, 0x7A, 0xAE, 0x0C, 0x00, 0x8A,
0xAE, 0x0C, 0x00, 0x67, 0x97, 0x0C, 0x00,
0x8D, 0x97, 0x0C, 0x00])
rom.write_byte(snes_to_pc(0x00D009), 0x31) # castle hole graphics
rom.write_byte(snes_to_pc(0x00D0E8), 0xE0)
rom.write_byte(snes_to_pc(0x00D1C7), 0x00)
write_int16(rom, snes_to_pc(0x1BE8DA), 0x39AD) # add color for shading for castle hole
#castle hole map16 data
write_int16s(rom, snes_to_pc(0x0FF1C8), [0x190F, 0x190F, 0x190F, 0x194C, 0x190F,
0x194B, 0x190F, 0x195C, 0x594B, 0x194C,
@@ -2685,18 +2687,18 @@ def set_inverted_mode(world, player, rom, inverted_buffer):
write_int16s(rom, snes_to_pc(0x1BB810), [0x00BE, 0x00C0, 0x013E]) # update pyramid hole entrance
write_int16s(rom, snes_to_pc(0x1BB836), [0x001B, 0x001B, 0x001B])
write_int16(rom, snes_to_pc(0x308300), 0x0140) # add extra pyramid hole
write_int16(rom, snes_to_pc(0x308320), 0x001B)
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
rom.write_byte(snes_to_pc(0x308340), 0x7B)
rom.write_byte(snes_to_pc(0x00DB9D), 0x1A) # make retreat bat gfx available in HC area
rom.write_byte(snes_to_pc(0x00DC09), 0x1A)
rom.write_byte(snes_to_pc(0x1AF696), 0xF0) # bat sprite retreat : bat X position
rom.write_byte(snes_to_pc(0x1AF6B2), 0x33) # bat sprite retreat : bat delay
write_int16(rom, snes_to_pc(0x1af504), 0x148B) # prioritize retreat Bat and use 3rd sprite group
write_int16(rom, snes_to_pc(0x1af50c), 0x149B)
write_int16(rom, snes_to_pc(0x1af514), 0x14A4)
@@ -2715,33 +2717,33 @@ def set_inverted_mode(world, player, rom, inverted_buffer):
write_int16(rom, snes_to_pc(0x1af57c), 0x548E)
write_int16(rom, snes_to_pc(0x1af584), 0x14AE)
write_int16(rom, snes_to_pc(0x1af58c), 0x54AE)
rom.write_byte(0xF6E58, 0x80) # no whirlpool under castle gate
rom.write_byte(snes_to_pc(0x09D436), 0xF3) # replace whirlpool with harmless sprite
write_int16(rom, 0xDB96F + 2 * 0x35, 0x001B) # move pyramid exit door
write_int16(rom, 0xDBA71 + 2 * 0x35, 0x011C)
if (world.mode[player] == 'inverted') != (0x29 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'):
if (world.mode[player] == 'inverted') != (0x29 in world.owswaps[player][0] and world.owMixed[player]):
rom.write_bytes(snes_to_pc(0x06B2AB), [0xF0, 0xE1, 0x05]) # frog pickup on contact
if (world.mode[player] == 'inverted') != (0x2C in world.owswaps[player][0] and world.owSwap[player] == 'mixed'):
if (world.mode[player] == 'inverted') != (0x2C in world.owswaps[player][0] and world.owMixed[player]):
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
rom.write_byte(0x15B8C, 0x6C) # exit links at bomb shop area
rom.write_byte(0xDBB73 + 0x00, 0x53) # switch bomb shop and links house
rom.write_byte(0xDBB73 + 0x52, 0x01)
if (world.mode[player] == 'inverted') != (0x2F in world.owswaps[player][0] and world.owSwap[player] == 'mixed'):
if (world.mode[player] == 'inverted') != (0x2F in world.owswaps[player][0] and world.owMixed[player]):
rom.write_bytes(snes_to_pc(0x1BC80D), [0xB2, 0x0B, 0x82]) # add warp under rock
if (world.mode[player] == 'inverted') != (0x30 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'):
if (world.mode[player] == 'inverted') != (0x30 in world.owswaps[player][0] and world.owMixed[player]):
rom.write_bytes(snes_to_pc(0x1BC81E), [0x94, 0x1D, 0x82]) # add warp under rock
if (world.mode[player] == 'inverted') != (0x33 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'):
if (world.mode[player] == 'inverted') != (0x33 in world.owswaps[player][0] and world.owMixed[player]):
rom.write_bytes(snes_to_pc(0x1BC3DF), [0xD8, 0xD1]) # add warp under rock
rom.write_bytes(snes_to_pc(0x1BD1D8), [0xA8, 0x02, 0x82, 0xFF, 0xFF]) # add warp under rock
if (world.mode[player] == 'inverted') != (0x35 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'):
if (world.mode[player] == 'inverted') != (0x35 in world.owswaps[player][0] and world.owMixed[player]):
rom.write_bytes(snes_to_pc(0x1BC85A), [0x50, 0x0F, 0x82]) # add warp under rock
# apply inverted map changes
for b in range(0x00, len(inverted_buffer)):
rom.write_byte(0x153B00 + b, inverted_buffer[b])
def patch_shuffled_dark_sanc(world, rom, player):
dark_sanc = world.get_region('Dark Sanctuary Hint', player)
dark_sanc_entrance = str([i for i in dark_sanc.entrances if i.parent_region.name != 'Menu'][0].name)
@@ -3048,7 +3050,7 @@ def write_pots_to_rom(rom, pot_contents):
pots = [pot for pot in pot_contents[i] if pot.item != PotItem.Nothing]
if len(pots) > 0:
write_int16(rom, pot_item_room_table_lookup + 2*i, n)
rom.write_bytes(n, itertools.chain(*((pot.x,pot.y,pot.item) for pot in pots)))
rom.write_bytes(n, list(itertools.chain.from_iterable(((pot.x, pot.y, pot.item) for pot in pots))))
n += 3*len(pots) + 2
rom.write_bytes(n - 2, [0xFF,0xFF])
else: