Merged in DR v1.0.1.0
This commit is contained in:
530
Rom.py
530
Rom.py
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user