Merged in DR v1.4.1.11

This commit is contained in:
codemann8
2024-04-25 15:06:59 -05:00
20 changed files with 327 additions and 213 deletions

View File

@@ -20,7 +20,7 @@ from source.dungeon.RoomObject import RoomObject
class World(object): class World(object):
def __init__(self, players, owShuffle, owCrossed, owMixed, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments, def __init__(self, players, owShuffle, owCrossed, owMixed, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments,
timer, progressive, goal, algorithm, accessibility, shuffle_ganon, custom, customitemarray, hints): timer, progressive, goal, algorithm, accessibility, shuffle_ganon, custom, customitemarray, hints, spoiler_mode):
self.players = players self.players = players
self.teams = 1 self.teams = 1
self.owShuffle = owShuffle.copy() self.owShuffle = owShuffle.copy()
@@ -78,6 +78,7 @@ class World(object):
self.prizes = {} self.prizes = {}
self.dynamic_regions = [] self.dynamic_regions = []
self.dynamic_locations = [] self.dynamic_locations = []
self.spoiler_mode = spoiler_mode
self.spoiler = Spoiler(self) self.spoiler = Spoiler(self)
self.lamps_needed_for_dark_rooms = 1 self.lamps_needed_for_dark_rooms = 1
self.owedges = [] self.owedges = []
@@ -106,6 +107,7 @@ class World(object):
self.data_tables = {} self.data_tables = {}
self.damage_table = {} self.damage_table = {}
for player in range(1, players + 1): for player in range(1, players + 1):
def set_player_attr(attr, val): def set_player_attr(attr, val):
self.__dict__.setdefault(attr, {})[player] = val self.__dict__.setdefault(attr, {})[player] = val
@@ -2869,6 +2871,17 @@ class Spoiler(object):
self.metadata = {} self.metadata = {}
self.shops = [] self.shops = []
self.bosses = OrderedDict() self.bosses = OrderedDict()
if world.spoiler_mode == 'settings':
self.settings = {'settings'}
elif world.spoiler_mode == 'semi':
self.settings = {'settings', 'entrances', 'requirements', 'prizes'}
elif world.spoiler_mode == 'full':
self.settings = {'all'}
elif world.spoiler_mode == 'debug':
self.settings = {'all', 'debug'}
else:
self.settings = {}
self.suppress_spoiler_locations = ['Big Bomb', 'Frog', 'Dark Blacksmith Ruins', 'Middle Aged Man', 'Lost Old Man', 'Old Man Drop Off'] self.suppress_spoiler_locations = ['Big Bomb', 'Frog', 'Dark Blacksmith Ruins', 'Middle Aged Man', 'Lost Old Man', 'Old Man Drop Off']
@@ -3012,27 +3025,29 @@ class Spoiler(object):
self.bottles[f'Waterfall Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][0] self.bottles[f'Waterfall Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][0]
self.bottles[f'Pyramid Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][1] self.bottles[f'Pyramid Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][1]
def include_item(item):
return 'all' in self.settings or ('items' in self.settings and not item.crystal) or ('prizes' in self.settings and item.crystal)
self.locations = OrderedDict() self.locations = OrderedDict()
listed_locations = set() listed_locations = set()
lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld and not loc.skip] lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld and not loc.skip and include_item(loc.item)]
self.locations['Light World'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in lw_locations]) self.locations['Light World'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in lw_locations])
listed_locations.update(lw_locations) listed_locations.update(lw_locations)
dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld and not loc.skip] dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld and not loc.skip and include_item(loc.item)]
self.locations['Dark World'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dw_locations]) self.locations['Dark World'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dw_locations])
listed_locations.update(dw_locations) listed_locations.update(dw_locations)
cave_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.Cave and not loc.skip] cave_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.Cave and not loc.skip and include_item(loc.item)]
self.locations['Caves'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in cave_locations]) self.locations['Caves'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in cave_locations])
listed_locations.update(cave_locations) listed_locations.update(cave_locations)
for dungeon in self.world.dungeons: for dungeon in self.world.dungeons:
dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and not loc.forced_item and not loc.skip] dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and not loc.forced_item and not loc.skip and include_item(loc.item)]
self.locations[str(dungeon)] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations]) self.locations[str(dungeon)] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations])
listed_locations.update(dungeon_locations) listed_locations.update(dungeon_locations)
other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and not loc.skip] other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and not loc.skip and include_item(loc.item)]
if other_locations: if other_locations:
self.locations['Other Locations'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in other_locations]) self.locations['Other Locations'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in other_locations])
listed_locations.update(other_locations) listed_locations.update(other_locations)
@@ -3132,14 +3147,17 @@ class Spoiler(object):
outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v) outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v)
if self.metadata['user_notes']: if self.metadata['user_notes']:
outfile.write('User Notes:'.ljust(line_width) + '%s\n' % self.metadata['user_notes']) outfile.write('User Notes:'.ljust(line_width) + '%s\n' % self.metadata['user_notes'])
if 'all' in self.settings or 'settings' in self.settings:
outfile.write('Filling Algorithm:'.ljust(line_width) + '%s\n' % self.world.algorithm) outfile.write('Filling Algorithm:'.ljust(line_width) + '%s\n' % self.world.algorithm)
outfile.write('Players:'.ljust(line_width) + '%d\n' % self.world.players) outfile.write('Players:'.ljust(line_width) + '%d\n' % self.world.players)
outfile.write('Teams:'.ljust(line_width) + '%d\n' % self.world.teams) outfile.write('Teams:'.ljust(line_width) + '%d\n' % self.world.teams)
for player in range(1, self.world.players + 1): for player in range(1, self.world.players + 1):
if self.world.players > 1: if self.world.players > 1:
outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_names(player))) outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_names(player)))
if 'all' in self.settings or 'settings' in self.settings:
outfile.write('Settings Code:'.ljust(line_width) + '%s\n' % self.metadata["code"][player]) outfile.write('Settings Code:'.ljust(line_width) + '%s\n' % self.metadata["code"][player])
outfile.write('\n') outfile.write('\n')
if 'all' in self.settings or 'settings' in self.settings:
outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player]) outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player])
outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player]) outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player])
outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player]) outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player])
@@ -3247,6 +3265,7 @@ class Spoiler(object):
self.parse_data() self.parse_data()
with open(filename, 'a') as outfile: with open(filename, 'a') as outfile:
line_width = 35 line_width = 35
if 'all' in self.settings or 'requirements' in self.settings:
outfile.write('\n\nRequirements:\n\n') outfile.write('\n\nRequirements:\n\n')
for dungeon, medallion in self.medallions.items(): for dungeon, medallion in self.medallions.items():
outfile.write(f'{dungeon}:'.ljust(line_width) + '%s Medallion\n' % medallion) outfile.write(f'{dungeon}:'.ljust(line_width) + '%s Medallion\n' % medallion)
@@ -3257,11 +3276,13 @@ class Spoiler(object):
if self.world.crystals_ganon_orig[player] == 'random': if self.world.crystals_ganon_orig[player] == 'random':
outfile.write(str('Crystals Required for Ganon' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['ganon_crystals'][player]))) outfile.write(str('Crystals Required for Ganon' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['ganon_crystals'][player])))
if 'all' in self.settings or 'misc' in self.settings:
outfile.write('\n\nBottle Refills:\n\n') outfile.write('\n\nBottle Refills:\n\n')
for fairy, bottle in self.bottles.items(): for fairy, bottle in self.bottles.items():
outfile.write(f'{fairy}: {bottle}\n') outfile.write(f'{fairy}: {bottle}\n')
if self.maps: if self.maps:
if 'all' in self.settings or 'flute' in self.settings:
# flute shuffle # flute shuffle
for player in range(1, self.world.players + 1): for player in range(1, self.world.players + 1):
if ('flute', player) in self.maps: if ('flute', player) in self.maps:
@@ -3273,6 +3294,7 @@ class Spoiler(object):
outfile.write(str('(Player ' + str(player) + ')\n')) # player name outfile.write(str('(Player ' + str(player) + ')\n')) # player name
outfile.write(self.maps[('flute', player)]['text']) outfile.write(self.maps[('flute', player)]['text'])
if 'all' in self.settings or 'overworld' in self.settings:
# overworld tile flips # overworld tile flips
for player in range(1, self.world.players + 1): for player in range(1, self.world.players + 1):
if ('swaps', player) in self.maps: if ('swaps', player) in self.maps:
@@ -3295,22 +3317,22 @@ class Spoiler(object):
outfile.write(str('(Player ' + str(player) + ')\n')) # player name outfile.write(str('(Player ' + str(player) + ')\n')) # player name
outfile.write(self.maps[('groups', player)]['text']) outfile.write(self.maps[('groups', player)]['text'])
if self.overworlds: if self.overworlds and ('all' in self.settings or 'overworld' in self.settings):
outfile.write('\n\nOverworld Edges:\n\n') outfile.write('\n\nOverworld Edges:\n\n')
# overworld transitions # overworld transitions
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","overworlds",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","overworlds",entry['exit'])) for entry in self.overworlds.values()])) outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","overworlds",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","overworlds",entry['exit'])) for entry in self.overworlds.values()]))
if self.whirlpools: if self.whirlpools and ('all' in self.settings or 'overworld' in self.settings):
outfile.write('\n\nWhirlpools:\n\n') outfile.write('\n\nWhirlpools:\n\n')
# whirlpools # whirlpools
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","whirlpools",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","whirlpools",entry['exit'])) for entry in self.whirlpools.values()])) outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","whirlpools",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","whirlpools",entry['exit'])) for entry in self.whirlpools.values()]))
if self.entrances: if self.entrances and ('all' in self.settings or 'entrances' in self.settings):
# entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly # entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly
outfile.write('\n\nEntrances:\n\n') outfile.write('\n\nEntrances:\n\n')
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta", "entrances", entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta", "entrances", entry['exit'])) for entry in self.entrances.values()])) outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta", "entrances", entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta", "entrances", entry['exit'])) for entry in self.entrances.values()]))
if self.doors: if self.doors and ('all' in self.settings or 'doors' in self.settings):
outfile.write('\n\nDoors:\n\n') outfile.write('\n\nDoors:\n\n')
outfile.write('\n'.join( outfile.write('\n'.join(
['%s%s %s %s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', ['%s%s %s %s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '',
@@ -3319,28 +3341,32 @@ class Spoiler(object):
self.world.fish.translate("meta", "doors", entry['exit']), self.world.fish.translate("meta", "doors", entry['exit']),
'({0})'.format(entry['dname']) if self.world.doorShuffle[entry['player']] != 'basic' else '') for '({0})'.format(entry['dname']) if self.world.doorShuffle[entry['player']] != 'basic' else '') for
entry in self.doors.values()])) entry in self.doors.values()]))
if self.lobbies: if self.lobbies and ('all' in self.settings or 'doors' in self.settings):
outfile.write('\n\nDungeon Lobbies:\n\n') outfile.write('\n\nDungeon Lobbies:\n\n')
outfile.write('\n'.join( outfile.write('\n'.join(
[f"{'Player {0}: '.format(entry['player']) if self.world.players > 1 else ''}{entry['lobby_name']}: {entry['door_name']}" [f"{'Player {0}: '.format(entry['player']) if self.world.players > 1 else ''}{entry['lobby_name']}: {entry['door_name']}"
for for
entry in self.lobbies.values()])) entry in self.lobbies.values()]))
if self.doorTypes: if self.doorTypes and ('all' in self.settings or 'doors' in self.settings):
# doorNames: For some reason these come in combined, somehow need to split on the thing to translate # doorNames: For some reason these come in combined, somehow need to split on the thing to translate
# doorTypes: Small Key, Bombable, Bonkable # doorTypes: Small Key, Bombable, Bonkable
outfile.write('\n\nDoor Types:\n\n') outfile.write('\n\nDoor Types:\n\n')
outfile.write('\n'.join(['%s%s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', self.world.fish.translate("meta", "doors", entry['doorNames']), self.world.fish.translate("meta", "doorTypes", entry['type'])) for entry in self.doorTypes.values()])) outfile.write('\n'.join(['%s%s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', self.world.fish.translate("meta", "doors", entry['doorNames']), self.world.fish.translate("meta", "doorTypes", entry['type'])) for entry in self.doorTypes.values()]))
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name # locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
# items: Item names # items: Item names
outfile.write('\n\nLocations:\n\n') outfile.write('\n\nLocations:\n\n')
outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta", "locations", location), self.world.fish.translate("meta", "items", item)) for grouping in self.locations.values() for (location, item) in grouping.items()])) outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta", "locations", location), self.world.fish.translate("meta", "items", item))
for grouping in self.locations.values() for (location, item) in grouping.items()]))
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name # locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
# items: Item names # items: Item names
if 'all' in self.settings or 'shops' in self.settings:
outfile.write('\n\nShops:\n\n') outfile.write('\n\nShops:\n\n')
outfile.write('\n'.join("{} [{}]\n {}".format(self.world.fish.translate("meta", "locations", shop['location']), shop['type'], "\n ".join(self.world.fish.translate("meta", "items", item) for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops)) outfile.write('\n'.join("{} [{}]\n {}".format(self.world.fish.translate("meta", "locations", shop['location']), shop['type'], "\n ".join(self.world.fish.translate("meta", "items", item) for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops))
if 'all' in self.settings or 'bosses' in self.settings:
for player in range(1, self.world.players + 1): for player in range(1, self.world.players + 1):
if self.world.boss_shuffle[player] != 'none': if self.world.boss_shuffle[player] != 'none':
bossmap = self.bosses[str(player)] if self.world.players > 1 else self.bosses bossmap = self.bosses[str(player)] if self.world.players > 1 else self.bosses

3
CLI.py
View File

@@ -267,10 +267,9 @@ def parse_settings():
"msu_resume": False, "msu_resume": False,
"collection_rate": False, "collection_rate": False,
# Spoiler defaults to TRUE 'spoiler': 'full',
# Playthrough defaults to TRUE # Playthrough defaults to TRUE
# ROM defaults to TRUE # ROM defaults to TRUE
"create_spoiler": True,
"calc_playthrough": True, "calc_playthrough": True,
"create_rom": True, "create_rom": True,
"bps": False, "bps": False,

View File

@@ -1115,7 +1115,7 @@ def set_prize_drops(world, player):
for player, drop_config in drops.items(): for player, drop_config in drops.items():
for pack_num in range(1, 8): for pack_num in range(1, 8):
if f'Pack {pack_num}' in drop_config: if f'Pack {pack_num}' in drop_config:
for prize, idx in enumerate(drop_config[f'Pack {pack_num}']): for idx, prize in enumerate(drop_config[f'Pack {pack_num}']):
chosen = random.choice(uniform_prizes) if prize == 'Random' else possible_prizes[prize] chosen = random.choice(uniform_prizes) if prize == 'Random' else possible_prizes[prize]
prizes[(pack_num-1)*8 + idx] = chosen prizes[(pack_num-1)*8 + idx] = chosen
for tree_pull_tier in range(1, 4): for tree_pull_tier in range(1, 4):

2
Gui.py
View File

@@ -44,7 +44,7 @@ def save_settings(gui, args, filename):
if not os.path.exists(settings_path): if not os.path.exists(settings_path):
os.makedirs(settings_path) os.makedirs(settings_path)
output_args = {} output_args = {}
settings = ["create_rom", "suppress_rom", "bps", "create_spoiler", "suppress_spoiler", settings = ["create_rom", "suppress_rom", "bps", "spoiler",
"calc_playthrough", "skip_playthrough", "print_custom_yaml", "calc_playthrough", "skip_playthrough", "print_custom_yaml",
"settingsonload", "rom", "outputpath"] "settingsonload", "rom", "outputpath"]
if filename == "settings.json": if filename == "settings.json":

21
Main.py
View File

@@ -41,7 +41,7 @@ from source.enemizer.DamageTables import DamageTable
from source.enemizer.Enemizer import randomize_enemies from source.enemizer.Enemizer import randomize_enemies
from source.rom.DataTables import init_data_tables from source.rom.DataTables import init_data_tables
version_number = '1.4.1.10' version_number = '1.4.1.11'
version_branch = '-u' version_branch = '-u'
__version__ = f'{version_number}{version_branch}' __version__ = f'{version_number}{version_branch}'
@@ -87,7 +87,6 @@ def main(args, seed=None, fish=None):
for i in zip(args.logic.values(), args.door_shuffle.values()): for i in zip(args.logic.values(), args.door_shuffle.values()):
if i[0] == 'hybridglitches' and i[1] != 'vanilla': if i[0] == 'hybridglitches' and i[1] != 'vanilla':
raise RuntimeError(BabelFish().translate("cli","cli","hybridglitches.door.shuffle")) raise RuntimeError(BabelFish().translate("cli","cli","hybridglitches.door.shuffle"))
if seed is None: if seed is None:
random.seed(None) random.seed(None)
world.seed = random.randint(0, 999999999) world.seed = random.randint(0, 999999999)
@@ -160,7 +159,7 @@ def main(args, seed=None, fish=None):
world.settings = CustomSettings() world.settings = CustomSettings()
world.settings.create_from_world(world, args) world.settings.create_from_world(world, args)
if args.create_spoiler and not args.jsonout: if world.spoiler_mode != 'none' and not args.jsonout:
logger.info(world.fish.translate("cli", "cli", "create.meta")) logger.info(world.fish.translate("cli", "cli", "create.meta"))
world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt')) world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
if args.mystery and not (args.suppress_meta or args.create_spoiler): if args.mystery and not (args.suppress_meta or args.create_spoiler):
@@ -365,12 +364,12 @@ def main(args, seed=None, fish=None):
if args.mystery and not (args.suppress_meta or args.create_spoiler): if args.mystery and not (args.suppress_meta or args.create_spoiler):
world.spoiler.hashes_to_file(output_path(f'{outfilebase}_meta.txt')) world.spoiler.hashes_to_file(output_path(f'{outfilebase}_meta.txt'))
elif args.create_spoiler and not args.jsonout: elif world.spoiler_mode != 'none' and not args.jsonout:
world.spoiler.hashes_to_file(output_path(f'{outfilebase}_Spoiler.txt')) world.spoiler.hashes_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
if args.create_spoiler and not args.jsonout: if world.spoiler_mode != 'none' and not args.jsonout:
logger.info(world.fish.translate("cli", "cli", "patching.spoiler")) logger.info(world.fish.translate("cli", "cli", "patching.spoiler"))
world.spoiler.to_file(output_path(f'{outfilebase}_Spoiler.txt')) world.spoiler.to_file(output_path(f'{outfilebase}_Spoiler.txt'))
if args.loglevel == 'debug': if 'debug' in world.spoiler.settings:
world.spoiler.extras(output_path(f'{outfilebase}_Spoiler.txt')) world.spoiler.extras(output_path(f'{outfilebase}_Spoiler.txt'))
if not args.skip_playthrough: if not args.skip_playthrough:
@@ -379,7 +378,7 @@ def main(args, seed=None, fish=None):
if args.jsonout: if args.jsonout:
print(json.dumps({**jsonout, 'spoiler': world.spoiler.to_json()})) print(json.dumps({**jsonout, 'spoiler': world.spoiler.to_json()}))
elif args.create_spoiler: elif world.spoiler_mode != 'none':
logger.info(world.fish.translate("cli","cli","patching.spoiler")) logger.info(world.fish.translate("cli","cli","patching.spoiler"))
if args.jsonout: if args.jsonout:
with open(output_path('%s_Spoiler.json' % outfilebase), 'w') as outfile: with open(output_path('%s_Spoiler.json' % outfilebase), 'w') as outfile:
@@ -394,7 +393,7 @@ def main(args, seed=None, fish=None):
logger.info("") logger.info("")
logger.info(world.fish.translate("cli","cli","made.rom") % (YES if (args.create_rom) else NO)) logger.info(world.fish.translate("cli","cli","made.rom") % (YES if (args.create_rom) else NO))
logger.info(world.fish.translate("cli","cli","made.playthrough") % (YES if (args.calc_playthrough) else NO)) logger.info(world.fish.translate("cli","cli","made.playthrough") % (YES if (args.calc_playthrough) else NO))
logger.info(world.fish.translate("cli","cli","made.spoiler") % (YES if (not args.jsonout and args.create_spoiler) else NO)) logger.info(world.fish.translate("cli","cli","made.spoiler") % (YES if (not args.jsonout and world.spoiler_mode != 'none') else NO))
logger.info(world.fish.translate("cli","cli","seed") + ": %s", world.seed) logger.info(world.fish.translate("cli","cli","seed") + ": %s", world.seed)
logger.info(world.fish.translate("cli","cli","total.time"), time.perf_counter() - start) logger.info(world.fish.translate("cli","cli","total.time"), time.perf_counter() - start)
@@ -452,7 +451,7 @@ def init_world(args, fish):
world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords,
args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm,
args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints, args.spoiler)
world.customizer = customized world.customizer = customized
world.boots_hint = args.boots_hint.copy() world.boots_hint = args.boots_hint.copy()
@@ -530,7 +529,7 @@ def copy_world(world):
# ToDo: Not good yet # ToDo: Not good yet
ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints) world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints, world.spoiler_mode)
ret.teams = world.teams ret.teams = world.teams
ret.player_names = copy.deepcopy(world.player_names) ret.player_names = copy.deepcopy(world.player_names)
ret.remote_items = world.remote_items.copy() ret.remote_items = world.remote_items.copy()
@@ -725,7 +724,7 @@ def copy_world_premature(world, player):
# ToDo: Not good yet # ToDo: Not good yet
ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints) world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints, world.spoiler_mode)
ret.teams = world.teams ret.teams = world.teams
ret.player_names = copy.deepcopy(world.player_names) ret.player_names = copy.deepcopy(world.player_names)
ret.remote_items = world.remote_items.copy() ret.remote_items = world.remote_items.copy()

