Merge branch 'DoorDevVolatile' into Customizer

# Conflicts:
#	Main.py

Also fixed a bug with lite/lean
This commit is contained in:
aerinon
2022-06-22 10:53:06 -06:00
13 changed files with 260 additions and 85 deletions

View File

@@ -2359,6 +2359,62 @@ class Spoiler(object):
else: else:
self.doorTypes[(doorNames, player)] = OrderedDict([('player', player), ('doorNames', doorNames), ('type', type)]) self.doorTypes[(doorNames, player)] = OrderedDict([('player', player), ('doorNames', doorNames), ('type', type)])
def parse_meta(self):
from Main import __version__ as ERVersion
self.startinventory = list(map(str, self.world.precollected_items))
self.metadata = {'version': ERVersion,
'logic': self.world.logic,
'mode': self.world.mode,
'retro': self.world.retro,
'bombbag': self.world.bombbag,
'weapons': self.world.swords,
'goal': self.world.goal,
'shuffle': self.world.shuffle,
'shuffleganon': self.world.shuffle_ganon,
'shufflelinks': self.world.shufflelinks,
'overworld_map': self.world.overworld_map,
'door_shuffle': self.world.doorShuffle,
'intensity': self.world.intensity,
'dungeon_counters': self.world.dungeon_counters,
'item_pool': self.world.difficulty,
'item_functionality': self.world.difficulty_adjustments,
'gt_crystals': self.world.crystals_needed_for_gt,
'ganon_crystals': self.world.crystals_needed_for_ganon,
'open_pyramid': self.world.open_pyramid,
'accessibility': self.world.accessibility,
'restricted_boss_items': self.world.restrict_boss_items,
'hints': self.world.hints,
'mapshuffle': self.world.mapshuffle,
'compassshuffle': self.world.compassshuffle,
'keyshuffle': self.world.keyshuffle,
'bigkeyshuffle': self.world.bigkeyshuffle,
'boss_shuffle': self.world.boss_shuffle,
'enemy_shuffle': self.world.enemy_shuffle,
'enemy_health': self.world.enemy_health,
'enemy_damage': self.world.enemy_damage,
'players': self.world.players,
'teams': self.world.teams,
'experimental': self.world.experimental,
'dropshuffle': self.world.dropshuffle,
'pottery': self.world.pottery,
'potshuffle': self.world.potshuffle,
'shopsanity': self.world.shopsanity,
'pseudoboots': self.world.pseudoboots,
'triforcegoal': self.world.treasure_hunt_count,
'triforcepool': self.world.treasure_hunt_total,
'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)}
}
for p in range(1, self.world.players + 1):
from ItemList import set_default_triforce
if self.world.custom and p in self.world.customitemarray:
self.metadata['triforcegoal'][p], self.metadata['triforcepool'][p] = set_default_triforce(self.metadata['goal'][p], self.world.customitemarray[p]["triforcepiecesgoal"], self.world.customitemarray[p]["triforcepieces"])
else:
custom_goal = self.world.treasure_hunt_count[p] if isinstance(self.world.treasure_hunt_count, dict) else self.world.treasure_hunt_count
custom_total = self.world.treasure_hunt_total[p] if isinstance(self.world.treasure_hunt_total, dict) else self.world.treasure_hunt_total
self.metadata['triforcegoal'][p], self.metadata['triforcepool'][p] = set_default_triforce(self.metadata['goal'][p], custom_goal, custom_total)
def parse_data(self): def parse_data(self):
self.medallions = OrderedDict() self.medallions = OrderedDict()
if self.world.players == 1: if self.world.players == 1:
@@ -2378,8 +2434,6 @@ 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]
self.startinventory = list(map(str, self.world.precollected_items))
self.locations = OrderedDict() self.locations = OrderedDict()
listed_locations = set() listed_locations = set()
@@ -2450,47 +2504,6 @@ class Spoiler(object):
for portal in self.world.dungeon_portals[player]: for portal in self.world.dungeon_portals[player]:
self.set_lobby(portal.name, portal.door.name, player) self.set_lobby(portal.name, portal.door.name, player)
from Main import __version__ as ERVersion
self.metadata = {'version': ERVersion,
'logic': self.world.logic,
'mode': self.world.mode,
'retro': self.world.retro,
'bombbag': self.world.bombbag,
'weapons': self.world.swords,
'goal': self.world.goal,
'shuffle': self.world.shuffle,
'shufflelinks': self.world.shufflelinks,
'door_shuffle': self.world.doorShuffle,
'intensity': self.world.intensity,
'item_pool': self.world.difficulty,
'item_functionality': self.world.difficulty_adjustments,
'gt_crystals': self.world.crystals_needed_for_gt,
'ganon_crystals': self.world.crystals_needed_for_ganon,
'open_pyramid': self.world.open_pyramid,
'accessibility': self.world.accessibility,
'restricted_boss_items': self.world.restrict_boss_items,
'hints': self.world.hints,
'mapshuffle': self.world.mapshuffle,
'compassshuffle': self.world.compassshuffle,
'keyshuffle': self.world.keyshuffle,
'bigkeyshuffle': self.world.bigkeyshuffle,
'boss_shuffle': self.world.boss_shuffle,
'enemy_shuffle': self.world.enemy_shuffle,
'enemy_health': self.world.enemy_health,
'enemy_damage': self.world.enemy_damage,
'players': self.world.players,
'teams': self.world.teams,
'experimental': self.world.experimental,
'dropshuffle': self.world.dropshuffle,
'pottery': self.world.pottery,
'potshuffle': self.world.potshuffle,
'shopsanity': self.world.shopsanity,
'pseudoboots': self.world.pseudoboots,
'triforcegoal': self.world.treasure_hunt_count,
'triforcepool': self.world.treasure_hunt_total,
'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)}
}
def to_json(self): def to_json(self):
self.parse_data() self.parse_data()
out = OrderedDict() out = OrderedDict()
@@ -2513,22 +2526,29 @@ class Spoiler(object):
return json.dumps(out) return json.dumps(out)
def to_file(self, filename): def mystery_meta_to_file(self, filename):
self.parse_meta()
with open(filename, 'w') as outfile:
outfile.write('ALttP Dungeon Randomizer Version %s - Seed: %s\n\n' % (self.metadata['version'], self.world.seed))
for player in range(1, self.world.players + 1):
if self.world.players > 1:
outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_names(player)))
outfile.write('Logic: %s\n' % self.metadata['logic'][player])
def meta_to_file(self, filename):
def yn(flag): def yn(flag):
return 'Yes' if flag else 'No' return 'Yes' if flag else 'No'
self.parse_data() line_width = 35
self.parse_meta()
with open(filename, 'w') as outfile: with open(filename, 'w') as outfile:
outfile.write('ALttP Entrance Randomizer Version %s - Seed: %s\n\n' % (self.metadata['version'], self.world.seed)) outfile.write('ALttP Dungeon Randomizer Version %s - Seed: %s\n\n' % (self.metadata['version'], self.world.seed))
outfile.write('Filling Algorithm: %s\n' % self.world.algorithm) outfile.write('Filling Algorithm: %s\n' % self.world.algorithm)
outfile.write('Players: %d\n' % self.world.players) outfile.write('Players: %d\n' % self.world.players)
outfile.write('Teams: %d\n' % self.world.teams) outfile.write('Teams: %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 len(self.hashes) > 0:
for team in range(self.world.teams):
outfile.write('%s%s\n' % (f"Hash - {self.world.player_names[player][team]} (Team {team+1}): " if self.world.teams > 1 else 'Hash: ', self.hashes[player, team]))
outfile.write(f'Settings Code: {self.metadata["code"][player]}\n') outfile.write(f'Settings Code: {self.metadata["code"][player]}\n')
outfile.write('Logic: %s\n' % self.metadata['logic'][player]) outfile.write('Logic: %s\n' % self.metadata['logic'][player])
outfile.write('Mode: %s\n' % self.metadata['mode'][player]) outfile.write('Mode: %s\n' % self.metadata['mode'][player])
@@ -2538,23 +2558,30 @@ class Spoiler(object):
if self.metadata['goal'][player] in ['triforcehunt', 'trinity']: if self.metadata['goal'][player] in ['triforcehunt', 'trinity']:
outfile.write('Triforce Pieces Required: %s\n' % self.metadata['triforcegoal'][player]) outfile.write('Triforce Pieces Required: %s\n' % self.metadata['triforcegoal'][player])
outfile.write('Triforce Pieces Total: %s\n' % self.metadata['triforcepool'][player]) outfile.write('Triforce Pieces Total: %s\n' % self.metadata['triforcepool'][player])
outfile.write('Crystals required for GT: %s\n' % (str(self.world.crystals_gt_orig[player])))
outfile.write('Crystals required for Ganon: %s\n' % (str(self.world.crystals_ganon_orig[player])))
outfile.write('Accessibility: %s\n' % self.metadata['accessibility'][player])
outfile.write(f"Restricted Boss Items: {self.metadata['restricted_boss_items'][player]}\n")
outfile.write('Difficulty: %s\n' % self.metadata['item_pool'][player]) outfile.write('Difficulty: %s\n' % self.metadata['item_pool'][player])
outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player]) outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player])
outfile.write(f"Shopsanity: {yn(self.metadata['shopsanity'][player])}\n")
outfile.write(f"Bombbag: {yn(self.metadata['bombbag'][player])}\n")
outfile.write(f"Pseudoboots: {yn(self.metadata['pseudoboots'][player])}\n")
outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player]) outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player])
if self.metadata['shuffle'][player] != 'vanilla':
outfile.write(f"Link's House Shuffled: {yn(self.metadata['shufflelinks'])}\n") outfile.write(f"Link's House Shuffled: {yn(self.metadata['shufflelinks'])}\n")
outfile.write(f"GT/Ganon Shuffled: {yn(self.metadata['shuffleganon'])}\n")
outfile.write(f"Overworld Map: {self.metadata['overworld_map'][player]}\n")
if self.metadata['goal'][player] != 'trinity':
outfile.write('Pyramid hole pre-opened: %s\n' % ('Yes' if self.metadata['open_pyramid'][player] else 'No'))
outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'][player]) outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'][player])
outfile.write('Intensity: %s\n' % self.metadata['intensity'][player]) if self.metadata['door_shuffle'][player] != 'vanilla':
outfile.write(f"Intensity: {self.metadata['intensity'][player]}\n")
outfile.write(f"Experimental: {yn(self.metadata['experimental'][player])}\n")
outfile.write(f"Dungeon Counters: {self.metadata['dungeon_counters'][player]}\n")
outfile.write(f"Drop Shuffle: {yn(self.metadata['dropshuffle'][player])}\n") outfile.write(f"Drop Shuffle: {yn(self.metadata['dropshuffle'][player])}\n")
outfile.write(f"Pottery Mode: {self.metadata['pottery'][player]}\n") outfile.write(f"Pottery Mode: {self.metadata['pottery'][player]}\n")
outfile.write(f"Pot Shuffle (Legacy): {yn(self.metadata['potshuffle'][player])}\n") outfile.write(f"Pot Shuffle (Legacy): {yn(self.metadata['potshuffle'][player])}\n")
addition = ' (Random)' if self.world.crystals_gt_orig[player] == 'random' else ''
outfile.write('Crystals required for GT: %s\n' % (str(self.metadata['gt_crystals'][player]) + addition))
addition = ' (Random)' if self.world.crystals_ganon_orig[player] == 'random' else ''
outfile.write('Crystals required for Ganon: %s\n' % (str(self.metadata['ganon_crystals'][player]) + addition))
if self.metadata['goal'][player] != 'trinity':
outfile.write('Pyramid hole pre-opened: %s\n' % ('Yes' if self.metadata['open_pyramid'][player] else 'No'))
outfile.write('Accessibility: %s\n' % self.metadata['accessibility'][player])
outfile.write(f"Restricted Boss Items: {self.metadata['restricted_boss_items'][player]}\n")
outfile.write('Map shuffle: %s\n' % ('Yes' if self.metadata['mapshuffle'][player] else 'No')) outfile.write('Map shuffle: %s\n' % ('Yes' if self.metadata['mapshuffle'][player] else 'No'))
outfile.write('Compass shuffle: %s\n' % ('Yes' if self.metadata['compassshuffle'][player] else 'No')) outfile.write('Compass shuffle: %s\n' % ('Yes' if self.metadata['compassshuffle'][player] else 'No'))
outfile.write('Small Key shuffle: %s\n' % ('Yes' if self.metadata['keyshuffle'][player] else 'No')) outfile.write('Small Key shuffle: %s\n' % ('Yes' if self.metadata['keyshuffle'][player] else 'No'))
@@ -2564,10 +2591,62 @@ class Spoiler(object):
outfile.write('Enemy health: %s\n' % self.metadata['enemy_health'][player]) outfile.write('Enemy health: %s\n' % self.metadata['enemy_health'][player])
outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'][player]) outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'][player])
outfile.write(f"Hints: {yn(self.metadata['hints'][player])}\n") outfile.write(f"Hints: {yn(self.metadata['hints'][player])}\n")
outfile.write(f"Experimental: {yn(self.metadata['experimental'][player])}\n")
outfile.write(f"Shopsanity: {yn(self.metadata['shopsanity'][player])}\n") if self.startinventory:
outfile.write(f"Bombbag: {yn(self.metadata['bombbag'][player])}\n") outfile.write('Starting Inventory:'.ljust(line_width))
outfile.write(f"Pseudoboots: {yn(self.metadata['pseudoboots'][player])}\n") outfile.write('\n'.ljust(line_width+1).join(self.startinventory) + '\n')
def hashes_to_file(self, filename):
with open(filename, 'r') as infile:
contents = infile.readlines()
def insert(lines, i, value):
lines.insert(i, value)
i += 1
return i
idx = 2
if self.world.players > 1:
idx = insert(contents, idx, 'Hashes:')
for player in range(1, self.world.players + 1):
if self.world.players > 1:
idx = insert(contents, idx, f'\nPlayer {player}: {self.world.get_player_names(player)}\n')
if len(self.hashes) > 0:
for team in range(self.world.teams):
player_name = self.world.player_names[player][team]
label = f"Hash - {player_name} (Team {team+1}): " if self.world.teams > 1 else 'Hash: '
idx = insert(contents, idx, f'{label}{self.hashes[player, team]}\n')
if self.world.players > 1:
insert(contents, idx, '\n') # return value ignored here, if you want to add more lines
with open(filename, "w") as f:
contents = "".join(contents)
f.write(contents)
def to_file(self, filename):
self.parse_data()
with open(filename, 'a') as outfile:
line_width = 35
outfile.write('\nRequirements:\n\n')
for dungeon, medallion in self.medallions.items():
outfile.write(f'{dungeon}: {medallion} Medallion\n')
for player in range(1, self.world.players + 1):
player_name = '' if self.world.players == 1 else str(' (Player ' + str(player) + ')')
if self.world.crystals_gt_orig[player] == 'random':
outfile.write(str('Crystals Required for GT' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['gt_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')
for fairy, bottle in self.bottles.items():
outfile.write(f'{fairy}: {bottle}\n')
if self.entrances:
# entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly
outfile.write('\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()]))
if self.doors: if self.doors:
outfile.write('\n\nDoors:\n\n') outfile.write('\n\nDoors:\n\n')
outfile.write('\n'.join( outfile.write('\n'.join(
@@ -2588,19 +2667,6 @@ class Spoiler(object):
# 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()]))
if self.entrances:
# entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly
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\nMedallions:\n')
for dungeon, medallion in self.medallions.items():
outfile.write(f'\n{dungeon}: {medallion} Medallion')
outfile.write('\n\nBottle Refills:\n')
for fairy, bottle in self.bottles.items():
outfile.write(f'\n{fairy}: {bottle}')
if self.startinventory:
outfile.write('\n\nStarting Inventory:\n\n')
outfile.write('\n'.join(self.startinventory))
# 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
@@ -2618,6 +2684,8 @@ class Spoiler(object):
outfile.write(f'\n\nBosses ({self.world.get_player_names(player)}):\n\n') 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']])) outfile.write('\n'.join([f'{x}: {y}' for x, y in bossmap.items() if y not in ['Agahnim', 'Agahnim 2', 'Ganon']]))
def playthrough_to_file(self, filename):
with open(filename, 'a') as outfile:
# 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\nPlaythrough:\n\n') outfile.write('\n\nPlaythrough:\n\n')

View File

@@ -1,11 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse if __name__ == '__main__':
import copy from source.meta.check_requirements import check_requirements
check_requirements(console=True)
import os import os
import logging import logging
import RaceRandom as random import RaceRandom as random
import textwrap
import shlex
import sys import sys
from source.classes.BabelFish import BabelFish from source.classes.BabelFish import BabelFish

View File

@@ -2551,6 +2551,8 @@ mandatory_connections = [('Links House S&Q', 'Links House'),
('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'), ('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
('Lake Hylia Central Island Teleporter', 'Dark Lake Hylia Central Island'), ('Lake Hylia Central Island Teleporter', 'Dark Lake Hylia Central Island'),
('Zoras River', 'Zoras River'), ('Zoras River', 'Zoras River'),
('Zora Waterfall Entryway', 'Zora Waterfall Entryway'),
('Zora Waterfall Water Drop', 'Light World'),
('Kings Grave Outer Rocks', 'Kings Grave Area'), ('Kings Grave Outer Rocks', 'Kings Grave Area'),
('Kings Grave Inner Rocks', 'Light World'), ('Kings Grave Inner Rocks', 'Light World'),
('Kings Grave Mirror Spot', 'Kings Grave Area'), ('Kings Grave Mirror Spot', 'Kings Grave Area'),

4
Gui.py
View File

@@ -1,3 +1,7 @@
if __name__ == '__main__':
from source.meta.check_requirements import check_requirements
check_requirements()
import json import json
import os import os
import sys import sys

22
Main.py
View File

@@ -33,7 +33,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new
from source.tools.BPS import create_bps_from_data from source.tools.BPS import create_bps_from_data
from source.classes.CustomSettings import CustomSettings from source.classes.CustomSettings import CustomSettings
__version__ = '1.0.2.5-w' __version__ = '1.0.2.6-w'
from source.classes.BabelFish import BabelFish from source.classes.BabelFish import BabelFish
@@ -144,6 +144,8 @@ def main(args, seed=None, fish=None):
world.settings = CustomSettings() world.settings = CustomSettings()
world.settings.create_from_world(world) world.settings.create_from_world(world)
outfilebase = f'DR_{args.outputname if args.outputname else world.seed}'
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
world.difficulty_requirements[player] = difficulties[world.difficulty[player]] world.difficulty_requirements[player] = difficulties[world.difficulty[player]]
@@ -157,6 +159,13 @@ def main(args, seed=None, fish=None):
if item: if item:
world.push_precollected(item) world.push_precollected(item)
if args.create_spoiler and not args.jsonout:
logger.info(world.fish.translate("cli", "cli", "create.meta"))
world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
if args.mystery:
world.spoiler.mystery_meta_to_file(output_path(f'{outfilebase}_meta.txt'))
for player in range(1, world.players + 1):
if world.mode[player] != 'inverted': if world.mode[player] != 'inverted':
create_regions(world, player) create_regions(world, player)
else: else:
@@ -288,7 +297,6 @@ def main(args, seed=None, fish=None):
balance_money_progression(world) balance_money_progression(world)
ensure_good_pots(world, True) ensure_good_pots(world, True)
outfilebase = f'DR_{args.outputname if args.outputname else world.seed}'
if args.print_custom_yaml: if args.print_custom_yaml:
world.settings.record_item_placements(world) world.settings.record_item_placements(world)
world.settings.write_to_file(output_path(f'{outfilebase}_custom.yaml')) world.settings.write_to_file(output_path(f'{outfilebase}_custom.yaml'))
@@ -367,6 +375,14 @@ def main(args, seed=None, fish=None):
with open(output_path('%s_multidata' % outfilebase), 'wb') as f: with open(output_path('%s_multidata' % outfilebase), 'wb') as f:
f.write(multidata) f.write(multidata)
if args.mystery:
world.spoiler.hashes_to_file(output_path(f'{outfilebase}_meta.txt'))
elif args.create_spoiler and not args.jsonout:
world.spoiler.hashes_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
if args.create_spoiler and not args.jsonout:
logger.info(world.fish.translate("cli", "cli", "patching.spoiler"))
world.spoiler.to_file(output_path(f'{outfilebase}_Spoiler.txt'))
if not args.skip_playthrough: if not args.skip_playthrough:
logger.info(world.fish.translate("cli","cli","calc.playthrough")) logger.info(world.fish.translate("cli","cli","calc.playthrough"))
create_playthrough(world) create_playthrough(world)
@@ -379,7 +395,7 @@ def main(args, seed=None, fish=None):
with open(output_path('%s_Spoiler.json' % outfilebase), 'w') as outfile: with open(output_path('%s_Spoiler.json' % outfilebase), 'w') as outfile:
outfile.write(world.spoiler.to_json()) outfile.write(world.spoiler.to_json())
else: else:
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase)) world.spoiler.playthrough_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
YES = world.fish.translate("cli","cli","yes") YES = world.fish.translate("cli","cli","yes")
NO = world.fish.translate("cli","cli","no") NO = world.fish.translate("cli","cli","no")

View File

@@ -166,11 +166,17 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o
#### Customizer #### Customizer
* Fixed an issue with lite/lean ER not generating
* Fixed up the GUI selection of the customizer file. * Fixed up the GUI selection of the customizer file.
* Fixed up the item_pool section to skip a lot of pool manipulations. Key items will be added (like the bow) if not detected. Extra dungeon items can be added to the pool and will be confined to the dungeon if possible (and not shuffled). If the pool isn't full, junk items are added to the pool to fill it out. * Fixed up the item_pool section to skip a lot of pool manipulations. Key items will be added (like the bow) if not detected. Extra dungeon items can be added to the pool and will be confined to the dungeon if possible (and not shuffled). If the pool isn't full, junk items are added to the pool to fill it out.
#### Volatile #### Volatile
* 1.0.2.6
* Fix for Zelda (or any follower) going to the maiden cell supertile and the boss is not Blind. The follower will not despawn unless the boss is Blind, then the maiden will spawn as normal.
* Added a check for package requirements before running code. GUI and console both for better error messages. Thanks to mtrethewey for the idea.
* Refactored spoiler to generate in stages for better error collection. A meta file will be generated additionally for mystery seeds. Some random settings moved later in the spoiler to have the meta section at the top not spoil certain things. (GT/Ganon requirements.) Thanks to codemann and OWR for most of this work.
* Fix for Waterfall of Wishing logic in open. You must have flippers to exit the Waterfall (or moon pearl in glitched modes that allow minor glitches in logic)
* 1.0.2.5 * 1.0.2.5
* Some textual changes for hints (capitalization standardization) * Some textual changes for hints (capitalization standardization)
* Item will be highlighted in red if experimental is on * Item will be highlighted in red if experimental is on

View File

@@ -15,7 +15,7 @@ def create_regions(world, player):
'Bonk Rock Cave', 'Library', 'Potion Shop', 'Two Brothers House (East)', 'Desert Palace Stairs', 'Eastern Palace', 'Master Sword Meadow', 'Bonk Rock Cave', 'Library', 'Potion Shop', 'Two Brothers House (East)', 'Desert Palace Stairs', 'Eastern Palace', 'Master Sword Meadow',
'Sanctuary', 'Sanctuary Grave', 'Death Mountain Entrance Rock', 'Flute Spot 1', 'Dark Desert Teleporter', 'East Hyrule Teleporter', 'South Hyrule Teleporter', 'Kakariko Teleporter', 'Sanctuary', 'Sanctuary Grave', 'Death Mountain Entrance Rock', 'Flute Spot 1', 'Dark Desert Teleporter', 'East Hyrule Teleporter', 'South Hyrule Teleporter', 'Kakariko Teleporter',
'Elder House (East)', 'Elder House (West)', 'North Fairy Cave', 'North Fairy Cave Drop', 'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Tavern (Front)', 'Elder House (East)', 'Elder House (West)', 'North Fairy Cave', 'North Fairy Cave Drop', 'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Tavern (Front)',
'Bush Covered House', 'Light World Bomb Hut', 'Kakariko Shop', 'Long Fairy Cave', 'Good Bee Cave', '20 Rupee Cave', 'Cave Shop (Lake Hylia)', 'Waterfall of Wishing', 'Hyrule Castle Main Gate', 'Bush Covered House', 'Light World Bomb Hut', 'Kakariko Shop', 'Long Fairy Cave', 'Good Bee Cave', '20 Rupee Cave', 'Cave Shop (Lake Hylia)', 'Zora Waterfall Entryway', 'Hyrule Castle Main Gate',
'Bonk Fairy (Light)', '50 Rupee Cave', 'Fortune Teller (Light)', 'Lake Hylia Fairy', 'Light Hype Fairy', 'Desert Fairy', 'Lumberjack House', 'Lake Hylia Fortune Teller', 'Kakariko Gamble Game', 'Top of Pyramid']), 'Bonk Fairy (Light)', '50 Rupee Cave', 'Fortune Teller (Light)', 'Lake Hylia Fairy', 'Light Hype Fairy', 'Desert Fairy', 'Lumberjack House', 'Lake Hylia Fortune Teller', 'Kakariko Gamble Game', 'Top of Pyramid']),
create_lw_region(player, 'Death Mountain Entrance', None, ['Old Man Cave (West)', 'Death Mountain Entrance Drop']), create_lw_region(player, 'Death Mountain Entrance', None, ['Old Man Cave (West)', 'Death Mountain Entrance Drop']),
create_lw_region(player, 'Lake Hylia Central Island', None, ['Capacity Upgrade', 'Lake Hylia Central Island Teleporter']), create_lw_region(player, 'Lake Hylia Central Island', None, ['Capacity Upgrade', 'Lake Hylia Central Island Teleporter']),
@@ -28,6 +28,7 @@ def create_regions(world, player):
create_cave_region(player, 'Blinds Hideout (Top)', 'a bounty of five items', ["Blind's Hideout - Top"]), create_cave_region(player, 'Blinds Hideout (Top)', 'a bounty of five items', ["Blind's Hideout - Top"]),
create_cave_region(player, 'Hyrule Castle Secret Entrance', 'a drop\'s exit', ['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']), create_cave_region(player, 'Hyrule Castle Secret Entrance', 'a drop\'s exit', ['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']),
create_lw_region(player, 'Zoras River', ['King Zora', 'Zora\'s Ledge']), create_lw_region(player, 'Zoras River', ['King Zora', 'Zora\'s Ledge']),
create_lw_region(player, 'Zora Waterfall Entryway', None, ['Waterfall of Wishing', 'Zora Waterfall Water Drop']),
create_cave_region(player, 'Waterfall of Wishing', 'a cave with two chests', ['Waterfall Fairy - Left', 'Waterfall Fairy - Right']), create_cave_region(player, 'Waterfall of Wishing', 'a cave with two chests', ['Waterfall Fairy - Left', 'Waterfall Fairy - Right']),
create_lw_region(player, 'Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']), create_lw_region(player, 'Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']),
create_cave_region(player, 'Kings Grave', 'a cave with a chest', ['King\'s Tomb']), create_cave_region(player, 'Kings Grave', 'a cave with a chest', ['King\'s Tomb']),

3
Rom.py
View File

@@ -353,6 +353,9 @@ def patch_enemizer(world, player, rom, local_rom, enemizercli, random_sprite_on_
0xad, 0x3, 0x4, 0x29, 0x20, 0xf0, 0x1d]) 0xad, 0x3, 0x4, 0x29, 0x20, 0xf0, 0x1d])
rom.write_byte(0x200101, 0) # Do not close boss room door on entry. rom.write_byte(0x200101, 0) # Do not close boss room door on entry.
rom.write_byte(0x1B0101, 0) # Do not close boss room door on entry. (for Ijwu's enemizer) rom.write_byte(0x1B0101, 0) # Do not close boss room door on entry. (for Ijwu's enemizer)
else:
rom.write_byte(0x04DE83, 0xB3) # maiden is now something else
if random_sprite_on_hit: if random_sprite_on_hit:
_populate_sprite_table() _populate_sprite_table()

