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,85 +3147,88 @@ 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'])
outfile.write('Filling Algorithm:'.ljust(line_width) + '%s\n' % self.world.algorithm) if 'all' in self.settings or 'settings' in self.settings:
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)))
outfile.write('Settings Code:'.ljust(line_width) + '%s\n' % self.metadata["code"][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('\n') outfile.write('\n')
outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player]) if 'all' in self.settings or 'settings' in self.settings:
outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player]) outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player])
outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player]) outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player])
if self.metadata['goal'][player] in ['triforcehunt', 'trinity', 'ganonhunt']: outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player])
outfile.write('Triforce Pieces Required:'.ljust(line_width) + '%s\n' % self.metadata['triforcegoal'][player]) if self.metadata['goal'][player] in ['triforcehunt', 'trinity', 'ganonhunt']:
outfile.write('Triforce Pieces Total:'.ljust(line_width) + '%s\n' % self.metadata['triforcepool'][player]) outfile.write('Triforce Pieces Required:'.ljust(line_width) + '%s\n' % self.metadata['triforcegoal'][player])
outfile.write('Crystals Required for GT:'.ljust(line_width) + '%s\n' % str(self.world.crystals_gt_orig[player])) outfile.write('Triforce Pieces Total:'.ljust(line_width) + '%s\n' % self.metadata['triforcepool'][player])
outfile.write('Crystals Required for Ganon:'.ljust(line_width) + '%s\n' % str(self.world.crystals_ganon_orig[player])) outfile.write('Crystals Required for GT:'.ljust(line_width) + '%s\n' % str(self.world.crystals_gt_orig[player]))
outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player]) outfile.write('Crystals Required for Ganon:'.ljust(line_width) + '%s\n' % str(self.world.crystals_ganon_orig[player]))
outfile.write('\n') outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player])
outfile.write('Accessibility:'.ljust(line_width) + '%s\n' % self.metadata['accessibility'][player]) outfile.write('\n')
outfile.write('Restricted Boss Items:'.ljust(line_width) + '%s\n' % self.metadata['restricted_boss_items'][player]) outfile.write('Accessibility:'.ljust(line_width) + '%s\n' % self.metadata['accessibility'][player])
outfile.write('Item Functionality:'.ljust(line_width) + '%s\n' % self.metadata['item_functionality'][player]) outfile.write('Restricted Boss Items:'.ljust(line_width) + '%s\n' % self.metadata['restricted_boss_items'][player])
outfile.write('Difficulty:'.ljust(line_width) + '%s\n' % self.metadata['item_pool'][player]) outfile.write('Item Functionality:'.ljust(line_width) + '%s\n' % self.metadata['item_functionality'][player])
outfile.write('Flute Mode:'.ljust(line_width) + '%s\n' % self.metadata['flute_mode'][player]) outfile.write('Difficulty:'.ljust(line_width) + '%s\n' % self.metadata['item_pool'][player])
outfile.write('Bow Mode:'.ljust(line_width) + '%s\n' % self.metadata['bow_mode'][player]) outfile.write('Flute Mode:'.ljust(line_width) + '%s\n' % self.metadata['flute_mode'][player])
outfile.write('Beemizer:'.ljust(line_width) + '%s\n' % self.metadata['beemizer'][player]) outfile.write('Bow Mode:'.ljust(line_width) + '%s\n' % self.metadata['bow_mode'][player])
outfile.write('Bombbag:'.ljust(line_width) + '%s\n' % yn(self.metadata['bombbag'][player])) outfile.write('Beemizer:'.ljust(line_width) + '%s\n' % self.metadata['beemizer'][player])
outfile.write('\n') outfile.write('Bombbag:'.ljust(line_width) + '%s\n' % yn(self.metadata['bombbag'][player]))
outfile.write('Shopsanity:'.ljust(line_width) + '%s\n' % yn(self.metadata['shopsanity'][player])) outfile.write('\n')
outfile.write('Bonk Drops:'.ljust(line_width) + '%s\n' % yn(self.metadata['bonk_drops'][player])) outfile.write('Shopsanity:'.ljust(line_width) + '%s\n' % yn(self.metadata['shopsanity'][player]))
outfile.write('Pottery Mode:'.ljust(line_width) + '%s\n' % self.metadata['pottery'][player]) outfile.write('Bonk Drops:'.ljust(line_width) + '%s\n' % yn(self.metadata['bonk_drops'][player]))
outfile.write('Pot Shuffle (Legacy):'.ljust(line_width) + '%s\n' % yn(self.metadata['potshuffle'][player])) outfile.write('Pottery Mode:'.ljust(line_width) + '%s\n' % self.metadata['pottery'][player])
outfile.write('Enemy Drop Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['dropshuffle'][player]) outfile.write('Pot Shuffle (Legacy):'.ljust(line_width) + '%s\n' % yn(self.metadata['potshuffle'][player]))
outfile.write('Take Any Caves:'.ljust(line_width) + '%s\n' % self.metadata['take_any'][player]) outfile.write('Enemy Drop Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['dropshuffle'][player])
outfile.write('\n') outfile.write('Take Any Caves:'.ljust(line_width) + '%s\n' % self.metadata['take_any'][player])
outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player]) outfile.write('\n')
if self.metadata['ow_shuffle'][player] != 'vanilla': outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player])
outfile.write('Free Terrain:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_terrain'][player])) if self.metadata['ow_shuffle'][player] != 'vanilla':
outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player]) outfile.write('Free Terrain:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_terrain'][player]))
if self.metadata['ow_shuffle'][player] != 'vanilla' or self.metadata['ow_crossed'][player] != 'none': outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player])
outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_keepsimilar'][player])) if self.metadata['ow_shuffle'][player] != 'vanilla' or self.metadata['ow_crossed'][player] != 'none':
outfile.write('OW Tile Flip (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player])) outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_keepsimilar'][player]))
outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_whirlpool'][player])) outfile.write('OW Tile Flip (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player]))
outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player]) outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_whirlpool'][player]))
outfile.write('\n') outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player])
outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player]) outfile.write('\n')
if self.metadata['shuffle'][player] != 'vanilla': outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player])
outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffleganon'][player])) if self.metadata['shuffle'][player] != 'vanilla':
outfile.write('Shuffle Link\'s House:'.ljust(line_width) + '%s\n' % yn(self.metadata['shufflelinks'][player])) outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffleganon'][player]))
outfile.write('Shuffle Tavern:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffletavern'][player])) outfile.write('Shuffle Link\'s House:'.ljust(line_width) + '%s\n' % yn(self.metadata['shufflelinks'][player]))
outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % self.metadata['open_pyramid'][player]) outfile.write('Shuffle Tavern:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffletavern'][player]))
if self.metadata['shuffle'][player] != 'vanilla' or self.metadata['ow_mixed'][player]: outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % self.metadata['open_pyramid'][player])
outfile.write('Overworld Map:'.ljust(line_width) + '%s\n' % self.metadata['overworld_map'][player]) if self.metadata['shuffle'][player] != 'vanilla' or self.metadata['ow_mixed'][player]:
outfile.write('\n') outfile.write('Overworld Map:'.ljust(line_width) + '%s\n' % self.metadata['overworld_map'][player])
outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['mapshuffle'][player])) outfile.write('\n')
outfile.write('Compass Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['compassshuffle'][player])) outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['mapshuffle'][player]))
outfile.write('Small Key Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['keyshuffle'][player]) outfile.write('Compass Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['compassshuffle'][player]))
outfile.write('Big Key Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['bigkeyshuffle'][player])) outfile.write('Small Key Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['keyshuffle'][player])
outfile.write('Key Logic Algorithm:'.ljust(line_width) + '%s\n' % self.metadata['key_logic'][player]) outfile.write('Big Key Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['bigkeyshuffle'][player]))
outfile.write('\n') outfile.write('Key Logic Algorithm:'.ljust(line_width) + '%s\n' % self.metadata['key_logic'][player])
outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player]) outfile.write('\n')
if self.metadata['door_shuffle'][player] != 'vanilla': outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player])
outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player]) if self.metadata['door_shuffle'][player] != 'vanilla':
outfile.write('Door Type Mode:'.ljust(line_width) + '%s\n' % self.metadata['door_type_mode'][player]) outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player])
outfile.write('Trap Door Mode:'.ljust(line_width) + '%s\n' % self.metadata['trap_door_mode'][player]) outfile.write('Door Type Mode:'.ljust(line_width) + '%s\n' % self.metadata['door_type_mode'][player])
outfile.write('Decouple Doors:'.ljust(line_width) + '%s\n' % yn(self.metadata['decoupledoors'][player])) outfile.write('Trap Door Mode:'.ljust(line_width) + '%s\n' % self.metadata['trap_door_mode'][player])
outfile.write('Spiral Stairs Self-Loop:'.ljust(line_width) + '%s\n' % yn(self.metadata['door_self_loops'][player])) outfile.write('Decouple Doors:'.ljust(line_width) + '%s\n' % yn(self.metadata['decoupledoors'][player]))
outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player])) outfile.write('Spiral Stairs Self-Loop:'.ljust(line_width) + '%s\n' % yn(self.metadata['door_self_loops'][player]))
outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player]) outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player]))
outfile.write('\n') outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player])
outfile.write('Boss Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['boss_shuffle'][player]) outfile.write('\n')
outfile.write('Enemy Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['enemy_shuffle'][player]) outfile.write('Boss Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['boss_shuffle'][player])
if self.metadata['enemy_shuffle'][player] != 'none': outfile.write('Enemy Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['enemy_shuffle'][player])
outfile.write('Enemy Logic:'.ljust(line_width) + '%s\n' % self.metadata['any_enemy_logic'][player]) if self.metadata['enemy_shuffle'][player] != 'none':
outfile.write('Enemy Health:'.ljust(line_width) + '%s\n' % self.metadata['enemy_health'][player]) outfile.write('Enemy Logic:'.ljust(line_width) + '%s\n' % self.metadata['any_enemy_logic'][player])
outfile.write('Enemy Damage:'.ljust(line_width) + '%s\n' % self.metadata['enemy_damage'][player]) outfile.write('Enemy Health:'.ljust(line_width) + '%s\n' % self.metadata['enemy_health'][player])
outfile.write('\n') outfile.write('Enemy Damage:'.ljust(line_width) + '%s\n' % self.metadata['enemy_damage'][player])
outfile.write('Pseudoboots:'.ljust(line_width) + '%s\n' % yn(self.metadata['pseudoboots'][player])) outfile.write('\n')
outfile.write('Hints:'.ljust(line_width) + '%s\n' % yn(self.metadata['hints'][player])) outfile.write('Pseudoboots:'.ljust(line_width) + '%s\n' % yn(self.metadata['pseudoboots'][player]))
outfile.write('Race:'.ljust(line_width) + '%s\n' % yn(self.world.settings.world_rep['meta']['race'])) outfile.write('Hints:'.ljust(line_width) + '%s\n' % yn(self.metadata['hints'][player]))
outfile.write('Race:'.ljust(line_width) + '%s\n' % yn(self.world.settings.world_rep['meta']['race']))
if self.startinventory: if self.startinventory:
outfile.write('Starting Inventory:'.ljust(line_width)) outfile.write('Starting Inventory:'.ljust(line_width))
@@ -3247,70 +3265,74 @@ 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
outfile.write('\n\nRequirements:\n\n') if 'all' in self.settings or 'requirements' in self.settings:
for dungeon, medallion in self.medallions.items(): outfile.write('\n\nRequirements:\n\n')
outfile.write(f'{dungeon}:'.ljust(line_width) + '%s Medallion\n' % medallion) for dungeon, medallion in self.medallions.items():
for player in range(1, self.world.players + 1): outfile.write(f'{dungeon}:'.ljust(line_width) + '%s Medallion\n' % medallion)
player_name = '' if self.world.players == 1 else str(' (Player ' + str(player) + ')') for player in range(1, self.world.players + 1):
if self.world.crystals_gt_orig[player] == 'random': player_name = '' if self.world.players == 1 else str(' (Player ' + str(player) + ')')
outfile.write(str('Crystals Required for GT' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['gt_crystals'][player]))) if self.world.crystals_gt_orig[player] == 'random':
if self.world.crystals_ganon_orig[player] == 'random': outfile.write(str('Crystals Required for GT' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['gt_crystals'][player])))
outfile.write(str('Crystals Required for Ganon' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['ganon_crystals'][player]))) 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('\n\nBottle Refills:\n\n') if 'all' in self.settings or 'misc' in self.settings:
for fairy, bottle in self.bottles.items(): outfile.write('\n\nBottle Refills:\n\n')
outfile.write(f'{fairy}: {bottle}\n') for fairy, bottle in self.bottles.items():
outfile.write(f'{fairy}: {bottle}\n')
if self.maps: if self.maps:
# flute shuffle if 'all' in self.settings or 'flute' in self.settings:
for player in range(1, self.world.players + 1): # flute shuffle
if ('flute', player) in self.maps: for player in range(1, self.world.players + 1):
outfile.write('\n\nFlute Spots:\n\n') if ('flute', player) in self.maps:
break outfile.write('\n\nFlute Spots:\n\n')
for player in range(1, self.world.players + 1): break
if ('flute', player) in self.maps: for player in range(1, self.world.players + 1):
if self.world.players > 1: if ('flute', player) in self.maps:
outfile.write(str('(Player ' + str(player) + ')\n')) # player name if self.world.players > 1:
outfile.write(self.maps[('flute', player)]['text']) outfile.write(str('(Player ' + str(player) + ')\n')) # player name
outfile.write(self.maps[('flute', player)]['text'])
# overworld tile flips if 'all' in self.settings or 'overworld' in self.settings:
for player in range(1, self.world.players + 1): # overworld tile flips
if ('swaps', player) in self.maps: for player in range(1, self.world.players + 1):
outfile.write('\n\nOW Tile Flips:\n\n') if ('swaps', player) in self.maps:
break outfile.write('\n\nOW Tile Flips:\n\n')
for player in range(1, self.world.players + 1): break
if ('swaps', player) in self.maps: for player in range(1, self.world.players + 1):
if self.world.players > 1: if ('swaps', player) in self.maps:
outfile.write(str('(Player ' + str(player) + ')\n')) # player name if self.world.players > 1:
outfile.write(self.maps[('swaps', player)]['text']) outfile.write(str('(Player ' + str(player) + ')\n')) # player name
outfile.write(self.maps[('swaps', player)]['text'])
# crossed groups
for player in range(1, self.world.players + 1): # crossed groups
if ('groups', player) in self.maps: for player in range(1, self.world.players + 1):
outfile.write('\n\nOW Crossed Groups:\n\n') if ('groups', player) in self.maps:
break outfile.write('\n\nOW Crossed Groups:\n\n')
for player in range(1, self.world.players + 1): break
if ('groups', player) in self.maps: for player in range(1, self.world.players + 1):
if self.world.players > 1: if ('groups', player) in self.maps:
outfile.write(str('(Player ' + str(player) + ')\n')) # player name if self.world.players > 1:
outfile.write(self.maps[('groups', player)]['text']) outfile.write(str('(Player ' + str(player) + ')\n')) # player name
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,33 +3341,37 @@ 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
outfile.write('\n\nShops:\n\n') if 'all' in self.settings or 'shops' in self.settings:
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\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))
for player in range(1, self.world.players + 1): if 'all' in self.settings or 'bosses' in self.settings:
if self.world.boss_shuffle[player] != 'none': for player in range(1, self.world.players + 1):
bossmap = self.bosses[str(player)] if self.world.players > 1 else self.bosses if self.world.boss_shuffle[player] != 'none':
outfile.write(f'\n\nBosses ({self.world.get_player_names(player)}):\n\n') bossmap = self.bosses[str(player)] if self.world.players > 1 else self.bosses
outfile.write('\n'.join([f'{x}: {y}' for x, y in bossmap.items() if y not in ['Agahnim', 'Agahnim 2', 'Ganon']])) outfile.write(f'\n\nBosses ({self.world.get_player_names(player)}):\n\n')
outfile.write('\n'.join([f'{x}: {y}' for x, y in bossmap.items() if y not in ['Agahnim', 'Agahnim 2', 'Ganon']]))
def extras(self, filename): def extras(self, filename):
# todo: conditional on enemy shuffle mode # todo: conditional on enemy shuffle mode

5
CLI.py
View File

@@ -236,7 +236,7 @@ def parse_settings():
"mixed_travel": "prevent", "mixed_travel": "prevent",
"standardize_palettes": "standardize", "standardize_palettes": "standardize",
'aga_randomness': True, 'aga_randomness': True,
"triforce_pool": 0, "triforce_pool": 0,
"triforce_goal": 0, "triforce_goal": 0,
"triforce_pool_min": 0, "triforce_pool_min": 0,
@@ -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 = {}