View File

@@ -28,7 +28,7 @@ def main():
parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255)) parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255))
parser.add_argument('--names', default='') parser.add_argument('--names', default='')
parser.add_argument('--teams', default=1, type=lambda value: max(int(value), 1)) parser.add_argument('--teams', default=1, type=lambda value: max(int(value), 1))
parser.add_argument('--create_spoiler', action='store_true') parser.add_argument('--spoiler', default='none', choices=['none', 'settings', 'semi', 'full', 'debug'])
parser.add_argument('--no_race', action='store_true') parser.add_argument('--no_race', action='store_true')
parser.add_argument('--suppress_rom', action='store_true') parser.add_argument('--suppress_rom', action='store_true')
parser.add_argument('--suppress_meta', action='store_true') parser.add_argument('--suppress_meta', action='store_true')
@@ -64,7 +64,7 @@ def main():
erargs = parse_cli(['--multi', str(args.multi)]) erargs = parse_cli(['--multi', str(args.multi)])
erargs.seed = seed erargs.seed = seed
erargs.names = args.names erargs.names = args.names
erargs.create_spoiler = args.create_spoiler erargs.spoiler = args.spoiler
erargs.suppress_rom = args.suppress_rom erargs.suppress_rom = args.suppress_rom
erargs.suppress_meta = args.suppress_meta erargs.suppress_meta = args.suppress_meta
erargs.bps = args.bps erargs.bps = args.bps