View File

@@ -793,6 +793,9 @@ def default_rules(world, player):
set_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has('Flippers', player)) set_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has('Flippers', player))
set_rule(world.get_entrance('Waterfall of Wishing', player), lambda state: state.has('Flippers', player)) # can be fake flippered into, but is in weird state inside that might prevent you from doing things. Can be improved in future Todo set_rule(world.get_entrance('Waterfall of Wishing', player), lambda state: state.has('Flippers', player)) # can be fake flippered into, but is in weird state inside that might prevent you from doing things. Can be improved in future Todo
# flippers or pearl to leave (via fake flippers)
set_rule(world.get_entrance('Zora Waterfall Water Drop', player),
lambda state: state.has('Flippers', player) or state.has_Pearl(player))
set_rule(world.get_location('Frog', player), lambda state: state.can_lift_heavy_rocks(player)) # will get automatic moon pearl requirement set_rule(world.get_location('Frog', player), lambda state: state.can_lift_heavy_rocks(player)) # will get automatic moon pearl requirement
set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player)) set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player))
set_rule(world.get_entrance('Desert Palace Entrance (North) Rocks', player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance('Desert Palace Entrance (North) Rocks', player), lambda state: state.can_lift_rocks(player))
@@ -1032,6 +1035,8 @@ def inverted_rules(world, player):
def no_glitches_rules(world, player): def no_glitches_rules(world, player):
if world.mode[player] != 'inverted': if world.mode[player] != 'inverted':
add_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Flippers', player) or state.can_lift_rocks(player)) add_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Flippers', player) or state.can_lift_rocks(player))
add_rule(world.get_entrance('Zora Waterfall Entryway', player), lambda state: state.has('Flippers', player))
add_rule(world.get_entrance('Zora Waterfall Water Drop', player), lambda state: state.has('Flippers', player))
add_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Flippers', player)) # can be fake flippered to add_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Flippers', player)) # can be fake flippered to
add_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has('Flippers', player)) add_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has('Flippers', player))
add_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) add_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))

