Merged in DR v1.0.1.0

This commit is contained in:
codemann8
2022-07-08 10:59:00 -05:00
64 changed files with 4100 additions and 1396 deletions

530
Rom.py
View File

@@ -1,6 +1,6 @@
import bisect
import collections
import io
import itertools
import json
import hashlib
import logging
@@ -15,7 +15,7 @@ try:
except ImportError:
raise Exception('Could not load BPS module')
from BaseClasses import CollectionState, ShopType, Region, Location, OWEdge, Door, DoorType, RegionType, PotItem
from BaseClasses import ShopType, Region, Location, OWEdge, Door, DoorType, RegionType, LocationType, Item
from DoorShuffle import compass_data, DROptions, boss_indicator, dungeon_portals
from Dungeons import dungeon_music_addresses, dungeon_table
from Regions import location_table, shop_to_location_table, retro_shops
@@ -23,17 +23,22 @@ from RoomData import DoorKind
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
from Text import Uncle_texts, Ganon1_texts, Ganon_Phase_3_No_Silvers_texts, TavernMan_texts, Sahasrahla2_texts
from Text import Triforce_texts, Blind_texts, BombShop2_texts, junk_texts
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts
from Text import LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts
from Text import Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc
from Items import ItemFactory
from EntranceShuffle import door_addresses, exit_ids, ow_prize_table
from OverworldShuffle import default_flute_connections, flute_data
from InitialSram import InitialSram
from source.classes.SFX import randomize_sfx
from source.item.FillUtil import valid_pot_items
from source.dungeon.RoomList import Room0127
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'fc6f1d6ba782d08ac92600c31cc7ee43'
RANDOMIZERBASEHASH = '210e4631353e3d094f01bf91562844a5'
class JsonRom(object):
@@ -44,6 +49,7 @@ class JsonRom(object):
self.orig_buffer = None
self.patches = {}
self.addresses = []
self.initial_sram = InitialSram()
def write_byte(self, address, value):
self.write_bytes(address, [value])
@@ -73,6 +79,9 @@ class JsonRom(object):
del self.patches[str(intervalstart)]
del self.addresses[pos]
def write_initial_sram(self):
self.write_bytes(0x183000, self.initial_sram.get_initial_sram())
def write_to_file(self, file):
with open(file, 'w') as stream:
json.dump([self.patches], stream)
@@ -90,6 +99,7 @@ class LocalRom(object):
self.hash = hash
self.orig_buffer = None
self.file = file
self.initial_sram = InitialSram()
self.has_smc_header = False
if not os.path.isfile(file):
raise RuntimeError("Could not find valid local base rom for patching at expected path %s." % file)
@@ -105,6 +115,9 @@ class LocalRom(object):
def write_bytes(self, startaddress, values):
self.buffer[startaddress:startaddress + len(values)] = values
def write_initial_sram(self):
self.write_bytes(0x183000, self.initial_sram.get_initial_sram())
def write_to_file(self, file):
with open(file, 'wb') as outfile:
outfile.write(self.buffer)
@@ -117,6 +130,13 @@ class LocalRom(object):
ret.write_bytes(int(address), values)
return ret
def verify_base_rom(self):
# verify correct checksum of baserom
basemd5 = hashlib.md5()
basemd5.update(self.buffer)
if JAP10HASH != basemd5.hexdigest():
raise RuntimeError('Supplied Base Rom does not match known MD5 for JAP(1.0) release.')
def patch_base_rom(self):
# verify correct checksum of baserom
basemd5 = hashlib.md5()
@@ -163,7 +183,6 @@ class LocalRom(object):
with open(local_path('data/base2current.json'), 'w') as fp:
json.dump(patches, fp, separators=(',', ':'))
def write_crc(self):
crc = (sum(self.buffer[:0x7FDC] + self.buffer[0x7FE0:]) + 0x01FE) & 0xFFFF
inv = crc ^ 0xFFFF
@@ -335,6 +354,9 @@ def patch_enemizer(world, player, rom, local_rom, enemizercli, random_sprite_on_
0xad, 0x3, 0x4, 0x29, 0x20, 0xf0, 0x1d])
rom.write_byte(0x200101, 0) # Do not close boss room door on entry.
rom.write_byte(0x1B0101, 0) # Do not close boss room door on entry. (for Ijwu's enemizer)
else:
rom.write_byte(0x04DE83, 0xB3) # maiden is now something else
if random_sprite_on_hit:
_populate_sprite_table()
@@ -549,6 +571,21 @@ class Sprite(object):
return array_chunk(palette_as_colors, 15)
def handle_native_dungeon(location, itemid):
# Keys in their native dungeon should use the original item code for keys
if location.parent_region.dungeon:
if location.parent_region.dungeon.name == location.item.dungeon:
if location.item.bigkey:
return 0x32
if location.item.smallkey:
return 0x24
if location.item.map:
return 0x33
if location.item.compass:
return 0x25
return itemid
def patch_rom(world, rom, player, team, enemized, is_mystery=False):
random.seed(world.rom_seeds[player])
@@ -560,28 +597,46 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
distinguished_prog_bow_loc.item.code = 0x65
# patch items
pot_mw_index = 0
for location in world.get_locations():
if location.player != player:
continue
itemid = location.item.code if location.item is not None else 0x5A
if location.type == LocationType.Pot:
if location.item.name in valid_pot_items and location.item.player == player:
location.pot.item = valid_pot_items[location.item.name]
else:
code = handle_native_dungeon(location, itemid)
standing_item_flag = 0x80
if location.item.player != player:
standing_item_flag |= 0x40
rom.write_byte(0x142800 + pot_mw_index * 2, code)
rom.write_byte(0x142800 + pot_mw_index * 2 + 1, location.item.player)
code = pot_mw_index
pot_mw_index += 1
location.pot.indicator = standing_item_flag
location.pot.standing_item_code = code
continue
elif location.type == LocationType.Drop:
if location.item.player != player:
code = 0xF9
else:
code = 0xF8
sprite_pointer = snes_to_pc(location.address)
rom.write_byte(sprite_pointer, handle_native_dungeon(location, itemid))
if code == 0xF9:
rom.write_byte(sprite_pointer+1, location.item.player)
else:
rom.write_byte(sprite_pointer+1, 0)
rom.write_byte(sprite_pointer+2, code)
continue
if location.address is None or (type(location.address) is int and location.address >= 0x400000):
continue
if not location.crystal:
if location.item is not None:
# Keys in their native dungeon should use the original item code for keys
if location.parent_region.dungeon:
if location.parent_region.dungeon.is_dungeon_item(location.item):
if location.item.bigkey:
itemid = 0x32
if location.item.smallkey:
itemid = 0x24
if location.item.map:
itemid = 0x33
if location.item.compass:
itemid = 0x25
itemid = handle_native_dungeon(location, itemid)
if world.remote_items[player]:
itemid = list(location_table.keys()).index(location.name) + 1
assert itemid < 0x100
@@ -609,8 +664,25 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
if world.mapshuffle[player]:
rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
if world.pottery[player] not in ['none']:
rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2A8000))
# make hammer pegs use different tiles
Room0127.write_to_rom(snes_to_pc(0x2A8000), rom)
if world.pot_contents[player]:
write_pots_to_rom(rom, world.pot_contents[player])
colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery']
and (world.colorizepots[player]
or world.pottery[player] in ['reduced', 'clustered']))
if world.pot_contents[player].size() > 0x2800:
raise Exception('Pot table is too big for current area')
world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots)
# fix for swamp drains if necessary
swamp1location = world.get_location('Swamp Palace - Trench 1 Pot Key', player)
if not swamp1location.pot.indicator:
rom.write_byte(0x142A53, 0)
swamp2location = world.get_location('Swamp Palace - Trench 2 Pot Key', player)
if not swamp2location.pot.indicator:
rom.write_byte(0x142A54, 0)
# patch flute spots
owFlags = 0
@@ -775,7 +847,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal
if world.doorShuffle[player] == 'crossed':
dr_flags |= DROptions.Map_Info
if world.experimental[player] and world.goal[player] not in ['triforcehunt', 'trinity']:
if world.collection_rate[player] and world.goal[player] not in ['triforcehunt', 'trinity']:
dr_flags |= DROptions.Debug
if world.doorShuffle[player] == 'crossed' and world.logic[player] != 'nologic'\
and world.mixed_travel[player] == 'prevent':
@@ -799,14 +871,20 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
if world.logic[player] != 'nologic':
dr_flags |= DROptions.Fix_EG
my_locations = world.get_filled_locations(player)
valid_locations = [l for l in my_locations if ((l.type == LocationType.Pot and not l.forced_item)
or (l.type == LocationType.Drop and not l.forced_item)
or (l.type == LocationType.Normal and not l.forced_item)
or (l.type == LocationType.Shop and world.shopsanity[player]))]
valid_loc_by_dungeon = valid_dungeon_locations(valid_locations)
# fix hc big key problems (map and compass too)
if world.doorShuffle[player] == 'crossed' or world.keydropshuffle[player]:
if world.doorShuffle[player] == 'crossed' or world.dropshuffle[player] or world.pottery[player] not in ['none', 'cave']:
rom.write_byte(0x151f1, 2)
rom.write_byte(0x15270, 2)
sanctuary = world.get_region('Sanctuary', player)
rom.write_byte(0x1597b, sanctuary.dungeon.dungeon_id*2)
update_compasses(rom, world, player)
update_compasses(rom, valid_loc_by_dungeon, world, player)
def should_be_bunny(region, mode):
if mode != 'inverted':
@@ -839,9 +917,12 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_byte(0x13f020+offset, layout.max_chests + layout.max_drops) # not currently used
rom.write_byte(0x13f030+offset, layout.max_chests)
builder = world.dungeon_layouts[player][name]
rom.write_byte(0x13f080+offset, builder.location_cnt % 10)
rom.write_byte(0x13f090+offset, builder.location_cnt // 10)
rom.write_byte(0x13f0a0+offset, builder.location_cnt)
valid_cnt = len(valid_loc_by_dungeon[name])
if valid_cnt > 99:
logging.getLogger('').warning(f'{name} exceeds 99 in locations ({valid_cnt})')
rom.write_byte(0x13f080+offset, valid_cnt % 10)
rom.write_byte(0x13f090+offset, valid_cnt // 10)
rom.write_byte(0x13f0a0+offset, valid_cnt)
bk_status = 1 if builder.bk_required else 0
bk_status = 2 if builder.bk_provided else bk_status
rom.write_byte(0x13f040+offset*2, bk_status)
@@ -924,33 +1005,53 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
# bot: $7A is 1, 7B is 2, etc so 7D=4, 82=9 (zero unknown...)
return 0x53+int(num), 0x79+int(num)
credits_total = 216
if world.keydropshuffle[player]:
credits_total += 33
if world.shopsanity[player]:
credits_total += 32
if world.retro[player]:
credits_total += 9 if world.shopsanity[player] else 5
credits_total = len(valid_locations)
if world.keydropshuffle[player]:
rom.write_byte(0x140000, 1)
multiClientFlags = ((0x1 if world.keydropshuffle[player] else 0) | (0x2 if world.shopsanity[player] else 0)
| (0x4 if world.retro[player] else 0))
rom.write_byte(0x140001, multiClientFlags)
if world.dropshuffle[player] or world.pottery[player] != 'none':
rom.write_byte(0x142A50, 1) # StandingItemsOn
multiClientFlags = ((0x1 if world.dropshuffle[player] else 0)
| (0x2 if world.shopsanity[player] else 0)
| (0x4 if world.retro[player] else 0)
| (0x8 if world.pottery[player] != 'none' else 0)
| (0x10 if is_mystery else 0))
rom.write_byte(0x142A51, multiClientFlags)
# StandingItemCounterMask
rom.write_byte(0x142A55, ((0x1 if world.pottery[player] not in ['none', 'cave'] else 0)
| (0x2 if world.dropshuffle[player] else 0)))
if world.pottery[player] not in ['none', 'keys']:
# Cuccos should not prevent kill rooms from opening
rom.write_byte(snes_to_pc(0x0DB457), 0x40)
rom.write_byte(snes_to_pc(0x28AA56), 0 if world.pottery[player] == 'none' else 1)
write_int16(rom, 0x187010, credits_total) # dynamic credits
if credits_total != 216:
# collection rate address (hi):
cr_address = 0x238057
cr_pc = cr_address - 0x120000 # convert to pc
cr_address = 0x238055
cr_pc = snes_to_pc(cr_address) # convert to pc
first_top, first_bot = credits_digit((credits_total // 100) % 10)
mid_top, mid_bot = credits_digit((credits_total // 10) % 10)
last_top, last_bot = credits_digit(credits_total % 10)
if credits_total >= 1000:
thousands_top, thousands_bot = credits_digit((credits_total // 1000) % 10)
rom.write_byte(cr_pc, 0xa2) # slash
rom.write_byte(cr_pc+1, thousands_top)
rom.write_byte(cr_pc+0x1e, 0xc2) # slash
rom.write_byte(cr_pc+0x1f, thousands_bot)
# modify stat config
stat_address = 0x23B969
stat_pc = snes_to_pc(stat_address)
rom.write_byte(stat_pc, 0xa9) # change to pos 21 (from b1)
rom.write_byte(stat_pc+2, 0xc0) # change to 12 bits (from a0)
rom.write_byte(stat_pc+3, 0x80) # change to four digits (from 60)
# top half
rom.write_byte(cr_pc+0x1, mid_top)
rom.write_byte(cr_pc+0x2, last_top)
rom.write_byte(cr_pc+2, first_top)
rom.write_byte(cr_pc+3, mid_top)
rom.write_byte(cr_pc+4, last_top)
# bottom half
rom.write_byte(cr_pc+0x1f, mid_bot)
rom.write_byte(cr_pc+0x20, last_bot)
rom.write_byte(cr_pc+0x20, first_bot)
rom.write_byte(cr_pc+0x21, mid_bot)
rom.write_byte(cr_pc+0x22, last_bot)
# patch medallion requirements
if world.required_medallions[player][0] == 'Bombos':
@@ -976,9 +1077,9 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
# set open mode:
if world.mode[player] in ['open', 'inverted']:
rom.write_byte(0x180032, 0x01) # open mode
init_open_mode_sram(rom)
elif world.mode[player] == 'standard':
rom.write_byte(0x180032, 0x00) # standard mode
init_standard_mode_sram(rom)
set_inverted_mode(world, player, rom, inverted_buffer)
@@ -1205,10 +1306,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
# set swordless mode settings
rom.write_byte(0x18003F, 0x01 if world.swords[player] == 'swordless' else 0x00) # hammer can harm ganon
rom.write_byte(0x180040, 0x01 if world.swords[player] == 'swordless' else 0x00) # open curtains
rom.write_byte(0x180041, 0x01 if world.swords[player] == 'swordless' else 0x00) # swordless medallions
rom.write_byte(0x180043, 0xFF if world.swords[player] == 'swordless' else 0x00) # starting sword for link
rom.write_byte(0x180044, 0x01 if world.swords[player] == 'swordless' else 0x00) # hammer activates tablets
if world.swords[player] == 'swordless':
rom.initial_sram.set_swordless_curtains() # open curtains
# set up clocks for timed modes
if world.shuffle[player] == 'vanilla':
@@ -1224,45 +1325,46 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
write_int32(rom, 0x180200, 0) # red clock adjustment time (in frames, sint32)
write_int32(rom, 0x180204, 0) # blue clock adjustment time (in frames, sint32)
write_int32(rom, 0x180208, 0) # green clock adjustment time (in frames, sint32)
write_int32(rom, 0x18020C, 0) # starting time (in frames, sint32)
rom.initial_sram.set_starting_timer(0) # starting time (in frames, sint32)
elif world.clock_mode == 'ohko':
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
write_int32(rom, 0x180200, 0) # red clock adjustment time (in frames, sint32)
write_int32(rom, 0x180204, 0) # blue clock adjustment time (in frames, sint32)
write_int32(rom, 0x180208, 0) # green clock adjustment time (in frames, sint32)
write_int32(rom, 0x18020C, 0) # starting time (in frames, sint32)
rom.initial_sram.set_starting_timer(0) # starting time (in frames, sint32)
elif world.clock_mode == 'countdown-ohko':
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
write_int32(rom, 0x180200, -100 * 60 * 60 * 60) # red clock adjustment time (in frames, sint32)
write_int32(rom, 0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
write_int32(rom, 0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
if world.difficulty_adjustments[player] == 'normal':
write_int32(rom, 0x18020C, (10 + ERtimeincrease) * 60 * 60) # starting time (in frames, sint32)
if world.difficulty_adjustments == 'normal':
rom.initial_sram.set_starting_timer((10 + ERtimeincrease) * 60) # starting time (in seconds)
else:
write_int32(rom, 0x18020C, int((5 + ERtimeincrease / 2) * 60 * 60)) # starting time (in frames, sint32)
rom.initial_sram.set_starting_timer(int((5 + ERtimeincrease / 2) * 60)) # starting time (in seconds)
if world.clock_mode == 'stopwatch':
rom.write_bytes(0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode
write_int32(rom, 0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32)
write_int32(rom, 0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
write_int32(rom, 0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
write_int32(rom, 0x18020C, 0) # starting time (in frames, sint32)
rom.initial_sram.set_starting_timer(0) # starting time (in frames, sint32)
if world.clock_mode == 'countdown':
rom.write_bytes(0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available
write_int32(rom, 0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32)
write_int32(rom, 0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
write_int32(rom, 0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
write_int32(rom, 0x18020C, (40 + ERtimeincrease) * 60 * 60) # starting time (in frames, sint32)
rom.initial_sram.set_starting_timer((40 + ERtimeincrease) * 60) # starting time (in seconds)
# set up goals for treasure hunt
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28])
if world.goal[player] in ['triforcehunt', 'trinity']:
rom.write_byte(0x180167, int(world.treasure_hunt_count[player]) % 256)
rom.write_bytes(0x180167, int16_as_bytes(world.treasure_hunt_count[player]))
rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled)
rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed
gametype = 0x04 # item
if world.shuffle[player] != 'vanilla' or world.doorShuffle[player] != 'vanilla' or world.keydropshuffle[player]:
if (world.shuffle[player] != 'vanilla' or world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player]
or world.pottery[player] != 'none'):
gametype |= 0x02 # entrance/door
if enemized:
gametype |= 0x01 # enemizer
@@ -1282,14 +1384,16 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted
rom.write_byte(0x180171, 0x01 if world.ganon_at_pyramid[player] else 0x00) # Enable respawning on pyramid after ganon death
rom.write_byte(0x180173, 0x01) # Bob is enabled
rom.write_byte(0x180168, 0x08) # Spike Cave Damage
rom.write_byte(0x180195, 0x08) # Spike Cave Damage
rom.write_bytes(0x18016B, [0x04, 0x02, 0x01]) # Set spike cave and MM spike room Byrna usage
rom.write_bytes(0x18016E, [0x04, 0x08, 0x10]) # Set spike cave and MM spike room Cape usage
rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest
rom.write_byte(0x50599, 0x00) # disable below ganon chest
rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest
rom.write_byte(0x18008B, 0x01 if world.is_pyramid_open(player) else 0x00) # pre-open Pyramid Hole
rom.write_byte(0x18008C, 0x01 if world.crystals_needed_for_gt[player] == 0 else 0x00) # GT pre-opened if crystal requirement is 0
if world.open_pyramid[player]:
rom.initial_sram.pre_open_pyramid_hole()
if world.crystals_needed_for_gt[player] == 0:
rom.initial_sram.pre_open_ganons_tower()
rom.write_byte(0x18008F, 0x01 if world.is_atgt_swapped(player) else 0x00) # AT/GT swapped
rom.write_byte(0xF5D73, 0xF0) # bees are catchable
rom.write_byte(0xF5F10, 0xF0) # bees are catchable
@@ -1306,162 +1410,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
# Starting equipment
if world.pseudoboots[player]:
rom.write_byte(0x18008E, 0x01)
rom.initial_sram.set_starting_equipment(world, player)
rom.write_byte(0x180034, 10 if not world.bombbag[player] else 0) # starting max bombs
rom.write_byte(0x180035, 30) # starting max arrows
equip = [0] * (0x340 + 0x4F)
equip[0x36C] = 0x18
equip[0x36D] = 0x18
equip[0x379] = 0x68
if world.bombbag[player]:
starting_max_bombs = 0
else:
starting_max_bombs = 10
starting_max_arrows = 30
startingstate = CollectionState(world)
if startingstate.has('Bow', player):
equip[0x340] = 3 if startingstate.has('Silver Arrows', player) else 1
equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases
if not world.retro[player]:
equip[0x38E] |= 0x80
if startingstate.has('Silver Arrows', player):
equip[0x38E] |= 0x40
if startingstate.has('Titans Mitts', player):
equip[0x354] = 2
elif startingstate.has('Power Glove', player):
equip[0x354] = 1
if startingstate.has('Golden Sword', player):
equip[0x359] = 4
elif startingstate.has('Tempered Sword', player):
equip[0x359] = 3
elif startingstate.has('Master Sword', player):
equip[0x359] = 2
elif startingstate.has('Fighter Sword', player):
equip[0x359] = 1
if startingstate.has('Mirror Shield', player):
equip[0x35A] = 3
elif startingstate.has('Red Shield', player):
equip[0x35A] = 2
elif startingstate.has('Blue Shield', player):
equip[0x35A] = 1
if startingstate.has('Red Mail', player):
equip[0x35B] = 2
elif startingstate.has('Blue Mail', player):
equip[0x35B] = 1
if startingstate.has('Magic Upgrade (1/4)', player):
equip[0x37B] = 2
equip[0x36E] = 0x80
elif startingstate.has('Magic Upgrade (1/2)', player):
equip[0x37B] = 1
equip[0x36E] = 0x80
for item in world.precollected_items:
if item.player != player:
continue
if item.name in ['Bow', 'Silver Arrows', 'Progressive Bow', 'Progressive Bow (Alt)',
'Titans Mitts', 'Power Glove', 'Progressive Glove',
'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword',
'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield',
'Red Mail', 'Blue Mail', 'Progressive Armor',
'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']:
continue
set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2),
'Cape': (0x352, 1), 'Lamp': (0x34A, 1), 'Moon Pearl': (0x357, 1), 'Cane of Somaria': (0x350, 1), 'Cane of Byrna': (0x351, 1),
'Fire Rod': (0x345, 1), 'Ice Rod': (0x346, 1), 'Bombos': (0x347, 1), 'Ether': (0x348, 1), 'Quake': (0x349, 1)}
or_table = {'Green Pendant': (0x374, 0x04), 'Red Pendant': (0x374, 0x01), 'Blue Pendant': (0x374, 0x02),
'Crystal 1': (0x37A, 0x02), 'Crystal 2': (0x37A, 0x10), 'Crystal 3': (0x37A, 0x40), 'Crystal 4': (0x37A, 0x20),
'Crystal 5': (0x37A, 0x04), 'Crystal 6': (0x37A, 0x01), 'Crystal 7': (0x37A, 0x08),
'Big Key (Eastern Palace)': (0x367, 0x20), 'Compass (Eastern Palace)': (0x365, 0x20), 'Map (Eastern Palace)': (0x369, 0x20),
'Big Key (Desert Palace)': (0x367, 0x10), 'Compass (Desert Palace)': (0x365, 0x10), 'Map (Desert Palace)': (0x369, 0x10),
'Big Key (Tower of Hera)': (0x366, 0x20), 'Compass (Tower of Hera)': (0x364, 0x20), 'Map (Tower of Hera)': (0x368, 0x20),
'Big Key (Escape)': (0x367, 0xC0), 'Compass (Escape)': (0x365, 0xC0), 'Map (Escape)': (0x369, 0xC0),
'Big Key (Agahnims Tower)': (0x367, 0x08), 'Compass (Agahnims Tower)': (0x365, 0x08), 'Map (Agahnims Tower)': (0x369, 0x08),
'Big Key (Palace of Darkness)': (0x367, 0x02), 'Compass (Palace of Darkness)': (0x365, 0x02), 'Map (Palace of Darkness)': (0x369, 0x02),
'Big Key (Thieves Town)': (0x366, 0x10), 'Compass (Thieves Town)': (0x364, 0x10), 'Map (Thieves Town)': (0x368, 0x10),
'Big Key (Skull Woods)': (0x366, 0x80), 'Compass (Skull Woods)': (0x364, 0x80), 'Map (Skull Woods)': (0x368, 0x80),
'Big Key (Swamp Palace)': (0x367, 0x04), 'Compass (Swamp Palace)': (0x365, 0x04), 'Map (Swamp Palace)': (0x369, 0x04),
'Big Key (Ice Palace)': (0x366, 0x40), 'Compass (Ice Palace)': (0x364, 0x40), 'Map (Ice Palace)': (0x368, 0x40),
'Big Key (Misery Mire)': (0x367, 0x01), 'Compass (Misery Mire)': (0x365, 0x01), 'Map (Misery Mire)': (0x369, 0x01),
'Big Key (Turtle Rock)': (0x366, 0x08), 'Compass (Turtle Rock)': (0x364, 0x08), 'Map (Turtle Rock)': (0x368, 0x08),
'Big Key (Ganons Tower)': (0x366, 0x04), 'Compass (Ganons Tower)': (0x364, 0x04), 'Map (Ganons Tower)': (0x368, 0x04)}
set_or_table = {'Flippers': (0x356, 1, 0x379, 0x02),'Pegasus Boots': (0x355, 1, 0x379, 0x04),
'Shovel': (0x34C, 1, 0x38C, 0x04), 'Ocarina': (0x34C, 3, 0x38C, 0x01), 'Ocarina (Activated)': (0x34C, 3, 0x38C, 0x01),
'Mushroom': (0x344, 1, 0x38C, 0x20 | 0x08), 'Magic Powder': (0x344, 2, 0x38C, 0x10),
'Blue Boomerang': (0x341, 1, 0x38C, 0x80), 'Red Boomerang': (0x341, 2, 0x38C, 0x40)}
keys = {'Small Key (Eastern Palace)': [0x37E], 'Small Key (Desert Palace)': [0x37F],
'Small Key (Tower of Hera)': [0x386],
'Small Key (Agahnims Tower)': [0x380], 'Small Key (Palace of Darkness)': [0x382],
'Small Key (Thieves Town)': [0x387],
'Small Key (Skull Woods)': [0x384], 'Small Key (Swamp Palace)': [0x381],
'Small Key (Ice Palace)': [0x385],
'Small Key (Misery Mire)': [0x383], 'Small Key (Turtle Rock)': [0x388],
'Small Key (Ganons Tower)': [0x389],
'Small Key (Universal)': [0x38B], 'Small Key (Escape)': [0x37C, 0x37D]}
bottles = {'Bottle': 2, 'Bottle (Red Potion)': 3, 'Bottle (Green Potion)': 4, 'Bottle (Blue Potion)': 5,
'Bottle (Fairy)': 6, 'Bottle (Bee)': 7, 'Bottle (Good Bee)': 8}
rupees = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)': 50, 'Rupees (100)': 100, 'Rupees (300)': 300}
bomb_caps = {'Bomb Upgrade (+5)': 5, 'Bomb Upgrade (+10)': 10}
arrow_caps = {'Arrow Upgrade (+5)': 5, 'Arrow Upgrade (+10)': 10}
bombs = {'Single Bomb': 1, 'Bombs (3)': 3, 'Bombs (10)': 10}
arrows = {'Single Arrow': 1, 'Arrows (10)': 10}
if item.name in set_table:
equip[set_table[item.name][0]] = set_table[item.name][1]
elif item.name in or_table:
equip[or_table[item.name][0]] |= or_table[item.name][1]
elif item.name in set_or_table:
equip[set_or_table[item.name][0]] = set_or_table[item.name][1]
equip[set_or_table[item.name][2]] |= set_or_table[item.name][3]
elif item.name in keys:
for address in keys[item.name]:
equip[address] = min(equip[address] + 1, 99)
elif item.name in bottles:
if equip[0x34F] < world.difficulty_requirements[player].progressive_bottle_limit:
equip[0x35C + equip[0x34F]] = bottles[item.name]
equip[0x34F] += 1
elif item.name in rupees:
equip[0x360:0x362] = list(min(equip[0x360] + (equip[0x361] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False))
equip[0x362:0x364] = list(min(equip[0x362] + (equip[0x363] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False))
elif item.name in bomb_caps:
starting_max_bombs = min(starting_max_bombs + bomb_caps[item.name], 50)
elif item.name in arrow_caps:
starting_max_arrows = min(starting_max_arrows + arrow_caps[item.name], 70)
elif item.name in bombs:
equip[0x343] += bombs[item.name]
elif item.name in arrows:
if world.retro[player]:
equip[0x38E] |= 0x80
equip[0x377] = 1
else:
equip[0x377] += arrows[item.name]
elif item.name in ['Piece of Heart', 'Boss Heart Container', 'Sanctuary Heart Container']:
if item.name == 'Piece of Heart':
equip[0x36B] = (equip[0x36B] + 1) % 4
if item.name != 'Piece of Heart' or equip[0x36B] == 0:
equip[0x36C] = min(equip[0x36C] + 0x08, 0xA0)
equip[0x36D] = min(equip[0x36D] + 0x08, 0xA0)
else:
raise RuntimeError(f'Unsupported item in starting equipment: {item.name}')
equip[0x343] = min(equip[0x343], starting_max_bombs)
rom.write_byte(0x180034, starting_max_bombs)
equip[0x377] = min(equip[0x377], starting_max_arrows)
rom.write_byte(0x180035, starting_max_arrows)
rom.write_bytes(0x180046, equip[0x360:0x362])
if equip[0x359]:
rom.write_byte(0x180043, equip[0x359])
assert equip[:0x340] == [0] * 0x340
rom.write_bytes(0x183340, equip[0x340:])
rom.write_bytes(0x271A6, equip[0x340:0x340+60])
rom.write_byte(0x18004A, 0x00 if world.mode[player] != 'inverted' else 0x01) # Inverted mode
rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier
rom.write_byte(0x03A943, 0xD0 if world.mode[player] != 'inverted' else 0xF0) # Mirror: Normal (D0=Dark to Light, F0=light to dark, 42 = both)
@@ -1523,7 +1475,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_byte(0x18003C, 0x00)
elif world.dungeon_counters[player] == 'on':
compass_mode = 0x02 # always on
elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dungeon_counters[player] == 'pickup':
elif (world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player]
or world.dungeon_counters[player] == 'pickup' or world.pottery[player] not in ['none', 'cave']):
compass_mode = 0x01 # show on pickup
if (world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default') \
or (world.owMixed[player] and not (world.shuffle[player] != 'vanilla' and world.overworld_map[player] == 'default')):
@@ -1574,25 +1527,27 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_bytes(0x53E36+ow_map_index*2, int16_as_bytes(coords[0]))
rom.write_bytes(0x53E56+ow_map_index*2, int16_as_bytes(coords[1]))
rom.write_byte(0x53EA6+ow_map_index, world_indicator)
# in crossed doors - flip the compass exists flags
if world.doorShuffle[player] == 'crossed':
exists_flag = any(x for x in world.get_dungeon(dungeon, player).dungeon_items if x.type == 'Compass')
rom.write_byte(0x53E96+ow_map_index, 0x1 if exists_flag else 0x0)
# in crossed doors - flip the compass exists flags
if world.doorShuffle[player] == 'crossed':
for dungeon, portal_list in dungeon_portals.items():
ow_map_index = dungeon_table[dungeon].map_index
exists_flag = any(x for x in world.get_dungeon(dungeon, player).dungeon_items if x.type == 'Compass')
rom.write_byte(0x53E96+ow_map_index, 0x1 if exists_flag else 0x0)
rom.write_byte(0x18003C, compass_mode)
# Bitfield - enable free items to show up in menu
#
# ----dcba
# d - Compass
# c - Map
# b - Big Key
# a - Small Key
#
# Bitfield - enable free items to show up in menu
#
# ----dcba
# d - Compass
# c - Map
# b - Big Key
# a - Small Key
#
enable_menu_map_check = world.overworld_map[player] != 'default' and world.shuffle[player] != 'none'
rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] else 0x00)
| (0x02 if world.bigkeyshuffle[player] else 0x00)
| (0x04 if world.mapshuffle[player] else 0x00)
| (0x04 if world.mapshuffle[player] or enable_menu_map_check else 0x00)
| (0x08 if world.compassshuffle[player] else 0x00))) # free roaming items in menu
# Map reveals
@@ -1720,13 +1675,17 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_byte(0xFED31, 0x0E) # preopen bombable exit
rom.write_byte(0xFEE41, 0x0E) # preopen bombable exit
if world.doorShuffle[player] != 'vanilla' or world.keydropshuffle[player]:
if (world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player]
or world.pottery[player] != 'none'):
for room in world.rooms:
if room.player == player and room.modified:
rom.write_bytes(room.address(), room.rom_data())
write_strings(rom, world, player, team)
# write initial sram
rom.write_initial_sram()
rom.write_byte(0x18636C, 1 if world.remote_items[player] else 0)
# set rom name
@@ -1831,14 +1790,16 @@ def hud_format_text(text):
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite,
ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx):
ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx, msu_resume):
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],
rom.orig_buffer[0xdedf5:0xdedf9], "data/sprites/official/001.link.1.zspr", "Nintendo", "Link")
# todo: implement a flag for msu resume delay
rom.write_bytes(0x18021D, [0, 0]) # default to off for now
if msu_resume:
rom.write_bytes(0x18021D, [0x8, 0x7])
else:
rom.write_bytes(0x18021D, [0, 0]) # default to off for now
if sprite and not isinstance(sprite, Sprite):
sprite = Sprite(sprite) if os.path.isfile(sprite) else get_sprite_from_name(sprite)
@@ -2151,6 +2112,8 @@ def write_strings(rom, world, player, team):
else:
if isinstance(dest, Region) and dest.type == RegionType.Dungeon and dest.dungeon:
hint = dest.dungeon.name
elif isinstance(dest, Item) and world.experimental[player]:
hint = f'{{C:RED}}{dest.hint_text}{{C:WHITE}}' if dest.hint_text else 'something'
else:
hint = dest.hint_text if dest.hint_text else "something"
if dest.player != player:
@@ -2170,7 +2133,7 @@ def write_strings(rom, world, player, team):
all_entrances = [entrance for entrance in world.get_entrances() if entrance.player == player]
random.shuffle(all_entrances)
#First we take care of the one inconvenient dungeon in the appropriately simple shuffles.
# First we take care of the one inconvenient dungeon in the appropriately simple shuffles.
entrances_to_hint = {}
entrances_to_hint.update(InconvenientDungeonEntrances)
if world.shuffle_ganon:
@@ -2185,7 +2148,7 @@ def write_strings(rom, world, player, team):
tt[hint_locations.pop(0)] = this_hint
entrances_to_hint = {}
break
#Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones.
# Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones.
entrances_to_hint.update(InconvenientOtherEntrances)
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
hint_count = 0
@@ -2203,7 +2166,7 @@ def write_strings(rom, world, player, team):
else:
break
#Next we handle hints for randomly selected other entrances, curating the selection intelligently based on shuffle.
# Next we handle hints for randomly selected other entrances, curating the selection intelligently based on shuffle.
if world.shuffle[player] not in ['simple', 'restricted', 'restricted_legacy']:
entrances_to_hint.update(ConnectorEntrances)
entrances_to_hint.update(DungeonEntrances)
@@ -2262,7 +2225,7 @@ def write_strings(rom, world, player, team):
else:
second_item = hint_text(world.get_location('Swamp Palace - West Chest', player).item)
first_item = hint_text(world.get_location('Swamp Palace - Big Key Chest', player).item)
this_hint = ('The westmost chests in Swamp Palace contain ' + first_item + ' and ' + second_item + '.')
this_hint = f'The westmost chests in Swamp Palace contain {first_item} and {second_item}.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Mire Left':
if random.randint(0, 1) == 0:
@@ -2271,34 +2234,43 @@ def write_strings(rom, world, player, team):
else:
second_item = hint_text(world.get_location('Misery Mire - Compass Chest', player).item)
first_item = hint_text(world.get_location('Misery Mire - Big Key Chest', player).item)
this_hint = ('The westmost chests in Misery Mire contain ' + first_item + ' and ' + second_item + '.')
this_hint = f'The westmost chests in Misery Mire contain {first_item} and {second_item}.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Tower of Hera - Big Key Chest':
this_hint = 'Waiting in the Tower of Hera basement leads to ' + hint_text(world.get_location(location, player).item) + '.'
item = hint_text(world.get_location(location, player).item)
this_hint = f'Waiting in the Tower of Hera basement leads to {item}.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Ganons Tower - Big Chest':
this_hint = 'The big chest in Ganon\'s Tower contains ' + hint_text(world.get_location(location, player).item) + '.'
item = hint_text(world.get_location(location, player).item)
this_hint = f'The big chest in Ganon\'s Tower contains {item}.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Thieves\' Town - Big Chest':
this_hint = 'The big chest in Thieves\' Town contains ' + hint_text(world.get_location(location, player).item) + '.'
item = hint_text(world.get_location(location, player).item)
this_hint = f'The big chest in Thieves\' Town contains {item}.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Ice Palace - Big Chest':
this_hint = 'The big chest in Ice Palace contains ' + hint_text(world.get_location(location, player).item) + '.'
item = hint_text(world.get_location(location, player).item)
this_hint = f'The big chest in Ice Palace contains {item}.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Eastern Palace - Big Key Chest':
this_hint = 'The antifairy guarded chest in Eastern Palace contains ' + hint_text(world.get_location(location, player).item) + '.'
item = hint_text(world.get_location(location, player).item)
this_hint = f'The antifairy guarded chest in Eastern Palace contains {item}.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Sahasrahla':
this_hint = 'Sahasrahla seeks a green pendant for ' + hint_text(world.get_location(location, player).item) + '.'
item = hint_text(world.get_location(location, player).item)
this_hint = f'Sahasrahla seeks a green pendant for {item}.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Graveyard Cave':
this_hint = 'The cave north of the graveyard contains ' + hint_text(world.get_location(location, player).item) + '.'
item = hint_text(world.get_location(location, player).item)
this_hint = f'The cave north of the graveyard contains {item}.'
tt[hint_locations.pop(0)] = this_hint
else:
this_hint = location + ' contains ' + hint_text(world.get_location(location, player).item) + '.'
this_hint = f'{location} contains {hint_text(world.get_location(location, player).item)}.'
tt[hint_locations.pop(0)] = this_hint
# 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.
# 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()
flute_item = 'Ocarina'
if world.is_tile_swapped(0x18, player):
@@ -2326,8 +2298,10 @@ def write_strings(rom, world, player, team):
this_location = world.find_items_not_key_only(this_item, player)
random.shuffle(this_location)
if this_location:
this_hint = this_location[0].item.hint_text + ' can be found ' + hint_text(this_location[0]) + '.'
this_hint = this_hint[0].upper() + this_hint[1:]
item_name = this_location[0].item.hint_text
item_name = item_name[0].upper() + item_name[1:]
item_format = f'{{C:RED}}{item_name}{{C:WHITE}}' if world.experimental[player] else item_name
this_hint = f'{item_format} can be found {hint_text(this_location[0])}.'
tt[hint_locations.pop(0)] = this_hint
hint_count -= 1
@@ -2381,7 +2355,8 @@ def write_strings(rom, world, player, team):
elif hint_type == 'path':
if item_count == 1:
the_item = text_for_item(next(iter(choice_set)), world, player, team)
hint_candidates.append((hint_type, f'{name} conceals {the_item}'))
item_format = f'{{C:RED}}{the_item}{{C:WHITE}}' if world.experimental[player] else the_item
hint_candidates.append((hint_type, f'{name} conceals only {item_format}'))
else:
hint_candidates.append((hint_type, f'{name} conceals {item_count} {item_type} items'))
district_hints = min(len(hint_candidates), len(hint_locations))
@@ -2468,7 +2443,7 @@ def write_strings(rom, world, player, team):
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
tt['sign_ganon'] = 'You need to get to the pedestal... Ganon is invincible!'
else:
if world.goal[player] in ['trinity']:
if world.goal[player] == 'trinity':
trinity_crystal_text = ('%d crystal to beat Ganon.' if world.crystals_needed_for_ganon[player] == 1 else '%d crystals to beat Ganon.') % world.crystals_needed_for_ganon[player]
tt['sign_ganon'] = 'Three ways to victory! %s Get to it!' % trinity_crystal_text
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player])
@@ -2569,6 +2544,16 @@ def text_for_item(item, world, player, team):
else:
return f'{item.hint_text} for {world.player_names[item.player][team]}'
def init_open_mode_sram(rom):
rom.initial_sram.pre_open_castle_gate()
rom.initial_sram.set_progress_indicator(0x02)
rom.initial_sram.set_progress_flags(0x14)
rom.initial_sram.set_starting_entrance(0x01)
def init_standard_mode_sram(rom):
rom.initial_sram.set_progress_indicator(0x00)
rom.initial_sram.set_progress_flags(0x0)
rom.initial_sram.set_starting_entrance(0x00)
def set_inverted_mode(world, player, rom, inverted_buffer):
if world.mode[player] == 'inverted':
@@ -2806,12 +2791,23 @@ def patch_shuffled_bomb_shop(world, rom, player):
write_int16(rom, snes_to_pc(0x02D996), door_1)
def update_compasses(rom, world, player):
def valid_dungeon_locations(valid_locations):
dungeon_locations = collections.defaultdict(set)
for l in valid_locations:
if l.parent_region.dungeon:
dungeon_locations[l.parent_region.dungeon.name].add(l)
return dungeon_locations
def update_compasses(rom, dungeon_locations, world, player):
layouts = world.dungeon_layouts[player]
provided_dungeon = False
for name, builder in layouts.items():
dungeon_id = compass_data[name][4]
rom.write_byte(0x187000 + dungeon_id//2, builder.location_cnt)
dungeon_count = len(dungeon_locations[name])
if dungeon_count > 255:
logging.getLogger('').warning(f'{name} has more locations than 255. Need 16-bit compass counts')
rom.write_byte(0x187000 + dungeon_id//2, dungeon_count % 256)
if builder.bk_provided:
if provided_dungeon:
logging.getLogger('').warning('Multiple dungeons have forced BKs! Compass code might need updating?')
@@ -2929,7 +2925,7 @@ OtherEntrances = {'Lake Hylia Fairy': 'A cave NE of Lake Hylia',
'Kakariko Gamble Game': 'The old Kakariko gambling den',
'Bonk Fairy (Light)': 'The rock pile near your home',
'Hookshot Fairy': 'The left paired cave on east DM',
'Bonk Fairy (Dark)': 'The rock pile near the old bomb shop',
'Bonk Fairy (Dark)': 'The rock pile near the old bomb shop',
'Dark Lake Hylia Fairy': 'The cave NE dark Lake Hylia',
'Dark Death Mountain Fairy': 'The SW cave on dark DM',
'East Dark World Hint': 'The dark cave near the eastmost portal',
@@ -3085,29 +3081,3 @@ hash_alphabet = [
"Lamp", "Hammer", "Shovel", "Ocarina", "Bug Net", "Book", "Bottle", "Potion", "Cane", "Cape", "Mirror", "Boots",
"Gloves", "Flippers", "Pearl", "Shield", "Tunic", "Heart", "Map", "Compass", "Key"
]
pot_item_room_table_lookup = 0xDB67
###
# Pointer to pot location and contents for each non-empty pot in a supertile
# Format: [(x, y, item)] FF FF (Note: x,y are bit packed to include layer)
pot_item_table = 0xDDE7
pot_item_table_end = 0xE6B0
def write_pots_to_rom(rom, pot_contents):
n = pot_item_table
rom.write_bytes(n, [0xFF,0xFF])
n += 2
for i in range(0x140):
if i in 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, 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:
write_int16(rom, pot_item_room_table_lookup + 2*i, n-2)
else:
write_int16(rom, pot_item_room_table_lookup + 2*i, n-2)
assert n <= pot_item_table_end