View File

@@ -141,6 +141,16 @@ These are now independent of retro mode and have three options: None, Random, an
# Patch Notes # Patch Notes
* 1.4.1.11u
* New Feature: Several spoiler levels added: None, Settings-only, Semi, Full, Debug
* Semi includes only entrances, prizes, and medallions (potential new spoiler mode being worked on, definition may change)
* Entrance: Lite/Lean support enemy drop shuffle
* Standard: Re-added tutorial guard near large rock
* Enemizer
* Fixed the overwriting of bonk fairies
* Fixed broken graphics on hyrule castle
* Enemy bans
* Customizer: Fixed bug with customizing prize packs
* 1.4.1.10u * 1.4.1.10u
* Vanilla key logic: Fix for vanilla layout Misery Mire which allows more complex key logic. Locations blocked by crystal switch access are only locked by 2 keys thanks to that being the minimum in Mire to reach one of two crystal switches. * Vanilla key logic: Fix for vanilla layout Misery Mire which allows more complex key logic. Locations blocked by crystal switch access are only locked by 2 keys thanks to that being the minimum in Mire to reach one of two crystal switches.
* Autotracking: Fix for chest turn counter with chest containing multiworld items (Thanks Hiimcody) * Autotracking: Fix for chest turn counter with chest containing multiworld items (Thanks Hiimcody)