View File

@@ -38,6 +38,7 @@
"cannot.reach.required": "Not all required items reachable. Something went terribly wrong here.", "cannot.reach.required": "Not all required items reachable. Something went terribly wrong here.",
"patching.rom": "Patching ROM", "patching.rom": "Patching ROM",
"patching.spoiler": "Creating Spoiler", "patching.spoiler": "Creating Spoiler",
"create.meta": "Creating Meta Info",
"calc.playthrough": "Calculating Playthrough", "calc.playthrough": "Calculating Playthrough",
"made.rom": "Patched ROM: %s", "made.rom": "Patched ROM: %s",
"made.playthrough": "Printed Playthrough: %s", "made.playthrough": "Printed Playthrough: %s",

View File

@@ -0,0 +1,41 @@
import importlib.util
import webbrowser
from tkinter import Tk, Label, Button, Frame
def check_requirements(console=False):
check_packages = {'aenum': 'aenum',
'fast-enum': 'fast_enum',
'python-bps-continued': 'bps',
'colorama': 'colorama',
'aioconsole' : 'aioconsole',
'websockets' : 'websockets',
'pyyaml': 'yaml'}
missing = []
for package, import_name in check_packages.items():
spec = importlib.util.find_spec(import_name)
if spec is None:
missing.append(package)
if len(missing) > 0:
packages = ','.join(missing)
if console:
import logging
logger = logging.getLogger('')
logger.error('You need to install the following python packages:')
logger.error(f'{packages}')
logger.error('See the step about "Installing Platform-specific dependencies":')
logger.error('https://github.com/aerinon/ALttPDoorRandomizer/blob/DoorDev/docs/BUILDING.md')
else:
master = Tk()
master.title('Error')
frame = Frame(master)
frame.pack(expand=True, padx =50, pady=50)
Label(frame, text='You need to install the following python packages:').pack()
Label(frame, text=f'{packages}').pack()
Label(frame, text='See the step about "Installing Platform-specific dependencies":').pack()
url = 'https://github.com/aerinon/ALttPDoorRandomizer/blob/DoorDev/docs/BUILDING.md'
link = Label(frame, fg='blue', cursor='hand2', text=url)
link.pack()
link.bind('<Button-1>', lambda e: webbrowser.open_new_tab(url))
Button(master, text='Ok', command=master.destroy).pack()
master.mainloop()

View File

@@ -947,7 +947,7 @@ def find_entrances_and_exits(avail_pool, entrance_pool):
if item in avail_pool.entrances: if item in avail_pool.entrances:
entrances.append(item) entrances.append(item)
if item in entrance_map and entrance_map[item] in avail_pool.exits: if item in entrance_map and entrance_map[item] in avail_pool.exits:
if item in ['Links House Exit', 'Inverted Links House Exit']: if entrance_map[item] in ['Links House Exit', 'Inverted Links House Exit']:
targets.append('Chris Houlihan Room Exit') targets.append('Chris Houlihan Room Exit')
targets.append(entrance_map[item]) targets.append(entrance_map[item])
elif item in single_entrance_map and single_entrance_map[item] in avail_pool.exits: elif item in single_entrance_map and single_entrance_map[item] in avail_pool.exits:
@@ -1837,6 +1837,8 @@ mandatory_connections = [('Links House S&Q', 'Links House'),
('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'), ('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
('Lake Hylia Central Island Teleporter', 'Dark Lake Hylia Central Island'), ('Lake Hylia Central Island Teleporter', 'Dark Lake Hylia Central Island'),
('Zoras River', 'Zoras River'), ('Zoras River', 'Zoras River'),
('Zora Waterfall Entryway', 'Zora Waterfall Entryway'),
('Zora Waterfall Water Drop', 'Light World'),
('Kings Grave Outer Rocks', 'Kings Grave Area'), ('Kings Grave Outer Rocks', 'Kings Grave Area'),
('Kings Grave Inner Rocks', 'Light World'), ('Kings Grave Inner Rocks', 'Light World'),
('Kings Grave Mirror Spot', 'Kings Grave Area'), ('Kings Grave Mirror Spot', 'Kings Grave Area'),

View File

@@ -0,0 +1,26 @@
meta:
# seed: 872603231
seed: 222336029
settings:
1:
door_shuffle: crossed
# door_shuffle: vanilla
mode: standard
shufflebosses: unique
doors:
1:
doors:
Hyrule Dungeon Cellblock Up Stairs: Thieves Basement Block Up Stairs
bosses:
1:
Thieves Town: Moldorm
#start_inventory:
# 1:
# - Titans Mitts
# - Ocarina
# - Moon Pearl
# - Tempered Sword
# - Boss Heart Container
# - Boss Heart Container
# - Boss Heart Container
# - Boss Heart Container