View File

@@ -347,11 +347,11 @@ def update_deprecated_args(args):
# Don't do: Yes # Don't do: Yes
# Do: No # Do: No
if "suppress_spoiler" in argVars: if "suppress_spoiler" in argVars:
args.create_spoiler = not args.suppress_spoiler in truthy args.spoiler = 'none'
# Don't do: No # Don't do: No
# Do: Yes # Do: Yes
if "create_spoiler" in argVars: if "create_spoiler" in argVars:
args.suppress_spoiler = not args.create_spoiler in truthy args.spoiler = 'full'
# ROM defaults to TRUE # ROM defaults to TRUE
# Don't do: Yes # Don't do: Yes

View File

@@ -4,14 +4,14 @@
"action": "store_true", "action": "store_true",
"type": "bool" "type": "bool"
}, },
"create_spoiler": { "spoiler": {
"action": "store_false", "choices": [
"dest": "suppress_spoiler", "none",
"type": "bool", "settings",
"help": "suppress" "semi",
}, "full",
"suppress_spoiler": { "debug"
"action": "store_true" ]
}, },
"mystery": { "mystery": {
"action": "store_true", "action": "store_true",

View File

@@ -61,7 +61,15 @@
}, },
"help": { "help": {
"lang": [ "App Language, if available, defaults to English" ], "lang": [ "App Language, if available, defaults to English" ],
"create_spoiler": [ "Output a Spoiler File" ], "spoiler": [
"Spoiler File Options. (default: %(default)s)",
"None: No Spoiler",
"Meta: Meta information only about game. Intended for mystery settings",
"Settings: Only settings information",
"Semi: ",
"Full: Full spoiler generated",
"Debug: Includes debug information"
],
"bps": [ "Output BPS patches instead of ROMs"], "bps": [ "Output BPS patches instead of ROMs"],
"logic": [ "logic": [
"Select Enforcement of Item Requirements. (default: %(default)s)", "Select Enforcement of Item Requirements. (default: %(default)s)",

View File

@@ -236,7 +236,12 @@
"randomizer.generation.bps": "Create BPS Patches", "randomizer.generation.bps": "Create BPS Patches",
"randomizer.generation.createspoiler": "Create Spoiler Log", "randomizer.generation.spoiler": "Create Spoiler Log",
"randomizer.generation.spoiler.none": "None",
"randomizer.generation.spoiler.settings": "Settings Only",
"randomizer.generation.spoiler.semi": "Semi (Entrances and Prizes)",
"randomizer.generation.spoiler.full": "Full",
"randomizer.generation.spoiler.debug": "Debug",
"randomizer.generation.createrom": "Create Patched ROM", "randomizer.generation.createrom": "Create Patched ROM",
"randomizer.generation.calcplaythrough": "Calculate Playthrough", "randomizer.generation.calcplaythrough": "Calculate Playthrough",
"randomizer.generation.print_custom_yaml": "Print Customizer File", "randomizer.generation.print_custom_yaml": "Print Customizer File",

View File

@@ -2,7 +2,16 @@
"checkboxes": { "checkboxes": {
"createrom": { "type": "checkbox" }, "createrom": { "type": "checkbox" },
"bps": { "type": "checkbox" }, "bps": { "type": "checkbox" },
"createspoiler": { "type": "checkbox" }, "spoiler": {
"type": "selectbox",
"options": [
"none",
"settings",
"semi",
"full",
"debug"
]
},
"calcplaythrough": { "type": "checkbox" }, "calcplaythrough": { "type": "checkbox" },
"print_custom_yaml": { "type": "checkbox" } "print_custom_yaml": { "type": "checkbox" }
} }

View File

@@ -146,7 +146,7 @@ SETTINGSTOPROCESS = {
}, },
"generation": { "generation": {
"bps": "bps", "bps": "bps",
"createspoiler": "create_spoiler", "spoiler": "spoiler",
"createrom": "create_rom", "createrom": "create_rom",
"calcplaythrough": "calc_playthrough", "calcplaythrough": "calc_playthrough",
"print_custom_yaml": "print_custom_yaml", "print_custom_yaml": "print_custom_yaml",

View File

@@ -260,7 +260,7 @@ def get_randomize_able_sprites_ow(area_id, data_tables):
req = data_tables.sprite_requirements[key] req = data_tables.sprite_requirements[key]
if isinstance(req, dict): if isinstance(req, dict):
continue continue
if not req.static and req.can_randomize: if not req.static and req.can_randomize and not sprite.static:
sprite_table[idx] = sprite sprite_table[idx] = sprite
return sprite_table return sprite_table

View File

@@ -29,6 +29,7 @@ def init_vanilla_sprites_ow():
create_sprite(0x21b, EnemySprite.GreenKnifeGuard, 0x20, 0x1A, '', 0x09CB51) create_sprite(0x21b, EnemySprite.GreenKnifeGuard, 0x20, 0x1A, '', 0x09CB51)
create_sprite(0x21b, EnemySprite.TutorialGuard, 0x2D, 0x25, '', 0x09CB54) create_sprite(0x21b, EnemySprite.TutorialGuard, 0x2D, 0x25, '', 0x09CB54)
create_sprite(0x21b, EnemySprite.TutorialGuard, 0x20, 0x29, '', 0x09CB57) create_sprite(0x21b, EnemySprite.TutorialGuard, 0x20, 0x29, '', 0x09CB57)
create_sprite(0x21b, EnemySprite.TutorialGuard, 0x3C, 0x2A, '', 0x09CB5B)
create_sprite(0x21d, EnemySprite.Apple, 0x0B, 0x06, '', 0x09CB5B, bonk=True) create_sprite(0x21d, EnemySprite.Apple, 0x0B, 0x06, '', 0x09CB5B, bonk=True)
create_sprite(0x22b, EnemySprite.TutorialGuard, 0x09, 0x1E, '', 0x09CB5F) create_sprite(0x22b, EnemySprite.TutorialGuard, 0x09, 0x1E, '', 0x09CB5F)
create_sprite(0x22b, EnemySprite.TutorialGuard, 0x0B, 0x1E, '', 0x09CB62) create_sprite(0x22b, EnemySprite.TutorialGuard, 0x0B, 0x1E, '', 0x09CB62)

View File

@@ -687,7 +687,11 @@ def setup_required_overworld_groups(sheets):
sheets[4].add_sprite_to_sheet([None, None, None, None], {0xF, 0x9F}) # Waterfall of wishing (pre/post-Aga) sheets[4].add_sprite_to_sheet([None, None, None, None], {0xF, 0x9F}) # Waterfall of wishing (pre/post-Aga)
sheets[3].add_sprite_to_sheet([None, None, None, 14], {0x14, 0xA4}) # Graveyard (pre/post-Aga) sheets[3].add_sprite_to_sheet([None, None, None, 14], {0x14, 0xA4}) # Graveyard (pre/post-Aga)
sheets[1].add_sprite_to_sheet([None, None, 76, 0x3F], {0x1B, 0xAB}) # Hyrule Castle (pre/post-Aga) sheets[1].add_sprite_to_sheet([None, None, 76, 0x3F], {0x1B, 0xAB}) # Hyrule Castle (pre/post-Aga)
sheets[2].add_sprite_to_sheet([None, None, None, 0x3F], {}) # Hyrule Castle - rain state ## group 0 set to 0x48 for tutortial guards
## group 1 & 2 set for green knife guards (and probably normal green guard)
## group 3 set for lightning gate
sheets[2].add_sprite_to_sheet([0x48, 0x49, 0x13, 0x3F], {}) # Hyrule Castle - rain state
# Smithy/Race/Kak (pre/post-Aga) # Smithy/Race/Kak (pre/post-Aga)
sheets[6].add_sprite_to_sheet([0x4F, 0x49, 0x4A, 0x50], {0x18, 0x22, 0x28, 0xA8, 0xB2, 0xB8}) sheets[6].add_sprite_to_sheet([0x4F, 0x49, 0x4A, 0x50], {0x18, 0x22, 0x28, 0xA8, 0xB2, 0xB8})
sheets[8].add_sprite_to_sheet([None, None, 18, None], {0x30, 0xC0}) # Desert (pre/post-Aga) sheets[8].add_sprite_to_sheet([None, None, 18, None], {0x30, 0xC0}) # Desert (pre/post-Aga)

View File

@@ -325,6 +325,7 @@ UwGeneralDeny:
- [ 0x00c2, 5, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots - [ 0x00c2, 5, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots
- [ 0x00c5, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Catwalk - Mini Helmasaur" - [ 0x00c5, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Catwalk - Mini Helmasaur"
- [ 0x00c5, 7, [ "Statue" ] ] #"Turtle Rock - Catwalk - Laser Eye (Left) 4" - [ 0x00c5, 7, [ "Statue" ] ] #"Turtle Rock - Catwalk - Laser Eye (Left) 4"
- [0x00c6, 5, ["Bumper"]]
- [ 0x00cb, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots - [ 0x00cb, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots
- [ 0x00cb, 3, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 1" - [ 0x00cb, 3, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 1"
- [ 0x00cb, 5, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 2" - [ 0x00cb, 5, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 2"
@@ -419,10 +420,12 @@ OwGeneralDeny:
- [0x03, 10, ["Gibo"]] # OldMan eating Gibo - [0x03, 10, ["Gibo"]] # OldMan eating Gibo
- [0x05, 11, ["Bumper", "AntiFairyCircle"]] # Blocks path to portal - [0x05, 11, ["Bumper", "AntiFairyCircle"]] # Blocks path to portal
- [0x1e, 3, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] # forbid a beamos here - [0x1e, 3, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] # forbid a beamos here
- [0x35, 8, ["RollerVerticalUp", "RollerVerticalDown"]] # blocks the dock
- [0x40, 0, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] - [0x40, 0, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]]
- [0x40, 7, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] - [0x40, 7, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]]
- [0x40, 13, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] - [0x40, 13, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]]
- [0x40, 14, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] - [0x40, 14, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]]
- [0x40, 16, ["RollerVerticalUp", "RollerVerticalDown"]] # Ropa near back hole is really large as a roller
- [0x55, 6, ["BigSpike"]] - [0x55, 6, ["BigSpike"]]
- [0x57, 5, ["RollerVerticalUp", "RollerVerticalDown"]] - [0x57, 5, ["RollerVerticalUp", "RollerVerticalDown"]]
- [0x5e, 0, ["Gibo"]] # kiki eating Gibo - [0x5e, 0, ["Gibo"]] # kiki eating Gibo

View File

@@ -133,7 +133,6 @@ def bottom_frame(self, parent, args=None):
made[k] = m.group(1) + m.group(2) + ' ' + m.group(4) made[k] = m.group(1) + m.group(2) + ' ' + m.group(4)
successMsg += (made["rom"] % (YES if (guiargs.create_rom) else NO)) + "\n" successMsg += (made["rom"] % (YES if (guiargs.create_rom) else NO)) + "\n"
successMsg += (made["playthrough"] % (YES if (guiargs.calc_playthrough) else NO)) + "\n" successMsg += (made["playthrough"] % (YES if (guiargs.calc_playthrough) else NO)) + "\n"
successMsg += (made["spoiler"] % (YES if (not guiargs.jsonout and guiargs.create_spoiler) else NO)) + "\n"
successMsg += (made["enemizer"] % (YES if needEnemizer else NO)) + "\n" successMsg += (made["enemizer"] % (YES if needEnemizer else NO)) + "\n"
# FIXME: English # FIXME: English
successMsg += ("Seed%s: %s" % ('s' if len(seeds) > 1 else "", ','.join(str(x) for x in seeds))) successMsg += ("Seed%s: %s" % ('s' if len(seeds) > 1 else "", ','.join(str(x) for x in seeds)))

View File

@@ -1340,18 +1340,24 @@ def do_limited_shuffle_exclude_drops(pool_def, avail, lw=True):
def do_vanilla_connect(pool_def, avail): def do_vanilla_connect(pool_def, avail):
if pool_def['condition'] == 'shopsanity': if 'shopsanity' in pool_def['condition']:
if avail.world.shopsanity[avail.player]: if avail.world.shopsanity[avail.player]:
return return
elif pool_def['condition'] == 'pottery': # this condition involves whether caves with pots are shuffled or not if 'pottery' in pool_def['condition']: # this condition involves whether caves with pots are shuffled or not
if avail.world.pottery[avail.player] not in ['none', 'keys', 'dungeon']: if avail.world.pottery[avail.player] not in ['none', 'keys', 'dungeon']:
return return
elif pool_def['condition'] == 'takeany': if 'takeany' in pool_def['condition']:
if avail.world.take_any[avail.player] == 'fixed': if avail.world.take_any[avail.player] == 'fixed':
return return
elif pool_def['condition'] == 'bonk': if 'bonk' in pool_def['condition']:
if avail.world.shuffle_bonk_drops[avail.player]: if avail.world.shuffle_bonk_drops[avail.player]:
return return
if 'dropshuffle' in pool_def['condition']:
if avail.world.dropshuffle[avail.player] not in ['none', 'keys']:
return
if 'enemy_drop' in pool_def['condition']:
if avail.world.dropshuffle[avail.player] not in ['none', 'keys'] or avail.world.enemy_shuffle[avail.player] != 'none':
return
defaults = {**default_connections, **(inverted_default_connections if avail.inverted != avail.world.is_tile_swapped(0x1b, avail.player) else open_default_connections)} defaults = {**default_connections, **(inverted_default_connections if avail.inverted != avail.world.is_tile_swapped(0x1b, avail.player) else open_default_connections)}
for entrance in pool_def['entrances']: for entrance in pool_def['entrances']:
if entrance in avail.entrances: if entrance in avail.entrances:
@@ -1871,35 +1877,56 @@ modes = {
'special': 'vanilla', 'special': 'vanilla',
'condition': '', 'condition': '',
'entrances': ['Mire Fairy', 'Archery Game', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'entrances': ['Mire Fairy', 'Archery Game', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint',
'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Fairy', 'Dark Lake Hylia Shop', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Fairy', 'East Dark World Hint',
'East Dark World Hint', 'Kakariko Gamble Game', 'Long Fairy Cave', 'Kakariko Gamble Game', 'Bush Covered House', 'Fortune Teller (Light)',
'Bush Covered House', 'Fortune Teller (Light)', 'Lost Woods Gamble', 'Lost Woods Gamble', 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy'],
'Lake Hylia Fortune Teller', 'Lake Hylia Fairy', 'Bonk Fairy (Light)'],
}, },
'fixed_shops': { 'fixed_shops': {
'special': 'vanilla', 'special': 'vanilla',
'condition': 'shopsanity', 'condition': 'shopsanity',
'entrances': ['Dark Death Mountain Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop', 'entrances': ['Dark Death Mountain Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop', 'Dark World Shop',
'Dark World Shop', 'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', 'Red Shield Shop', 'Kakariko Shop', 'Lake Hylia Shop', 'Dark Lake Hylia Shop'],
'Lake Hylia Shop'],
}, },
'fixed_takeanys': { 'fixed_takeanys': {
'special': 'vanilla', 'special': 'vanilla',
'condition': 'takeany', 'condition': 'takeany',
'entrances': ['Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy', 'entrances': ['Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy', 'Dark Lake Hylia Ledge Fairy'],
'Dark Lake Hylia Ledge Fairy', 'Bonk Fairy (Dark)'], },
'fixed_takeanys_enemy_drops_fairies': {
'special': 'vanilla',
'condition': ['takeany', 'enemy_drop'],
'entrances': ['Bonk Fairy (Dark)'],
}, },
'fixed_pottery': { 'fixed_pottery': {
'special': 'vanilla', 'special': 'vanilla',
'condition': 'pottery', 'condition': 'pottery',
'entrances': ['Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)', 'entrances': ['Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)',
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy', '20 Rupee Cave', '50 Rupee Cave', 'Palace of Darkness Hint',
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Dark Lake Hylia Ledge Spike Cave', 'Mire Hint']
'Mire Hint'] },
'fixed_enemy_drops_fairies': {
'special': 'vanilla',
'condition': 'enemy_drop',
'entrances': ['Long Fairy Cave', 'Bonk Fairy (Light)']
},
'fixed_pots_n_bones_fairies': {
'special': 'vanilla',
'condition': ['pottery', 'enemy_drop'],
'entrances': ['Hookshot Fairy']
},
'fixed_pots_n_bones': {
'special': 'vanilla',
'condition': ['pottery', 'dropshuffle'],
'entrances': ['Light World Bomb Hut']
},
'fixed_shop_n_bones': {
'special': 'vanilla',
'condition': ['shopsanity', 'enemy_drop'],
'entrances': ['Capacity Upgrade']
}, },
'fixed_bonk': { 'fixed_bonk': {
'special': 'vanilla', 'special': 'vanilla',
'condition': 'bonk', 'condition': ['enemy_drop', 'bonk'],
'entrances': ['Good Bee Cave'] 'entrances': ['Good Bee Cave']
}, },
'item_caves': { # shuffles shops/pottery if they weren't fixed in the last steps 'item_caves': { # shuffles shops/pottery if they weren't fixed in the last steps
@@ -1908,15 +1935,15 @@ modes = {
'Ice Rod Cave', 'Dam', 'Bonk Rock Cave', 'Library', 'Potion Shop', 'Mini Moldorm Cave', 'Ice Rod Cave', 'Dam', 'Bonk Rock Cave', 'Library', 'Potion Shop', 'Mini Moldorm Cave',
'Checkerboard Cave', 'Graveyard Cave', 'Cave 45', 'Sick Kids House', 'Blacksmiths Hut', 'Checkerboard Cave', 'Graveyard Cave', 'Cave 45', 'Sick Kids House', 'Blacksmiths Hut',
'Sahasrahlas Hut', 'Aginahs Cave', 'Chicken House', 'Kings Grave', 'Blinds Hideout', 'Sahasrahlas Hut', 'Aginahs Cave', 'Chicken House', 'Kings Grave', 'Blinds Hideout',
'Waterfall of Wishing', 'Dark Death Mountain Shop', 'Good Bee Cave', 'Waterfall of Wishing', 'Dark Death Mountain Shop', 'Dark Lake Hylia Shop',
'Dark Potion Shop', 'Dark Lumberjack Shop', 'Dark World Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop', 'Dark World Shop',
'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', 'Lake Hylia Shop', 'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', 'Lake Hylia Shop',
'Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)', 'Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)',
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy', 'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Desert Fairy',
'Mire Hint', 'Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy', 'Dark Lake Hylia Ledge Fairy',
'Dark Lake Hylia Ledge Fairy', 'Bonk Fairy (Dark)', 'Bonk Fairy (Dark)', 'Good Bee Cave', 'Long Fairy Cave', 'Bonk Fairy (Light)',
'Links House', 'Tavern North'] 'Mire Hint', 'Links House', 'Tavern North']
}, },
'old_man_cave': { # have to do old man cave first so lw dungeon don't use up everything 'old_man_cave': { # have to do old man cave first so lw dungeon don't use up everything
'special': 'old_man_cave_east', 'special': 'old_man_cave_east',
@@ -1962,35 +1989,56 @@ modes = {
'special': 'vanilla', 'special': 'vanilla',
'condition': '', 'condition': '',
'entrances': ['Mire Fairy', 'Archery Game', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'entrances': ['Mire Fairy', 'Archery Game', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint',
'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Fairy', 'Dark Lake Hylia Shop', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Fairy', 'East Dark World Hint',
'East Dark World Hint', 'Kakariko Gamble Game', 'Long Fairy Cave', 'Kakariko Gamble Game', 'Bush Covered House', 'Fortune Teller (Light)',
'Bush Covered House', 'Fortune Teller (Light)', 'Lost Woods Gamble', 'Lost Woods Gamble', 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy'],
'Lake Hylia Fortune Teller', 'Lake Hylia Fairy', 'Bonk Fairy (Light)'],
}, },
'fixed_shops': { 'fixed_shops': {
'special': 'vanilla', 'special': 'vanilla',
'condition': 'shopsanity', 'condition': 'shopsanity',
'entrances': ['Dark Death Mountain Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop', 'entrances': ['Dark Death Mountain Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop', 'Dark World Shop',
'Dark World Shop', 'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', 'Red Shield Shop', 'Kakariko Shop', 'Lake Hylia Shop', 'Dark Lake Hylia Shop'],
'Lake Hylia Shop'],
}, },
'fixed_takeanys': { 'fixed_takeanys': {
'special': 'vanilla', 'special': 'vanilla',
'condition': 'takeany', 'condition': 'takeany',
'entrances': ['Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy', 'entrances': ['Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy', 'Dark Lake Hylia Ledge Fairy'],
'Dark Lake Hylia Ledge Fairy', 'Bonk Fairy (Dark)'], },
'fixed_takeanys_enemy_drops_fairies': {
'special': 'vanilla',
'condition': ['takeany', 'enemy_drop'],
'entrances': ['Bonk Fairy (Dark)'],
}, },
'fixed_pottery': { 'fixed_pottery': {
'special': 'vanilla', 'special': 'vanilla',
'condition': 'pottery', 'condition': 'pottery',
'entrances': ['Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)', 'entrances': ['Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)',
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy', '20 Rupee Cave', '50 Rupee Cave', 'Palace of Darkness Hint',
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Dark Lake Hylia Ledge Spike Cave', 'Mire Hint']
'Mire Hint'] },
'fixed_enemy_drops_fairies': {
'special': 'vanilla',
'condition': 'enemy_drop',
'entrances': ['Long Fairy Cave', 'Bonk Fairy (Light)']
},
'fixed_pots_n_bones_fairies': {
'special': 'vanilla',
'condition': ['pottery', 'enemy_drop'],
'entrances': ['Hookshot Fairy']
},
'fixed_pots_n_bones': {
'special': 'vanilla',
'condition': ['pottery', 'dropshuffle'],
'entrances': ['Light World Bomb Hut']
},
'fixed_shop_n_bones': {
'special': 'vanilla',
'condition': ['shopsanity', 'enemy_drop'],
'entrances': ['Capacity Upgrade']
}, },
'fixed_bonk': { 'fixed_bonk': {
'special': 'vanilla', 'special': 'vanilla',
'condition': 'bonk', 'condition': ['enemy_drop', 'bonk'],
'entrances': ['Good Bee Cave'] 'entrances': ['Good Bee Cave']
}, },
'item_caves': { # shuffles shops/pottery if they weren't fixed in the last steps 'item_caves': { # shuffles shops/pottery if they weren't fixed in the last steps
@@ -1999,15 +2047,15 @@ modes = {
'Ice Rod Cave', 'Dam', 'Bonk Rock Cave', 'Library', 'Potion Shop', 'Mini Moldorm Cave', 'Ice Rod Cave', 'Dam', 'Bonk Rock Cave', 'Library', 'Potion Shop', 'Mini Moldorm Cave',
'Checkerboard Cave', 'Graveyard Cave', 'Cave 45', 'Sick Kids House', 'Blacksmiths Hut', 'Checkerboard Cave', 'Graveyard Cave', 'Cave 45', 'Sick Kids House', 'Blacksmiths Hut',
'Sahasrahlas Hut', 'Aginahs Cave', 'Chicken House', 'Kings Grave', 'Blinds Hideout', 'Sahasrahlas Hut', 'Aginahs Cave', 'Chicken House', 'Kings Grave', 'Blinds Hideout',
'Waterfall of Wishing', 'Dark Death Mountain Shop', 'Good Bee Cave', 'Waterfall of Wishing', 'Dark Death Mountain Shop', 'Dark Lake Hylia Shop',
'Dark Potion Shop', 'Dark Lumberjack Shop', 'Dark World Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop', 'Dark World Shop',
'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', 'Lake Hylia Shop', 'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', 'Lake Hylia Shop',
'Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)', 'Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)',
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy', 'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Desert Fairy',
'Mire Hint', 'Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy', 'Dark Lake Hylia Ledge Fairy',
'Dark Lake Hylia Ledge Fairy', 'Bonk Fairy (Dark)', 'Bonk Fairy (Dark)', 'Good Bee Cave', 'Long Fairy Cave', 'Bonk Fairy (Light)',
'Links House', 'Tavern North'] # inverted links house gets substituted 'Mire Hint', 'Links House', 'Tavern North']
} }
} }
}, },

View File

@@ -94,6 +94,7 @@ class DataTables:
# _00FA81 is LW normal # _00FA81 is LW normal
# _00FAC1 is LW post-aga # _00FAC1 is LW post-aga
# _00FB01 is DW # _00FB01 is DW
# _00FA41 is rain state
self.write_ow_sprite_data_to_rom(rom) self.write_ow_sprite_data_to_rom(rom)
for sprite, stats in self.enemy_stats.items(): for sprite, stats in self.enemy_stats.items():
# write health to rom # write health to rom
@@ -134,9 +135,11 @@ class DataTables:
# calculate how big this table is going to be? # calculate how big this table is going to be?
# bytes = sum(1+len(x)*3 for x in self.ow_enemy_table.values() if len(x) > 0)+1 # bytes = sum(1+len(x)*3 for x in self.ow_enemy_table.values() if len(x) > 0)+1
# ending_byte = 0x09CB3B + bytes # ending_byte = 0x09CB3B + bytes
max_per_state = {0: 0x40, 1: 0x90, 2: 0x8D} # dropped max on state 2 to steal space for a couple extra sprites (Murahdahla) max_per_state = {0: 0x40, 1: 0x90, 2: 0x8B} # dropped max on state 2 to steal space for a couple extra sprites (Murahdahla, extra tutorial guard)
pointer_address = snes_to_pc(0x09C881) pointer_address = snes_to_pc(0x09C881)
data_pointer = snes_to_pc(0x09CB3B) # was originally 0x09CB41 - stealing space for a couple extra sprites (Murahdahla) # currently borrowed 10 bytes, used 9 (2xMurah + TutorialGuard)
data_pointer = snes_to_pc(0x09CB38) # was originally 0x09CB41 - stealing space for a couple extra sprites (Murahdahla, extra tutorial guard)
empty_pointer = pc_to_snes(data_pointer) & 0xFFFF empty_pointer = pc_to_snes(data_pointer) & 0xFFFF
rom.write_byte(data_pointer, 0xff) rom.write_byte(data_pointer, 0xff)
cached_dark_world = {} cached_dark_world = {}