Merge branch 'OverworldShuffleDev' into OverworldShuffle

This commit is contained in:
codemann8
2023-08-07 16:06:46 -05:00
28 changed files with 550 additions and 161 deletions
+23 -13
View File
@@ -96,6 +96,7 @@ class World(object):
self.rooms = [] self.rooms = []
self._room_cache = {} self._room_cache = {}
self.dungeon_layouts = {} self.dungeon_layouts = {}
self.dungeon_pool = {}
self.inaccessible_regions = {} self.inaccessible_regions = {}
self.enabled_entrances = {} self.enabled_entrances = {}
self.key_logic = {} self.key_logic = {}
@@ -164,6 +165,7 @@ class World(object):
set_player_attr('colorizepots', True) set_player_attr('colorizepots', True)
set_player_attr('pot_pool', {}) set_player_attr('pot_pool', {})
set_player_attr('decoupledoors', False) set_player_attr('decoupledoors', False)
set_player_attr('door_self_loops', False)
set_player_attr('door_type_mode', 'original') set_player_attr('door_type_mode', 'original')
set_player_attr('trap_door_mode', 'optional') set_player_attr('trap_door_mode', 'optional')
set_player_attr('key_logic_algorithm', 'default') set_player_attr('key_logic_algorithm', 'default')
@@ -347,7 +349,7 @@ class World(object):
else: else:
if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']: if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
return False return False
elif self.goal[player] in ['crystals', 'trinity']: elif self.goal[player] in ['crystals', 'trinity', 'ganonhunt']:
return True return True
else: else:
return False return False
@@ -2023,6 +2025,7 @@ class Door(object):
self.dest = None self.dest = None
self.blocked = False # Indicates if the door is normally blocked off as an exit. (Sanc door or always closed) self.blocked = False # Indicates if the door is normally blocked off as an exit. (Sanc door or always closed)
self.blocked_orig = False self.blocked_orig = False
self.trapped = False
self.stonewall = False # Indicate that the door cannot be enter until exited (Desert Torches, PoD Eye Statue) self.stonewall = False # Indicate that the door cannot be enter until exited (Desert Torches, PoD Eye Statue)
self.smallKey = False # There's a small key door on this side self.smallKey = False # There's a small key door on this side
self.bigKey = False # There's a big key door on this side self.bigKey = False # There's a big key door on this side
@@ -2133,7 +2136,7 @@ class Door(object):
return self return self
def no_exit(self): def no_exit(self):
self.blocked = self.blocked_orig = True self.blocked = self.blocked_orig = self.trapped = True
return self return self
def no_entrance(self): def no_entrance(self):
@@ -2869,6 +2872,7 @@ class Spoiler(object):
'trap_door_mode': self.world.trap_door_mode, 'trap_door_mode': self.world.trap_door_mode,
'key_logic': self.world.key_logic_algorithm, 'key_logic': self.world.key_logic_algorithm,
'decoupledoors': self.world.decoupledoors, 'decoupledoors': self.world.decoupledoors,
'door_self_loops': self.world.door_self_loops,
'dungeon_counters': self.world.dungeon_counters, 'dungeon_counters': self.world.dungeon_counters,
'item_pool': self.world.difficulty, 'item_pool': self.world.difficulty,
'item_functionality': self.world.difficulty_adjustments, 'item_functionality': self.world.difficulty_adjustments,
@@ -2898,6 +2902,7 @@ class Spoiler(object):
'triforcegoal': self.world.treasure_hunt_count, 'triforcegoal': self.world.treasure_hunt_count,
'triforcepool': self.world.treasure_hunt_total, 'triforcepool': self.world.treasure_hunt_total,
'race': self.world.settings.world_rep['meta']['race'], 'race': self.world.settings.world_rep['meta']['race'],
'user_notes': self.world.settings.world_rep['meta']['user_notes'],
'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)}, 'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)},
'seed': self.world.seed 'seed': self.world.seed
} }
@@ -3047,6 +3052,8 @@ class Spoiler(object):
outfile.write('ALttP Overworld Randomizer - Seed: %s\n\n' % (self.world.seed)) outfile.write('ALttP Overworld Randomizer - Seed: %s\n\n' % (self.world.seed))
for k,v in self.metadata["versions"].items(): for k,v in self.metadata["versions"].items():
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']:
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) 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)
@@ -3112,6 +3119,7 @@ class Spoiler(object):
outfile.write('Door Type Mode:'.ljust(line_width) + '%s\n' % self.metadata['door_type_mode'][player]) outfile.write('Door Type Mode:'.ljust(line_width) + '%s\n' % self.metadata['door_type_mode'][player])
outfile.write('Trap Door Mode:'.ljust(line_width) + '%s\n' % self.metadata['trap_door_mode'][player]) outfile.write('Trap Door Mode:'.ljust(line_width) + '%s\n' % self.metadata['trap_door_mode'][player])
outfile.write('Decouple Doors:'.ljust(line_width) + '%s\n' % yn(self.metadata['decoupledoors'][player])) outfile.write('Decouple Doors:'.ljust(line_width) + '%s\n' % yn(self.metadata['decoupledoors'][player]))
outfile.write('Spiral Stairs Self-Loop:'.ljust(line_width) + '%s\n' % yn(self.metadata['door_self_loops'][player]))
outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player])) outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player]))
outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player]) outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player])
outfile.write('\n') outfile.write('\n')
@@ -3393,7 +3401,7 @@ class Pot(object):
# byte 0: DDDE EEEE (DR, ER) # byte 0: DDDE EEEE (DR, ER)
dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0, "partitioned": 3} dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0, "partitioned": 3, 'paired': 4}
er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, 'lite': 8, er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, 'lite': 8,
'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10} 'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10}
@@ -3418,10 +3426,10 @@ mixed_travel_mode = {"prevent": 0, "allow": 1, "force": 2}
pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7, pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7,
'clustered': 8, 'nonempty': 9} 'clustered': 8, 'nonempty': 9}
# byte 5: CCCC CTTX (crystals gt, ctr2, experimental) # byte 5: SCCC CTTX (self-loop doors, crystals gt, ctr2, experimental)
counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3} counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3}
# byte 6: CCCC CPAA (crystals ganon, pyramid, access # byte 6: ?CCC CPAA (crystals ganon, pyramid, access
access_mode = {"items": 0, "locations": 1, "none": 2} access_mode = {"items": 0, "locations": 1, "none": 2}
# byte 7: B?MC DDEE (big, ?, maps, compass, door_type, enemies) # byte 7: B?MC DDEE (big, ?, maps, compass, door_type, enemies)
@@ -3450,7 +3458,7 @@ flutespot_mode = {"vanilla": 0, "balanced": 1, "random": 2}
# byte 13: FBBB TTSS (flute_mode, bow_mode, take_any, small_key_mode) # byte 13: FBBB TTSS (flute_mode, bow_mode, take_any, small_key_mode)
flute_mode = {'normal': 0, 'active': 1} flute_mode = {'normal': 0, 'active': 1}
keyshuffle_mode = {'none': 0, 'wild': 1, 'universal': 2} # reserved 8 modes? keyshuffle_mode = {'none': 0, 'off': 0, 'wild': 1, 'on': 1, 'universal': 2} # reserved 8 modes?
take_any_mode = {'none': 0, 'random': 1, 'fixed': 2} take_any_mode = {'none': 0, 'random': 1, 'fixed': 2}
bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3} bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3}
@@ -3486,7 +3494,8 @@ class Settings(object):
(0x80 if w.shuffletavern[p] else 0) | (0x10 if w.dropshuffle[p] else 0) | (pottery_mode[w.pottery[p]]), (0x80 if w.shuffletavern[p] else 0) | (0x10 if w.dropshuffle[p] else 0) | (pottery_mode[w.pottery[p]]),
((8 if w.crystals_gt_orig[p] == "random" else int(w.crystals_gt_orig[p])) << 3) (0x80 if w.door_self_loops[p] else 0)
| ((8 if w.crystals_gt_orig[p] == "random" else int(w.crystals_gt_orig[p])) << 3)
| (counter_mode[w.dungeon_counters[p]] << 1) | (1 if w.experimental[p] else 0), | (counter_mode[w.dungeon_counters[p]] << 1) | (1 if w.experimental[p] else 0),
((8 if w.crystals_ganon_orig[p] == "random" else int(w.crystals_ganon_orig[p])) << 3) ((8 if w.crystals_ganon_orig[p] == "random" else int(w.crystals_ganon_orig[p])) << 3)
@@ -3512,8 +3521,8 @@ class Settings(object):
(flute_mode[w.flute_mode[p]] << 7 | bow_mode[w.bow_mode[p]] << 4 (flute_mode[w.flute_mode[p]] << 7 | bow_mode[w.bow_mode[p]] << 4
| take_any_mode[w.take_any[p]] << 2 | keyshuffle_mode[w.keyshuffle[p]]), | take_any_mode[w.take_any[p]] << 2 | keyshuffle_mode[w.keyshuffle[p]]),
((0x80 if w.pseudoboots[p] else 0) | overworld_map_mode[w.overworld_map[p]] << 6 ((0x80 if w.pseudoboots[p] else 0) | overworld_map_mode[w.overworld_map[p]] << 5
| trap_door_mode[w.trap_door_mode[p]] << 4 | key_logic_algo[w.key_logic_algorithm[p]]), | trap_door_mode[w.trap_door_mode[p]] << 3 | key_logic_algo[w.key_logic_algorithm[p]]),
]) ])
return base64.b64encode(code, "+-".encode()).decode() return base64.b64encode(code, "+-".encode()).decode()
@@ -3551,12 +3560,13 @@ class Settings(object):
args.dropshuffle[p] = True if settings[4] & 0x10 else False args.dropshuffle[p] = True if settings[4] & 0x10 else False
args.pottery[p] = r(pottery_mode)[settings[4] & 0x0F] args.pottery[p] = r(pottery_mode)[settings[4] & 0x0F]
args.door_self_loops[p] = True if settings[5] & 0x80 else False
args.dungeon_counters[p] = r(counter_mode)[(settings[5] & 0x6) >> 1] args.dungeon_counters[p] = r(counter_mode)[(settings[5] & 0x6) >> 1]
cgt = (settings[5] & 0xf8) >> 3 cgt = (settings[5] & 0x78) >> 3
args.crystals_gt[p] = "random" if cgt == 8 else cgt args.crystals_gt[p] = "random" if cgt == 8 else cgt
args.experimental[p] = True if settings[5] & 0x1 else False args.experimental[p] = True if settings[5] & 0x1 else False
cgan = (settings[6] & 0xf8) >> 3 cgan = (settings[6] & 0x78) >> 3
args.crystals_ganon[p] = "random" if cgan == 8 else cgan args.crystals_ganon[p] = "random" if cgan == 8 else cgan
args.openpyramid[p] = True if settings[6] & 0x4 else False args.openpyramid[p] = True if settings[6] & 0x4 else False
@@ -3596,8 +3606,8 @@ class Settings(object):
if len(settings) > 14: if len(settings) > 14:
args.pseudoboots[p] = True if settings[14] & 0x80 else False args.pseudoboots[p] = True if settings[14] & 0x80 else False
args.overworld_map[p] = r(overworld_map_mode)[(settings[14] & 0x60) >> 6] args.overworld_map[p] = r(overworld_map_mode)[(settings[14] & 0x60) >> 5]
args.trap_door_mode[p] = r(trap_door_mode)[(settings[14] & 0x14) >> 4] args.trap_door_mode[p] = r(trap_door_mode)[(settings[14] & 0x18) >> 3]
args.key_logic_algorithm[p] = r(key_logic_algo)[settings[14] & 0x07] args.key_logic_algorithm[p] = r(key_logic_algo)[settings[14] & 0x07]
+5
View File
@@ -1,5 +1,10 @@
# Changelog # Changelog
## 0.3.2.1
- \~Merged in DR v1.2.0.20~
- Some minor Swapped ER improvements
- Fixed generation error with Flute Activation
## 0.3.2.0 ## 0.3.2.0
- New Swapped ER mode option - New Swapped ER mode option
- Fixed issue with flipper rules not properly getting pearl requirement added - Fixed issue with flipper rules not properly getting pearl requirement added
+6 -3
View File
@@ -134,7 +134,7 @@ def parse_cli(argv, no_defaults=False):
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items', 'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items',
'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', 'triforce_max_difference',
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern', 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern',
'pseudoboots', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', 'pseudoboots', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
@@ -142,7 +142,7 @@ def parse_cli(argv, no_defaults=False):
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle', 'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx',
'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode', 'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode',
'bonk_drops', 'trap_door_mode', 'key_logic_algorithm']: 'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops']:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1: if player == 1:
setattr(ret, name, {1: value}) setattr(ret, name, {1: value})
@@ -227,6 +227,7 @@ def parse_settings():
"trap_door_mode": "optional", "trap_door_mode": "optional",
"key_logic_algorithm": "default", "key_logic_algorithm": "default",
"decoupledoors": False, "decoupledoors": False,
"door_self_loops": False,
"experimental": False, "experimental": False,
"dungeon_counters": "default", "dungeon_counters": "default",
"mixed_travel": "prevent", "mixed_travel": "prevent",
@@ -239,6 +240,7 @@ def parse_settings():
"triforce_goal_min": 0, "triforce_goal_min": 0,
"triforce_goal_max": 0, "triforce_goal_max": 0,
"triforce_min_difference": 0, "triforce_min_difference": 0,
"triforce_max_difference": 10000,
"code": "", "code": "",
"multi": 1, "multi": 1,
@@ -354,7 +356,8 @@ def parse_settings():
"outputpath": os.path.join("."), "outputpath": os.path.join("."),
"saveonexit": "ask", "saveonexit": "ask",
"outputname": "", "outputname": "",
"startinventoryarray": {} "startinventoryarray": {},
"notes": ""
} }
if sys.platform.lower().find("windows"): if sys.platform.lower().find("windows"):
+74 -57
View File
@@ -88,7 +88,8 @@ def link_doors_prep(world, player):
find_inaccessible_regions(world, player) find_inaccessible_regions(world, player)
if world.intensity[player] >= 3 and world.doorShuffle[player] != 'vanilla': create_dungeon_pool(world, player)
if world.intensity[player] >= 3 and world.doorShuffle[player] != 'vanilla':
choose_portals(world, player) choose_portals(world, player)
else: else:
if world.shuffle[player] == 'vanilla': if world.shuffle[player] == 'vanilla':
@@ -133,6 +134,20 @@ def create_dungeon_pool(world, player):
pool = None pool = None
if world.doorShuffle[player] == 'basic': if world.doorShuffle[player] == 'basic':
pool = [([name], regions) for name, regions in dungeon_regions.items()] pool = [([name], regions) for name, regions in dungeon_regions.items()]
elif world.doorShuffle[player] == 'paired':
dungeon_pool = list(dungeon_regions.keys())
groups = []
while dungeon_pool:
if len(dungeon_pool) == 3:
groups.append(list(dungeon_pool))
dungeon_pool.clear()
else:
choice_a = random.choice(dungeon_pool)
dungeon_pool.remove(choice_a)
choice_b = random.choice(dungeon_pool)
dungeon_pool.remove(choice_b)
groups.append([choice_a, choice_b])
pool = [(group, list(chain.from_iterable([dungeon_regions[d] for d in group]))) for group in groups]
elif world.doorShuffle[player] == 'partitioned': elif world.doorShuffle[player] == 'partitioned':
groups = [['Hyrule Castle', 'Eastern Palace', 'Desert Palace', 'Tower of Hera', 'Agahnims Tower'], groups = [['Hyrule Castle', 'Eastern Palace', 'Desert Palace', 'Tower of Hera', 'Agahnims Tower'],
['Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town'], ['Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town'],
@@ -143,38 +158,17 @@ def create_dungeon_pool(world, player):
elif world.doorShuffle[player] != 'vanilla': elif world.doorShuffle[player] != 'vanilla':
logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player]) logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player])
raise Exception('Invalid door shuffle setting: %s' % world.doorShuffle[player]) raise Exception('Invalid door shuffle setting: %s' % world.doorShuffle[player])
return pool world.dungeon_pool[player] = pool
def link_doors_main(world, player): def link_doors_main(world, player):
pool = create_dungeon_pool(world, player) pool = world.dungeon_pool[player]
if pool: if pool:
main_dungeon_pool(pool, world, player) main_dungeon_pool(pool, world, player)
if world.doorShuffle[player] != 'vanilla': if world.doorShuffle[player] != 'vanilla':
create_door_spoiler(world, player) create_door_spoiler(world, player)
# todo: I think this function is not necessary
def mark_regions(world, player):
# traverse dungeons and make sure dungeon property is assigned
player_dungeons = [dungeon for dungeon in world.dungeons if dungeon.player == player]
for dungeon in player_dungeons:
queue = deque(dungeon.regions)
while len(queue) > 0:
region = world.get_region(queue.popleft(), player)
if region.name not in dungeon.regions:
dungeon.regions.append(region.name)
region.dungeon = dungeon
for ext in region.exits:
d = world.check_for_door(ext.name, player)
connected = ext.connected_region
if d is not None and connected is not None:
if d.dest is not None and connected.name not in dungeon.regions and connected.type == RegionType.Dungeon and connected.name not in queue:
queue.append(connected) # needs to be added
elif connected is not None and connected.name not in dungeon.regions and connected.type == RegionType.Dungeon and connected.name not in queue:
queue.append(connected) # needs to be added
def create_door_spoiler(world, player): def create_door_spoiler(world, player):
logger = logging.getLogger('') logger = logging.getLogger('')
shuffled_door_types = [DoorType.Normal, DoorType.SpiralStairs] shuffled_door_types = [DoorType.Normal, DoorType.SpiralStairs]
@@ -438,17 +432,7 @@ def pair_existing_key_doors(world, player, door_a, door_b):
def choose_portals(world, player): def choose_portals(world, player):
if world.doorShuffle[player] != ['vanilla']: if world.doorShuffle[player] != ['vanilla']:
shuffle_flag = world.doorShuffle[player] != 'basic' shuffle_flag = world.doorShuffle[player] != 'basic'
allowed = {} allowed = {name: set(group[0]) for group in world.dungeon_pool[player] for name in group[0]}
if world.doorShuffle[player] == 'basic':
allowed = {name: {name} for name in dungeon_regions}
elif world.doorShuffle[player] == 'partitioned':
groups = [['Hyrule Castle', 'Eastern Palace', 'Desert Palace', 'Tower of Hera', 'Agahnims Tower'],
['Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town'],
['Ice Palace', 'Misery Mire', 'Turtle Rock', 'Ganons Tower']]
allowed = {name: set(group) for group in groups for name in group}
elif world.doorShuffle[player] == 'crossed':
all_dungeons = set(dungeon_regions.keys())
allowed = {name: all_dungeons for name in dungeon_regions}
# key drops allow the big key in the right place in Desert Tiles 2 # key drops allow the big key in the right place in Desert Tiles 2
bk_shuffle = world.bigkeyshuffle[player] or world.pottery[player] not in ['none', 'cave'] bk_shuffle = world.bigkeyshuffle[player] or world.pottery[player] not in ['none', 'cave']
@@ -593,7 +577,7 @@ def customizer_portals(master_door_list, world, player):
assigned_doors.add(door) assigned_doors.add(door)
# restricts connected doors to the customized portals # restricts connected doors to the customized portals
if assigned_doors: if assigned_doors:
pool = create_dungeon_pool(world, player) pool = world.dungeon_pool[player]
if pool: if pool:
pool_map = {} pool_map = {}
for pool, region_list in pool: for pool, region_list in pool:
@@ -1860,12 +1844,12 @@ def shuffle_trap_doors(door_type_pools, paths, start_regions_map, all_custom, wo
builder.candidates.trap = filter_key_door_pool(builder.candidates.trap, all_custom[dungeon]) builder.candidates.trap = filter_key_door_pool(builder.candidates.trap, all_custom[dungeon])
remaining -= len(custom_trap_doors[dungeon]) remaining -= len(custom_trap_doors[dungeon])
ttl += len(builder.candidates.trap) ttl += len(builder.candidates.trap)
if ttl == 0: if ttl == 0 and all(len(custom_trap_doors[dungeon]) == 0 for dungeon in pool):
continue continue
for dungeon in pool: for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon] builder = world.dungeon_layouts[player][dungeon]
proportion = len(builder.candidates.trap) proportion = len(builder.candidates.trap)
calc = int(round(proportion * door_type_pool.traps/ttl)) calc = 0 if ttl == 0 else int(round(proportion * door_type_pool.traps/ttl))
suggested = min(proportion, calc) suggested = min(proportion, calc)
remaining -= suggested remaining -= suggested
suggestion_map[dungeon] = suggested suggestion_map[dungeon] = suggested
@@ -1963,7 +1947,7 @@ def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, all_cu
if flex_map[dungeon] > 0: if flex_map[dungeon] > 0:
queue.append(dungeon) queue.append(dungeon)
# time to re-assign # time to re-assign
reassign_big_key_doors(bk_map, world, player) reassign_big_key_doors(bk_map, used_doors, world, player)
for name, big_list in bk_map.items(): for name, big_list in bk_map.items():
used_doors.update(flatten_pair_list(big_list)) used_doors.update(flatten_pair_list(big_list))
return used_doors return used_doors
@@ -1997,7 +1981,10 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_
remaining = max(0, remaining) remaining = max(0, remaining)
for dungeon in pool: for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon] builder = world.dungeon_layouts[player][dungeon]
calculated = int(round(builder.key_doors_num*total_keys/ttl)) if ttl == 0:
calculated = 0
else:
calculated = int(round(builder.key_doors_num*total_keys/ttl))
max_keys = max(0, builder.location_cnt - calc_used_dungeon_items(builder, world, player)) max_keys = max(0, builder.location_cnt - calc_used_dungeon_items(builder, world, player))
cand_len = max(0, len(builder.candidates.small) - builder.key_drop_cnt) cand_len = max(0, len(builder.candidates.small) - builder.key_drop_cnt)
limit = min(max_keys, cand_len, max_computation) limit = min(max_keys, cand_len, max_computation)
@@ -2048,7 +2035,7 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_
else: else:
builder.key_doors_num -= 1 builder.key_doors_num -= 1
# time to re-assign # time to re-assign
reassign_key_doors(small_map, world, player) reassign_key_doors(small_map, used_doors, world, player)
for dungeon_name in pool: for dungeon_name in pool:
if world.keyshuffle[player] != 'universal': if world.keyshuffle[player] != 'universal':
builder = world.dungeon_layouts[player][dungeon_name] builder = world.dungeon_layouts[player][dungeon_name]
@@ -2130,7 +2117,7 @@ def shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, all_
suggestion_map[dungeon] = pair suggestion_map[dungeon] = pair
queue.append(dungeon) queue.append(dungeon)
# time to re-assign # time to re-assign
reassign_bd_doors(bd_map, world, player) reassign_bd_doors(bd_map, used_doors, world, player)
for name, pair in bd_map.items(): for name, pair in bd_map.items():
used_doors.update(flatten_pair_list(pair[0])) used_doors.update(flatten_pair_list(pair[0]))
used_doors.update(flatten_pair_list(pair[1])) used_doors.update(flatten_pair_list(pair[1]))
@@ -2227,9 +2214,10 @@ def find_valid_trap_combination(builder, suggested, start_regions, paths, world,
sample_list = build_sample_list(combinations, 1000) sample_list = build_sample_list(combinations, 1000)
proposal = kth_combination(sample_list[itr], trap_door_pool, trap_doors_needed) proposal = kth_combination(sample_list[itr], trap_door_pool, trap_doors_needed)
proposal.extend(custom_trap_doors) proposal.extend(custom_trap_doors)
filtered_proposal = [x for x in proposal if x.name not in trap_door_exceptions]
start_regions, event_starts = filter_start_regions(builder, start_regions, world, player) start_regions, event_starts = filter_start_regions(builder, start_regions, world, player)
while not validate_trap_layout(proposal, builder, start_regions, paths, world, player): while not validate_trap_layout(filtered_proposal, builder, start_regions, paths, world, player):
itr += 1 itr += 1
if itr >= len(sample_list): if itr >= len(sample_list):
if not drop: if not drop:
@@ -2264,6 +2252,12 @@ def filter_start_regions(builder, start_regions, world, player):
portal_entrance_region = portal.door.entrance.parent_region.name portal_entrance_region = portal.door.entrance.parent_region.name
if portal_entrance_region not in builder.path_entrances: if portal_entrance_region not in builder.path_entrances:
excluded[region] = None excluded[region] = None
if not portal:
drop_region = next((x.parent_region for x in region.entrances
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]
or x.parent_region.name == 'Sewer Drop'), None)
if drop_region and drop_region.name in world.inaccessible_regions[player]:
excluded[region] = None
if std_flag and (not portal or portal.find_portal_entrance().parent_region.name != 'Hyrule Castle Courtyard'): if std_flag and (not portal or portal.find_portal_entrance().parent_region.name != 'Hyrule Castle Courtyard'):
excluded[region] = None excluded[region] = None
if portal is None: if portal is None:
@@ -2359,10 +2353,12 @@ def reassign_trap_doors(trap_map, world, player):
elif kind in [DoorKind.Trap2, DoorKind.TrapTriggerable]: elif kind in [DoorKind.Trap2, DoorKind.TrapTriggerable]:
room.change(d.doorListPos, DoorKind.Normal) room.change(d.doorListPos, DoorKind.Normal)
d.blocked = False d.blocked = False
d.trapped = False
# connect_one_way(world, d.name, d.dest.name, player) # connect_one_way(world, d.name, d.dest.name, player)
elif d.type is DoorType.Normal and d not in traps: elif d.type is DoorType.Normal and d not in traps:
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
d.blocked = False d.blocked = False
d.trapped = False
for d in traps: for d in traps:
change_door_to_trap(d, world, player) change_door_to_trap(d, world, player)
world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Trap Door', player) world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Trap Door', player)
@@ -2400,24 +2396,45 @@ def change_door_to_trap(d, world, player):
elif d.direction in [Direction.North, Direction.West]: elif d.direction in [Direction.North, Direction.West]:
new_kind = DoorKind.TrapTriggerable new_kind = DoorKind.TrapTriggerable
if new_kind: if new_kind:
d.blocked = True d.blocked = is_trap_door_blocked(d)
d.trapped = True
pos = 3 if d.type == DoorType.Normal else 4 pos = 3 if d.type == DoorType.Normal else 4
verify_door_list_pos(d, room, world, player, pos) verify_door_list_pos(d, room, world, player, pos)
d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1, 3: 0x8}[d.doorListPos] d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1, 3: 0x8}[d.doorListPos]
room.change(d.doorListPos, new_kind) room.change(d.doorListPos, new_kind)
if d.entrance.connected_region is not None: if d.entrance.connected_region is not None and d.blocked:
d.entrance.connected_region.entrances.remove(d.entrance) d.entrance.connected_region.entrances.remove(d.entrance)
d.entrance.connected_region = None d.entrance.connected_region = None
elif d.type is DoorType.Normal: elif d.type is DoorType.Normal:
d.blocked = True d.blocked = is_trap_door_blocked(d)
d.trapped = True
verify_door_list_pos(d, room, world, player, pos=3) verify_door_list_pos(d, room, world, player, pos=3)
d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1}[d.doorListPos] d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1}[d.doorListPos]
room.change(d.doorListPos, DoorKind.Trap) room.change(d.doorListPos, DoorKind.Trap)
if d.entrance.connected_region is not None: if d.entrance.connected_region is not None and d.blocked:
d.entrance.connected_region.entrances.remove(d.entrance) d.entrance.connected_region.entrances.remove(d.entrance)
d.entrance.connected_region = None d.entrance.connected_region = None
trap_door_exceptions = {
'PoD Mimics 2 SW', 'TR Twin Pokeys NW', 'Thieves Blocked Entry SW', 'Hyrule Dungeon Armory Interior Key Door N',
'Desert Compass Key Door WN', 'TR Tile Room SE', 'Mire Cross SW', 'Tower Circle of Pots ES',
'Eastern Single Eyegore ES', 'Eastern Duo Eyegores SE', 'Swamp Push Statue S',
'Skull 2 East Lobby WS', 'GT Hope Room WN', 'Eastern Courtyard Ledge S', 'Ice Lobby SE', 'GT Speed Torch WN',
'Ice Switch Room ES', 'Ice Switch Room NE', 'Skull Torch Room WS', 'GT Speed Torch NE', 'GT Speed Torch WS',
'GT Torch Cross WN', 'Mire Tile Room SW', 'Mire Tile Room ES', 'TR Torches WN', 'PoD Lobby N', 'PoD Middle Cage S',
'Ice Bomb Jump NW', 'GT Hidden Spikes SE', 'Ice Tall Hint EN', 'GT Conveyor Cross EN', 'Eastern Pot Switch WN',
'Thieves Conveyor Maze WN', 'Thieves Conveyor Maze SW', 'Eastern Dark Square Key Door WN', 'Eastern Lobby NW',
'Eastern Lobby NE', 'Ice Cross Bottom SE', 'Desert Back Lobby S', 'Desert West S',
'Desert West Lobby ES', 'Mire Hidden Shooters SE', 'Mire Hidden Shooters ES', 'Mire Hidden Shooters WS',
'Tower Dark Pits EN', 'Tower Dark Maze ES', 'TR Tongue Pull WS',
}
def is_trap_door_blocked(door):
return door.name not in trap_door_exceptions
def find_big_key_candidates(builder, start_regions, used, world, player): def find_big_key_candidates(builder, start_regions, used, world, player):
if world.door_type_mode[player] != 'original': # big, all, chaos if world.door_type_mode[player] != 'original': # big, all, chaos
# traverse dungeon and find candidates # traverse dungeon and find candidates
@@ -2540,7 +2557,7 @@ def find_current_bk_doors(builder):
return current_doors return current_doors
def reassign_big_key_doors(bk_map, world, player): def reassign_big_key_doors(bk_map, used_doors, world, player):
logger = logging.getLogger('') logger = logging.getLogger('')
for name, big_doors in bk_map.items(): for name, big_doors in bk_map.items():
flat_proposal = flatten_pair_list(big_doors) flat_proposal = flatten_pair_list(big_doors)
@@ -2549,11 +2566,11 @@ def reassign_big_key_doors(bk_map, world, player):
while len(queue) > 0: while len(queue) > 0:
d = queue.pop() d = queue.pop()
if d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal: if d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal:
if not d.entranceFlag: if not d.entranceFlag and d not in used_doors and d.dest not in used_doors:
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
d.bigKey = False d.bigKey = False
elif d.type is DoorType.Normal and d not in flat_proposal: elif d.type is DoorType.Normal and d not in flat_proposal :
if not d.entranceFlag: if not d.entranceFlag and d not in used_doors:
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
d.bigKey = False d.bigKey = False
for obj in big_doors: for obj in big_doors:
@@ -2796,7 +2813,7 @@ def find_valid_bd_combination(builder, suggested, world, player):
return bomb_proposal, dash_proposal, ttl_needed return bomb_proposal, dash_proposal, ttl_needed
def reassign_bd_doors(bd_map, world, player): def reassign_bd_doors(bd_map, used_doors, world, player):
for name, pair in bd_map.items(): for name, pair in bd_map.items():
flat_bomb_proposal = flatten_pair_list(pair[0]) flat_bomb_proposal = flatten_pair_list(pair[0])
flat_dash_proposal = flatten_pair_list(pair[1]) flat_dash_proposal = flatten_pair_list(pair[1])
@@ -2809,10 +2826,10 @@ def reassign_bd_doors(bd_map, world, player):
queue = deque(find_current_bd_doors(builder, world)) queue = deque(find_current_bd_doors(builder, world))
while len(queue) > 0: while len(queue) > 0:
d = queue.pop() d = queue.pop()
if d.type is DoorType.Interior and not_in_proposal(d): if d.type is DoorType.Interior and not_in_proposal(d) and d not in used_doors and d.dest not in used_doors:
if not d.entranceFlag: if not d.entranceFlag:
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
elif d.type is DoorType.Normal and not_in_proposal(d): elif d.type is DoorType.Normal and not_in_proposal(d) and d not in used_doors:
if not d.entranceFlag: if not d.entranceFlag:
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
do_bombable_dashable(pair[0], DoorKind.Bombable, world, player) do_bombable_dashable(pair[0], DoorKind.Bombable, world, player)
@@ -3004,7 +3021,7 @@ def valid_key_door_pair(door1, door2):
return len(door1.entrance.parent_region.exits) <= 1 or len(door2.entrance.parent_region.exits) <= 1 return len(door1.entrance.parent_region.exits) <= 1 or len(door2.entrance.parent_region.exits) <= 1
def reassign_key_doors(small_map, world, player): def reassign_key_doors(small_map, used_doors, world, player):
logger = logging.getLogger('') logger = logging.getLogger('')
for name, small_doors in small_map.items(): for name, small_doors in small_map.items():
logger.debug(f'Key doors for {name}') logger.debug(f'Key doors for {name}')
@@ -3025,13 +3042,13 @@ def reassign_key_doors(small_map, world, player):
room.delete(d.doorListPos) room.delete(d.doorListPos)
d.smallKey = False d.smallKey = False
elif d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal: elif d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal:
if not d.entranceFlag: if not d.entranceFlag and d not in used_doors and d.dest not in used_doors:
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
d.smallKey = False d.smallKey = False
d.dest.smallKey = False d.dest.smallKey = False
queue.remove(d.dest) queue.remove(d.dest)
elif d.type is DoorType.Normal and d not in flat_proposal: elif d.type is DoorType.Normal and d not in flat_proposal:
if not d.entranceFlag: if not d.entranceFlag and d not in used_doors:
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
d.smallKey = False d.smallKey = False
for dp in world.paired_doors[player]: for dp in world.paired_doors[player]:
+22 -10
View File
@@ -1677,10 +1677,24 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p
random.shuffle(sector_list) random.shuffle(sector_list)
orig_location_set = build_orig_location_set(dungeon_map) orig_location_set = build_orig_location_set(dungeon_map)
num_dungeon_items = requested_dungeon_items(world, player) num_dungeon_items = requested_dungeon_items(world, player)
locations_to_distribute = sum(sector.chest_locations for sector in free_location_sectors.keys())
reserved_per_dungeon = {d_name: count_reserved_locations(world, player, orig_location_set[d_name])
for d_name in dungeon_map.keys()}
base_free, found_enough = 2, False
while not found_enough:
needed = sum(max(0, max(base_free, reserved_per_dungeon[d]) + num_dungeon_items - len(orig_location_set[d]))
for d in dungeon_map.keys())
if needed > locations_to_distribute:
if base_free == 0:
raise Exception('Unable to meet minimum requirements, check for customizer problems')
base_free -= 1
else:
found_enough = True
d_idx = {builder.name: i for i, builder in enumerate(dungeon_map.values())} d_idx = {builder.name: i for i, builder in enumerate(dungeon_map.values())}
next_sector = sector_list.pop() next_sector = sector_list.pop()
while not valid: while not valid:
choice, totals, location_set = weighted_random_location(dungeon_map, choices, orig_location_set, world, player) choice, totals, location_set = weighted_random_location(dungeon_map, choices, orig_location_set,
base_free, world, player)
if not choice: if not choice:
break break
choices[choice].append(next_sector) choices[choice].append(next_sector)
@@ -1691,7 +1705,7 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p
valid = True valid = True
for d_name, idx in d_idx.items(): for d_name, idx in d_idx.items():
free_items = count_reserved_locations(world, player, location_set[d_name]) free_items = count_reserved_locations(world, player, location_set[d_name])
target = max(free_items, 2) + num_dungeon_items target = max(free_items, base_free) + num_dungeon_items
if totals[idx] < target: if totals[idx] < target:
valid = False valid = False
break break
@@ -1699,8 +1713,7 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p
if len(sector_list) == 0: if len(sector_list) == 0:
choices = defaultdict(list) choices = defaultdict(list)
sector_list = list(free_location_sectors) sector_list = list(free_location_sectors)
else: next_sector = sector_list.pop()
next_sector = sector_list.pop()
else: else:
choices[choice].remove(next_sector) choices[choice].remove(next_sector)
for builder, choice_list in choices.items(): for builder, choice_list in choices.items():
@@ -1709,7 +1722,7 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p
return free_location_sectors return free_location_sectors
def weighted_random_location(dungeon_map, choices, orig_location_set, world, player): def weighted_random_location(dungeon_map, choices, orig_location_set, base_free, world, player):
population = [] population = []
totals = [] totals = []
location_set = {x: set(y) for x, y in orig_location_set.items()} location_set = {x: set(y) for x, y in orig_location_set.items()}
@@ -1720,7 +1733,7 @@ def weighted_random_location(dungeon_map, choices, orig_location_set, world, pla
builder_set = location_set[dungeon_builder.name] builder_set = location_set[dungeon_builder.name]
builder_set.update(set().union(*(s.chest_location_set for s in choices[dungeon_builder]))) builder_set.update(set().union(*(s.chest_location_set for s in choices[dungeon_builder])))
free_items = count_reserved_locations(world, player, builder_set) free_items = count_reserved_locations(world, player, builder_set)
target = max(free_items, 2) + num_dungeon_items target = max(free_items, base_free) + num_dungeon_items
if ttl < target: if ttl < target:
population.append(dungeon_builder) population.append(dungeon_builder)
choice = random.choice(population) if len(population) > 0 else None choice = random.choice(population) if len(population) > 0 else None
@@ -1775,7 +1788,7 @@ def count_reserved_locations(world, player, proposed_set):
return 2 return 2
def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole, assign_one=False): def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole):
population = [] population = []
some_c_switches_present = False some_c_switches_present = False
for name, builder in dungeon_map.items(): for name, builder in dungeon_map.items():
@@ -1784,7 +1797,7 @@ def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barrier
if builder.c_switch_present and not builder.c_locked: if builder.c_switch_present and not builder.c_locked:
some_c_switches_present = True some_c_switches_present = True
if len(population) == 0: # nothing needs a switch if len(population) == 0: # nothing needs a switch
if assign_one and not some_c_switches_present: # something should have one if len(crystal_barriers) > 0 and not some_c_switches_present: # something should have one
if len(crystal_switches) == 0: if len(crystal_switches) == 0:
raise GenerationException('No crystal switches to assign. Ref %s' % next(iter(dungeon_map.keys()))) raise GenerationException('No crystal switches to assign. Ref %s' % next(iter(dungeon_map.keys())))
valid, builder_choice, switch_choice = False, None, None valid, builder_choice, switch_choice = False, None, None
@@ -3139,8 +3152,7 @@ def balance_split(candidate_sectors, dungeon_map, global_pole, builder_info):
check_for_forced_assignments(dungeon_map, candidate_sectors, global_pole) check_for_forced_assignments(dungeon_map, candidate_sectors, global_pole)
check_for_forced_crystal(dungeon_map, candidate_sectors, global_pole) check_for_forced_crystal(dungeon_map, candidate_sectors, global_pole)
crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors) crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors)
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole)
global_pole, len(crystal_barriers) > 0)
ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole) ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole)
for sector in leftover: for sector in leftover:
if sector.polarity().is_neutral(): if sector.polarity().is_neutral():
+26 -30
View File
@@ -212,7 +212,8 @@ def generate_itempool(world, player):
loc.locked = True loc.locked = True
loc.forced_item = loc.item loc.forced_item = loc.item
if not world.is_tile_swapped(0x18, player): if (world.flute_mode[player] != 'active' and not world.is_tile_swapped(0x18, player)
and 'Ocarina (Activated)' not in list(map(str, [i for i in world.precollected_items if i.player == player]))):
region = world.get_region('Kakariko Village',player) region = world.get_region('Kakariko Village',player)
loc = Location(player, "Flute Activation", parent=region) loc = Location(player, "Flute Activation", parent=region)
@@ -1361,19 +1362,6 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_total, treasure_hunt_icon, lamps_needed_for_dark_rooms) return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_total, treasure_hunt_icon, lamps_needed_for_dark_rooms)
def set_default_triforce(goal, custom_goal, custom_total):
triforce_goal, triforce_total = 0, 0
if goal == 'triforcehunt':
triforce_goal, triforce_total = 20, 30
elif goal == 'trinity':
triforce_goal, triforce_total = 8, 10
if custom_goal > 0:
triforce_goal = max(min(custom_goal, 128), 1)
if custom_total > 0 or custom_goal > 0:
triforce_total = max(min(custom_total, 128), triforce_goal) #128 max to ensure other progression can fit.
return (triforce_goal, triforce_total)
def make_customizer_pool(world, player): def make_customizer_pool(world, player):
pool = [] pool = []
placed_items = {} placed_items = {}
@@ -1486,7 +1474,8 @@ def make_customizer_pool(world, player):
bow_found = next((i for i in pool if i in {'Bow', 'Progressive Bow'}), None) bow_found = next((i for i in pool if i in {'Bow', 'Progressive Bow'}), None)
if not bow_found: if not bow_found:
missing_items.append('Progressive Bow') missing_items.append('Progressive Bow')
logging.getLogger('').warning(f'The following items are not in the custom item pool {", ".join(missing_items)}') if missing_items:
logging.getLogger('').warning(f'The following items are not in the custom item pool {", ".join(missing_items)}')
g, t = set_default_triforce(world.goal[player], world.treasure_hunt_count[player], g, t = set_default_triforce(world.goal[player], world.treasure_hunt_count[player],
world.treasure_hunt_total[player]) world.treasure_hunt_total[player])
@@ -1495,20 +1484,23 @@ def make_customizer_pool(world, player):
if pieces < t: if pieces < t:
pool.extend(['Triforce Piece'] * (t - pieces)) pool.extend(['Triforce Piece'] * (t - pieces))
if not world.customizer.get_start_inventory(): sphere_0 = world.customizer.get_start_inventory()
if world.logic[player] in ['owglitches', 'nologic']: no_start_inventory = not sphere_0 or not sphere_0[player]
precollected_items.append('Pegasus Boots') init_equip = [] if no_start_inventory else sphere_0[player]
if 'Pegasus Boots' in pool: if (world.logic[player] in ['owglitches', 'nologic']
pool.remove('Pegasus Boots') and (no_start_inventory or all(x != 'Pegasus Boots' for x in init_equip))):
pool.append('Rupees (20)') precollected_items.append('Pegasus Boots')
if world.swords[player] == 'assured': if 'Pegasus Boots' in pool:
precollected_items.append('Progressive Sword') pool.remove('Pegasus Boots')
if 'Progressive Sword' in pool: pool.append('Rupees (20)')
pool.remove('Progressive Sword') if world.swords[player] == 'assured' and (no_start_inventory or all(' Sword' not in x for x in init_equip)):
pool.append('Rupees (50)') precollected_items.append('Progressive Sword')
elif 'Fighter Sword' in pool: if 'Progressive Sword' in pool:
pool.remove('Fighter Sword') pool.remove('Progressive Sword')
pool.append('Rupees (50)') pool.append('Rupees (50)')
elif 'Fighter Sword' in pool:
pool.remove('Fighter Sword')
pool.append('Rupees (50)')
return pool, placed_items, precollected_items, clock_mode, 1 return pool, placed_items, precollected_items, clock_mode, 1
@@ -1577,10 +1569,13 @@ def fill_specific_items(world):
dungeon_pool, prize_set, prize_pool) dungeon_pool, prize_set, prize_pool)
if item_to_place: if item_to_place:
world.push_item(loc, item_to_place, False) world.push_item(loc, item_to_place, False)
loc.locked = True
track_outside_keys(item_to_place, loc, world) track_outside_keys(item_to_place, loc, world)
track_dungeon_items(item_to_place, loc, world) track_dungeon_items(item_to_place, loc, world)
loc.event = (event_flag or item_to_place.advancement loc.event = (event_flag or item_to_place.advancement
or item_to_place.bigkey or item_to_place.smallkey) or item_to_place.bigkey or item_to_place.smallkey)
else:
raise Exception(f'Did not find "{item}" in item pool to place at "{location}"')
advanced_placements = world.customizer.get_advanced_placements() advanced_placements = world.customizer.get_advanced_placements()
if advanced_placements: if advanced_placements:
for player, placement_list in advanced_placements.items(): for player, placement_list in advanced_placements.items():
@@ -1590,7 +1585,7 @@ def fill_specific_items(world):
item_to_place, event_flag = get_item_and_event_flag(item, world, player, item_to_place, event_flag = get_item_and_event_flag(item, world, player,
dungeon_pool, prize_set, prize_pool) dungeon_pool, prize_set, prize_pool)
if not item_to_place: if not item_to_place:
continue raise Exception(f'Did not find "{item}" in item pool to place for a LocationGroup"')
locations = placement['locations'] locations = placement['locations']
handled = False handled = False
while not handled: while not handled:
@@ -1610,6 +1605,7 @@ def fill_specific_items(world):
if loc.item: if loc.item:
continue continue
world.push_item(loc, item_to_place, False) world.push_item(loc, item_to_place, False)
loc.locked = True
track_outside_keys(item_to_place, loc, world) track_outside_keys(item_to_place, loc, world)
track_dungeon_items(item_to_place, loc, world) track_dungeon_items(item_to_place, loc, world)
loc.event = (event_flag or item_to_place.advancement loc.event = (event_flag or item_to_place.advancement
+28 -5
View File
@@ -36,7 +36,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_number = '1.2.0.17' version_number = '1.2.0.20'
version_branch = '-u' version_branch = '-u'
__version__ = f'{version_number}{version_branch}' __version__ = f'{version_number}{version_branch}'
@@ -122,6 +122,7 @@ def main(args, seed=None, fish=None):
world.trap_door_mode = args.trap_door_mode.copy() world.trap_door_mode = args.trap_door_mode.copy()
world.key_logic_algorithm = args.key_logic_algorithm.copy() world.key_logic_algorithm = args.key_logic_algorithm.copy()
world.decoupledoors = args.decoupledoors.copy() world.decoupledoors = args.decoupledoors.copy()
world.door_self_loops = args.door_self_loops.copy()
world.experimental = args.experimental.copy() world.experimental = args.experimental.copy()
world.dungeon_counters = args.dungeon_counters.copy() world.dungeon_counters = args.dungeon_counters.copy()
world.fish = fish world.fish = fish
@@ -131,8 +132,6 @@ def main(args, seed=None, fish=None):
world.potshuffle = args.shufflepots.copy() world.potshuffle = args.shufflepots.copy()
world.mixed_travel = args.mixed_travel.copy() world.mixed_travel = args.mixed_travel.copy()
world.standardize_palettes = args.standardize_palettes.copy() world.standardize_palettes = args.standardize_palettes.copy()
world.treasure_hunt_count = {k: int(v) for k, v in args.triforce_goal.items()}
world.treasure_hunt_total = {k: int(v) for k, v in args.triforce_pool.items()}
world.shufflelinks = args.shufflelinks.copy() world.shufflelinks = args.shufflelinks.copy()
world.shuffletavern = args.shuffletavern.copy() world.shuffletavern = args.shuffletavern.copy()
world.pseudoboots = args.pseudoboots.copy() world.pseudoboots = args.pseudoboots.copy()
@@ -142,6 +141,26 @@ def main(args, seed=None, fish=None):
world.collection_rate = args.collection_rate.copy() world.collection_rate = args.collection_rate.copy()
world.colorizepots = args.colorizepots.copy() world.colorizepots = args.colorizepots.copy()
world.treasure_hunt_count = {}
world.treasure_hunt_total = {}
for p in args.triforce_goal:
if int(args.triforce_goal[p]) != 0 or int(args.triforce_pool[p]) != 0 or int(args.triforce_goal_min[p]) != 0 or int(args.triforce_goal_max[p]) != 0 or int(args.triforce_pool_min[p]) != 0 or int(args.triforce_pool_max[p]) != 0:
if int(args.triforce_goal[p]) != 0:
world.treasure_hunt_count[p] = int(args.triforce_goal[p])
elif int(args.triforce_goal_min[p]) != 0 and int(args.triforce_goal_max[p]) != 0:
world.treasure_hunt_count[p] = random.randint(int(args.triforce_goal_min[p]), int(args.triforce_goal_max[p]))
else:
world.treasure_hunt_count[p] = 8 if world.goal[p] == 'trinity' else 20
if int(args.triforce_pool[p]) != 0:
world.treasure_hunt_total[p] = int(args.triforce_pool[p])
elif int(args.triforce_pool_min[p]) != 0 and int(args.triforce_pool_max[p]) != 0:
world.treasure_hunt_total[p] = random.randint(max(int(args.triforce_pool_min[p]), world.treasure_hunt_count[p] + int(args.triforce_min_difference[p])), min(int(args.triforce_pool_max[p]), world.treasure_hunt_count[p] + int(args.triforce_max_difference[p])))
else:
world.treasure_hunt_total[p] = 10 if world.goal[p] == 'trinity' else 30
else:
# this will be handled in ItemList.py and custom item pool is used to determine the numbers
world.treasure_hunt_count[p], world.treasure_hunt_total[p] = 0, 0
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
world.finish_init() world.finish_init()
@@ -165,7 +184,7 @@ def main(args, seed=None, fish=None):
world.player_names[player].append(name) world.player_names[player].append(name)
logger.info('') logger.info('')
world.settings = CustomSettings() world.settings = CustomSettings()
world.settings.create_from_world(world, args.race) world.settings.create_from_world(world, args)
outfilebase = f'OR_{args.outputname if args.outputname else world.seed}' outfilebase = f'OR_{args.outputname if args.outputname else world.seed}'
@@ -501,6 +520,7 @@ def copy_world(world):
ret.beemizer = world.beemizer.copy() ret.beemizer = world.beemizer.copy()
ret.intensity = world.intensity.copy() ret.intensity = world.intensity.copy()
ret.decoupledoors = world.decoupledoors.copy() ret.decoupledoors = world.decoupledoors.copy()
ret.door_self_loops = world.door_self_loops.copy()
ret.experimental = world.experimental.copy() ret.experimental = world.experimental.copy()
ret.shopsanity = world.shopsanity.copy() ret.shopsanity = world.shopsanity.copy()
ret.dropshuffle = world.dropshuffle.copy() ret.dropshuffle = world.dropshuffle.copy()
@@ -814,7 +834,9 @@ def create_playthrough(world):
logging.getLogger('').debug(world.fish.translate("cli", "cli", "building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations)) logging.getLogger('').debug(world.fish.translate("cli", "cli", "building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations))
if not sphere: if not sphere:
logging.getLogger('').error(world.fish.translate("cli", "cli", "cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates]) if world.accessibility[location.item.player] != 'none':
logging.getLogger('').error(world.fish.translate("cli", "cli", "cannot.reach.items"),
[world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
if any([location.name not in optional_locations and world.accessibility[location.item.player] != 'none' for location in sphere_candidates]): if any([location.name not in optional_locations and world.accessibility[location.item.player] != 'none' for location in sphere_candidates]):
raise RuntimeError(world.fish.translate("cli", "cli", "cannot.reach.progression")) raise RuntimeError(world.fish.translate("cli", "cli", "cannot.reach.progression"))
else: else:
@@ -877,6 +899,7 @@ def create_playthrough(world):
if world.has_beaten_game(state): if world.has_beaten_game(state):
required_locations.clear() required_locations.clear()
else: else:
logging.getLogger('').error(world.fish.translate("cli", "cli", "cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (loc.item.name, loc.item.player, loc.name, loc.player) for loc in required_locations])
raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.required")) raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.required"))
# store the required locations for statistical analysis # store the required locations for statistical analysis
+12 -3
View File
@@ -66,6 +66,8 @@ class Context:
self.lookup_name_to_id = {} self.lookup_name_to_id = {}
self.lookup_id_to_name = {} self.lookup_id_to_name = {}
self.pottery_locations_enabled = None
def color_code(*args): def color_code(*args):
codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34, codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43, 'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
@@ -96,6 +98,8 @@ SHOP_SRAM_START = WRAM_START + 0x0164B8 # 2 bytes?
ITEM_SRAM_SIZE = 0x250 ITEM_SRAM_SIZE = 0x250
SHOP_SRAM_LEN = 0x29 # 41 tracked items SHOP_SRAM_LEN = 0x29 # 41 tracked items
POT_LOCATION_TABLE = 0x142A60
RECV_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes RECV_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes
RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte
RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte
@@ -827,12 +831,14 @@ def get_location_name_from_address(ctx, address):
def filter_location(ctx, location): def filter_location(ctx, location):
if location in location_table_pot_items:
tile_idx, mask = location_table_pot_items[location]
tracking_data = ctx.pottery_locations_enabled
tile_pots = tracking_data[tile_idx] | (tracking_data[tile_idx+1] << 8)
return (mask & tile_pots) == 0
if (not ctx.key_drop_mode and location in PotShuffle.key_drop_data if (not ctx.key_drop_mode and location in PotShuffle.key_drop_data
and PotShuffle.key_drop_data[location][0] == 'Drop'): and PotShuffle.key_drop_data[location][0] == 'Drop'):
return True return True
if (not ctx.pottery_mode and location in PotShuffle.key_drop_data
and PotShuffle.key_drop_data[location][0] == 'Pot'):
return True
if not ctx.shop_mode and location in Regions.flat_normal_shops: if not ctx.shop_mode and location in Regions.flat_normal_shops:
return True return True
if not ctx.retro_mode and location in Regions.flat_retro_shops: if not ctx.retro_mode and location in Regions.flat_retro_shops:
@@ -1006,6 +1012,9 @@ async def game_watcher(ctx : Context):
logging.warning("ROM change detected, please reconnect to the multiworld server") logging.warning("ROM change detected, please reconnect to the multiworld server")
await disconnect(ctx) await disconnect(ctx)
if ctx.pottery_locations_enabled is None:
ctx.pottery_locations_enabled = await snes_read(ctx, POT_LOCATION_TABLE, 0x250)
gamemode = await snes_read(ctx, WRAM_START + 0x10, 1) gamemode = await snes_read(ctx, WRAM_START + 0x10, 1)
if gamemode is None or gamemode[0] not in INGAME_MODES: if gamemode is None or gamemode[0] not in INGAME_MODES:
continue continue
+1 -1
View File
@@ -7,7 +7,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType
from OverworldGlitchRules import create_owg_connections from OverworldGlitchRules import create_owg_connections
from Utils import bidict from Utils import bidict
version_number = '0.3.2.0' version_number = '0.3.2.1'
# branch indicator is intentionally different across branches # branch indicator is intentionally different across branches
version_branch = '' version_branch = ''
+1 -1
View File
@@ -1022,7 +1022,7 @@ key_drop_data = {
'Ice Palace - Jelly Key Drop': ['Drop', (0x09DA21, 0xE, 3), 'dropped in Ice Palace', 'Small Key (Ice Palace)'], 'Ice Palace - Jelly Key Drop': ['Drop', (0x09DA21, 0xE, 3), 'dropped in Ice Palace', 'Small Key (Ice Palace)'],
'Ice Palace - Conveyor Key Drop': ['Drop', (0x09DE08, 0x3E, 8), 'dropped in Ice Palace', 'Small Key (Ice Palace)'], 'Ice Palace - Conveyor Key Drop': ['Drop', (0x09DE08, 0x3E, 8), 'dropped in Ice Palace', 'Small Key (Ice Palace)'],
'Ice Palace - Hammer Block Key Drop': ['Pot', 0x3F, 'under a block in Ice Palace', 'Small Key (Ice Palace)'], 'Ice Palace - Hammer Block Key Drop': ['Pot', 0x3F, 'under a block in Ice Palace', 'Small Key (Ice Palace)'],
'Ice Palace - Many Pots Pot Key': ['Pot', 0x9F, 'int a pot in Ice Palace', 'Small Key (Ice Palace)'], 'Ice Palace - Many Pots Pot Key': ['Pot', 0x9F, 'in a pot in Ice Palace', 'Small Key (Ice Palace)'],
'Misery Mire - Spikes Pot Key': ['Pot', 0xB3, 'in a pot in Misery Mire', 'Small Key (Misery Mire)'], 'Misery Mire - Spikes Pot Key': ['Pot', 0xB3, 'in a pot in Misery Mire', 'Small Key (Misery Mire)'],
'Misery Mire - Fishbone Pot Key': ['Pot', 0xA1, 'in a pot in forgotten Mire', 'Small Key (Misery Mire)'], 'Misery Mire - Fishbone Pot Key': ['Pot', 0xA1, 'in a pot in forgotten Mire', 'Small Key (Misery Mire)'],
'Misery Mire - Conveyor Crystal Key Drop': ['Drop', (0x09E7FB, 0xC1, 9), 'dropped in Misery Mire', 'Small Key (Misery Mire)'], 'Misery Mire - Conveyor Crystal Key Drop': ['Drop', (0x09E7FB, 0xC1, 9), 'dropped in Misery Mire', 'Small Key (Misery Mire)'],
+26 -1
View File
@@ -57,7 +57,7 @@ Please see [Customizer documentation](docs/Customizer.md) on how to create custo
## New Goals ## New Goals
### Triforce Hunt + Ganon ### Ganonhunt
Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI
### Completionist ### Completionist
@@ -109,6 +109,31 @@ These are now independent of retro mode and have three options: None, Random, an
# Bug Fixes and Notes # Bug Fixes and Notes
* 1.2.0.20u
* New generation feature that allows Spiral Stair to link to themselves (thank Catobat)
* Added logic for trap doors that could be opened using existing room triggers
* Fixed a problem with inverted generation and the experimental flag
* Added a notes field for user added notes either via CLI or Customizer (thanks Hiimcody and Codemann)
* Fixed a typo for a specific pot hint
* Fix for Hera Boss music (thanks Codemann)
* 1.1.6 (from Stable)
* Minor issue with dungeon counter hud interfering with timer
* 1.2.0.19u
* Added min/max for triforce pool, goal, and difference for CLI and Customizer. (Thanks Catobat)
* Fixed a bug with dungeon generation
* Multiworld: Fixed /missing command to not list all the pots
* Changed the "Ganonhunt" goal to use open pyramid on the Auto setting
* Customizer: Fixed the example yaml for shopsanity
* 1.2.0.18u
* Fixed an issue with pyramid hole being in logic when it is not opened.
* Crystal cutscene at GT use new symmetrical layouts (thanks Codemann)
* Fix for Hera Boss music (thanks Codemann)
* Fixed an issue where certain vanilla door types would not allow other types to be placed.
* Customizer: fixed an issue where last ditch placements would move customized items. Those are now locked and the generation will fail instead if no alternatives are found.
* Customizer: fixed an issue with assured sword and start_inventory
* Customizer: warns when trying to specifically place an item that's not in the item pool
* Fixed "accessibility: none" displaying a spoiling message
* Fixed warning message about custom item pool when it is fine
* 1.2.0.17u * 1.2.0.17u
* Fixed logic bug that allowed Pearl to be behind Graveyard Cave or King's Tomb entrances with only Mirror and West Dark World access (cross world shuffles only) * Fixed logic bug that allowed Pearl to be behind Graveyard Cave or King's Tomb entrances with only Mirror and West Dark World access (cross world shuffles only)
* Removed backup locations for Dungeon Only and Major Only algorithms. If item cannot be placed in the appropriate location, the seed will fail to generate instead * Removed backup locations for Dungeon Only and Major Only algorithms. If item cannot be placed in the appropriate location, the seed will fail to generate instead
+1 -1
View File
@@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '32f6a9f479f6ccb7e66e9906ff0d0e4c' RANDOMIZERBASEHASH = 'da111397d4118054e5ab4b9375cfb9e4'
class JsonRom(object): class JsonRom(object):
+99 -3
View File
@@ -69,7 +69,8 @@ def set_rules(world, player):
elif world.goal[player] == 'completionist': elif world.goal[player] == 'completionist':
add_rule(world.get_location('Ganon', player), lambda state: state.everything(player)) add_rule(world.get_location('Ganon', player), lambda state: state.everything(player))
if not world.is_tile_swapped(0x18, player): if (world.flute_mode[player] != 'active' and not world.is_tile_swapped(0x18, player)
and 'Ocarina (Activated)' not in list(map(str, [i for i in world.precollected_items if i.player == player]))):
if not world.is_copied_world: if not world.is_copied_world:
# Commented out below, this would be needed for rando implementations where Inverted requires flute activation in bunny territory # Commented out below, this would be needed for rando implementations where Inverted requires flute activation in bunny territory
# kak_region = self.world.get_region('Kakariko Village', player) # kak_region = self.world.get_region('Kakariko Village', player)
@@ -382,11 +383,18 @@ def global_rules(world, player):
# Start of door rando rules # Start of door rando rules
# TODO: Do these need to flag off when door rando is off? - some of them, yes # TODO: Do these need to flag off when door rando is off? - some of them, yes
def is_trapped(entrance):
return world.get_entrance(entrance, player).door.trapped
# Eastern Palace # Eastern Palace
# Eyegore room needs a bow # Eyegore room needs a bow
set_rule(world.get_entrance('Eastern Duo Eyegores NE', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('Eastern Duo Eyegores NE', player), lambda state: state.can_shoot_arrows(player))
set_rule(world.get_entrance('Eastern Single Eyegore NE', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('Eastern Single Eyegore NE', player), lambda state: state.can_shoot_arrows(player))
set_rule(world.get_entrance('Eastern Map Balcony Hook Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Eastern Map Balcony Hook Path', player), lambda state: state.has('Hookshot', player))
if is_trapped('Eastern Single Eyegore ES'):
set_rule(world.get_entrance('Eastern Single Eyegore ES', player), lambda state: state.can_shoot_arrows(player))
if is_trapped('Eastern Duo Eyegores SE'):
set_rule(world.get_entrance('Eastern Duo Eyegores SE', player), lambda state: state.can_shoot_arrows(player))
# Boss rules. Same as below but no BK or arrow requirement. # Boss rules. Same as below but no BK or arrow requirement.
set_defeat_dungeon_boss_rule(world.get_location('Eastern Palace - Prize', player)) set_defeat_dungeon_boss_rule(world.get_location('Eastern Palace - Prize', player))
@@ -411,13 +419,18 @@ def global_rules(world, player):
set_rule(world.get_entrance('Tower Red Spears WN', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('Tower Red Spears WN', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_entrance('Tower Red Guards EN', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('Tower Red Guards EN', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_entrance('Tower Red Guards SW', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('Tower Red Guards SW', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_entrance('Tower Circle of Pots NW', player), lambda state: state.can_kill_most_things(player))
if is_trapped('Tower Circle of Pots ES'):
set_rule(world.get_entrance('Tower Circle of Pots ES', player),
lambda state: state.can_kill_most_things(player))
set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player)) set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player))
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player)) set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player))
set_rule(world.get_entrance('PoD Arena Landing Bonk Path', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('PoD Arena Landing Bonk Path', player), lambda state: state.has_Boots(player))
set_rule(world.get_entrance('PoD Mimics 1 NW', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('PoD Mimics 1 NW', player), lambda state: state.can_shoot_arrows(player))
set_rule(world.get_entrance('PoD Mimics 2 NW', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('PoD Mimics 2 NW', player), lambda state: state.can_shoot_arrows(player))
if is_trapped('PoD Mimics 2 SW'):
set_rule(world.get_entrance('PoD Mimics 2 SW', player), lambda state: state.can_shoot_arrows(player))
set_rule(world.get_entrance('PoD Bow Statue Down Ladder', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('PoD Bow Statue Down Ladder', player), lambda state: state.can_shoot_arrows(player))
set_rule(world.get_entrance('PoD Map Balcony Drop Down', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('PoD Map Balcony Drop Down', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('PoD Dark Pegs Landing to Right', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('PoD Dark Pegs Landing to Right', player), lambda state: state.has('Hammer', player))
@@ -466,6 +479,8 @@ def global_rules(world, player):
set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_entrance('Skull Torch Room WN', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('Skull Torch Room WN', player), lambda state: state.has('Fire Rod', player))
if is_trapped('Skull Torch Room WS'):
set_rule(world.get_entrance('Skull Torch Room WS', player), lambda state: state.has('Fire Rod', player))
set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player)) set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player))
hidden_pits_door = world.get_door('Skull Small Hall WS', player) hidden_pits_door = world.get_door('Skull Small Hall WS', player)
@@ -503,6 +518,8 @@ def global_rules(world, player):
set_rule(world.get_location('Thieves\' Town - Prize', player), lambda state: state.has('Maiden Unmasked', player) and world.get_location('Thieves\' Town - Prize', player).parent_region.dungeon.boss.can_defeat(state)) set_rule(world.get_location('Thieves\' Town - Prize', player), lambda state: state.has('Maiden Unmasked', player) and world.get_location('Thieves\' Town - Prize', player).parent_region.dungeon.boss.can_defeat(state))
set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.can_melt_things(player)) set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.can_melt_things(player))
if is_trapped('Ice Lobby SE'):
set_rule(world.get_entrance('Ice Lobby SE', player), lambda state: state.can_melt_things(player))
set_rule(world.get_entrance('Ice Hammer Block ES', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player)) set_rule(world.get_entrance('Ice Hammer Block ES', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
set_rule(world.get_location('Ice Palace - Hammer Block Key Drop', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player)) set_rule(world.get_location('Ice Palace - Hammer Block Key Drop', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
set_rule(world.get_location('Ice Palace - Map Chest', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player)) set_rule(world.get_location('Ice Palace - Map Chest', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
@@ -517,6 +534,12 @@ def global_rules(world, player):
set_rule(world.get_entrance('Ice Hookshot Balcony Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Ice Hookshot Balcony Path', player), lambda state: state.has('Hookshot', player))
if not world.get_door('Ice Switch Room SE', player).entranceFlag: if not world.get_door('Ice Switch Room SE', player).entranceFlag:
set_rule(world.get_entrance('Ice Switch Room SE', player), lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player)) set_rule(world.get_entrance('Ice Switch Room SE', player), lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player))
if is_trapped('Ice Switch Room ES'):
set_rule(world.get_entrance('Ice Switch Room ES', player),
lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player))
if is_trapped('Ice Switch Room NE'):
set_rule(world.get_entrance('Ice Switch Room NE', player),
lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player))
set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Boss', player))
set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Prize', player)) set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Prize', player))
@@ -537,8 +560,15 @@ def global_rules(world, player):
or state.has('Cane of Byrna', player) or state.has('Cape', player)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player))
if is_trapped('Mire Tile Room SW'):
set_rule(world.get_entrance('Mire Tile Room SW', player), lambda state: state.has_fire_source(player))
if is_trapped('Mire Tile Room ES'):
set_rule(world.get_entrance('Mire Tile Room ES', player), lambda state: state.has_fire_source(player))
set_rule(world.get_entrance('Mire Attic Hint Hole', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('Mire Attic Hint Hole', player), lambda state: state.has_fire_source(player))
set_rule(world.get_entrance('Mire Dark Shooters SW', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('Mire Dark Shooters SW', player), lambda state: state.has('Cane of Somaria', player))
if is_trapped('Mire Dark Shooters SE'):
set_rule(world.get_entrance('Mire Dark Shooters SE', player),
lambda state: state.has('Cane of Somaria', player))
set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Boss', player))
set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Prize', player)) set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Prize', player))
@@ -554,6 +584,9 @@ def global_rules(world, player):
set_rule(world.get_entrance('TR Hub Path', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Hub Path', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Hub Ledges Path', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Hub Ledges Path', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Torches NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) set_rule(world.get_entrance('TR Torches NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
if is_trapped('TR Torches WN'):
set_rule(world.get_entrance('TR Torches WN', player),
lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player)) set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has_Boots(player)) set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has_Boots(player))
set_rule(world.get_entrance('TR Dark Ride Up Stairs', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Dark Ride Up Stairs', player), lambda state: state.has('Cane of Somaria', player))
@@ -573,10 +606,20 @@ def global_rules(world, player):
set_rule(world.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has_Boots(player)) set_rule(world.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has_Boots(player))
set_rule(world.get_entrance('GT Hope Room EN', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('GT Hope Room EN', player), lambda state: state.has('Cane of Somaria', player))
if is_trapped('GT Hope Room WN'):
set_rule(world.get_entrance('GT Hope Room WN', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('GT Conveyor Cross Hammer Path', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('GT Conveyor Cross Hammer Path', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('GT Conveyor Cross Hookshot Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('GT Conveyor Cross Hookshot Path', player), lambda state: state.has('Hookshot', player))
if is_trapped('GT Conveyor Cross EN'):
set_rule(world.get_entrance('GT Conveyor Cross EN', player), lambda state: state.has('Hammer', player))
if not world.get_door('GT Speed Torch SE', player).entranceFlag: if not world.get_door('GT Speed Torch SE', player).entranceFlag:
set_rule(world.get_entrance('GT Speed Torch SE', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('GT Speed Torch SE', player), lambda state: state.has('Fire Rod', player))
if is_trapped('GT Speed Torch NE'):
set_rule(world.get_entrance('GT Speed Torch NE', player), lambda state: state.has('Fire Rod', player))
if is_trapped('GT Speed Torch WS'):
set_rule(world.get_entrance('GT Speed Torch WS', player), lambda state: state.has('Fire Rod', player))
if is_trapped('GT Speed Torch WN'):
set_rule(world.get_entrance('GT Speed Torch WN', player), lambda state: state.has('Fire Rod', player))
set_rule(world.get_entrance('GT Hookshot South-Mid Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('GT Hookshot South-Mid Path', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_entrance('GT Hookshot Mid-North Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('GT Hookshot Mid-North Path', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_entrance('GT Hookshot East-Mid Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player)) set_rule(world.get_entrance('GT Hookshot East-Mid Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
@@ -611,6 +654,8 @@ def global_rules(world, player):
set_rule(world.get_entrance('GT Lanmolas 2 ES', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state)) set_rule(world.get_entrance('GT Lanmolas 2 ES', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
set_rule(world.get_entrance('GT Lanmolas 2 NW', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state)) set_rule(world.get_entrance('GT Lanmolas 2 NW', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
set_rule(world.get_entrance('GT Torch Cross ES', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('GT Torch Cross ES', player), lambda state: state.has_fire_source(player))
if is_trapped('GT Torch Cross WN'):
set_rule(world.get_entrance('GT Torch Cross WN', player), lambda state: state.has_fire_source(player))
set_rule(world.get_entrance('GT Falling Torches NE', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('GT Falling Torches NE', player), lambda state: state.has_fire_source(player))
# todo: the following only applies to crystal state propagation from this supertile # todo: the following only applies to crystal state propagation from this supertile
# you can also reset the supertile, but I'm not sure how to model that # you can also reset the supertile, but I'm not sure how to model that
@@ -870,13 +915,29 @@ def bomb_rules(world, player):
('GT Petting Zoo SE', False), # Dont make anyone do this room with bombs and/or pots. ('GT Petting Zoo SE', False), # Dont make anyone do this room with bombs and/or pots.
('GT DMs Room SW', False) # Four red stalfos ('GT DMs Room SW', False) # Four red stalfos
] ]
conditional_kill_traps = [
('Hyrule Dungeon Armory Interior Key Door N', True),
('Desert Compass Key Door WN', True),
('Thieves Blocked Entry SW', True),
('TR Tongue Pull WS', True),
('TR Twin Pokeys NW', False),
]
for killdoor,bombable in easy_kill_rooms: for killdoor,bombable in easy_kill_rooms:
if bombable: if bombable:
add_rule(world.get_entrance(killdoor, player), lambda state: (state.can_use_bombs(player) or state.can_kill_most_things(player))) add_rule(world.get_entrance(killdoor, player), lambda state: (state.can_use_bombs(player) or state.can_kill_most_things(player)))
else: else:
add_rule(world.get_entrance(killdoor, player), lambda state: state.can_kill_most_things(player)) add_rule(world.get_entrance(killdoor, player), lambda state: state.can_kill_most_things(player))
for kill_door, bombable in conditional_kill_traps:
if world.get_entrance(kill_door, player).door.trapped:
if bombable:
add_rule(world.get_entrance(kill_door, player),
lambda state: (state.can_use_bombs(player) or state.can_kill_most_things(player)))
else:
add_rule(world.get_entrance(kill_door, player), lambda state: state.can_kill_most_things(player))
add_rule(world.get_entrance('Ice Stalfos Hint SE', player), lambda state: state.can_use_bombs(player)) # Need bombs for big stalfos knights add_rule(world.get_entrance('Ice Stalfos Hint SE', player), lambda state: state.can_use_bombs(player)) # Need bombs for big stalfos knights
add_rule(world.get_entrance('Mire Cross ES', player), lambda state: state.can_kill_most_things(player)) # 4 Sluggulas. Bombs don't work // or (state.can_use_bombs(player) and state.has('Magic Powder'), player) add_rule(world.get_entrance('Mire Cross ES', player), lambda state: state.can_kill_most_things(player)) # 4 Sluggulas. Bombs don't work // or (state.can_use_bombs(player) and state.has('Magic Powder'), player)
if world.get_entrance('Mire Cross SW', player).door.trapped:
add_rule(world.get_entrance('Mire Cross SW', player), lambda state: state.can_kill_most_things(player))
enemy_kill_drops = [ # Location, bool-bombable enemy_kill_drops = [ # Location, bool-bombable
('Hyrule Castle - Map Guard Key Drop', True), ('Hyrule Castle - Map Guard Key Drop', True),
@@ -1399,6 +1460,9 @@ def swordless_rules(world, player):
set_rule(world.get_entrance('Tower Altar NW', player), lambda state: True) set_rule(world.get_entrance('Tower Altar NW', player), lambda state: True)
set_rule(world.get_entrance('Skull Vines NW', player), lambda state: True) set_rule(world.get_entrance('Skull Vines NW', player), lambda state: True)
set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))
if world.get_entrance('Ice Lobby SE', player).door.trapped:
set_rule(world.get_entrance('Ice Lobby SE', player),
lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))
set_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) set_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))
set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player)) set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player))
@@ -1412,7 +1476,7 @@ def swordless_rules(world, player):
if not world.is_atgt_swapped(player): if not world.is_atgt_swapped(player):
set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player)) # barrier gets removed after killing agahnim, rule for that added later set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player)) # barrier gets removed after killing agahnim, rule for that added later
# todo: new traps
std_kill_rooms = { std_kill_rooms = {
'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES'], # One green guard 'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES'], # One green guard
'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'], # One blue guard 'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'], # One blue guard
@@ -1443,6 +1507,18 @@ std_kill_rooms = {
'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE'] # Wizzrobes. Bombs don't work 'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE'] # Wizzrobes. Bombs don't work
} # all trap rooms? } # all trap rooms?
std_kill_doors_if_trapped = {
'Hyrule Dungeon Armory Main': 'Hyrule Dungeon Armory Interior Key Door N',
# 'Eastern Single Eyegore ES', # arrow rule is sufficient
# 'Eastern Duo Eyegores S', # arrow rule is sufficient
'TR Twin Pokeys': 'TR Twin Pokeys NW',
'Thieves Basement Block': 'Thieves Blocked Entry SW',
'Desert Compass Room': 'Desert Compass Key Door WN',
'Mire Cross': 'Mire Cross SW',
'Tower Circle of Pots': 'Tower Circle of Pots ES',
# 'Ice Lobby S' # can melt rule is sufficient
}
def add_connection(parent_name, target_name, entrance_name, world, player): def add_connection(parent_name, target_name, entrance_name, world, player):
parent = world.get_region(parent_name, player) parent = world.get_region(parent_name, player)
target = world.get_region(target_name, player) target = world.get_region(target_name, player)
@@ -1495,6 +1571,10 @@ def standard_rules(world, player):
if region.name in std_kill_rooms: if region.name in std_kill_rooms:
for ent in std_kill_rooms[region.name]: for ent in std_kill_rooms[region.name]:
add_rule(world.get_entrance(ent, player), lambda state: standard_escape_rule(state)) add_rule(world.get_entrance(ent, player), lambda state: standard_escape_rule(state))
if region.name in std_kill_doors_if_trapped:
ent = world.get_entrance(std_kill_doors_if_trapped[region.name], player)
if ent.door.trapped:
add_rule(ent, lambda state: standard_escape_rule(state))
set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player)) set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player))
set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), lambda state: state.has('Zelda Herself', player)) set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), lambda state: state.has('Zelda Herself', player))
@@ -1685,6 +1765,11 @@ def set_bunny_rules(world, player, inverted):
if bunny_exit.connected_region and is_bunny(bunny_exit.parent_region): if bunny_exit.connected_region and is_bunny(bunny_exit.parent_region):
add_rule(bunny_exit, get_rule_to_add(bunny_exit.parent_region)) add_rule(bunny_exit, get_rule_to_add(bunny_exit.parent_region))
for ent_name in bunny_impassible_if_trapped:
bunny_exit = world.get_entrance(ent_name, player)
if bunny_exit.door.trapped and is_bunny(bunny_exit.parent_region):
add_rule(bunny_exit, get_rule_to_add(bunny_exit.parent_region))
doors_to_check = [x for x in world.doors if x.player == player and x not in bunny_impassible_doors] doors_to_check = [x for x in world.doors if x.player == player and x not in bunny_impassible_doors]
doors_to_check = [x for x in doors_to_check if x.type in [DoorType.Normal, DoorType.Interior] and not x.blocked] doors_to_check = [x for x in doors_to_check if x.type in [DoorType.Normal, DoorType.Interior] and not x.blocked]
for door in doors_to_check: for door in doors_to_check:
@@ -1816,6 +1901,17 @@ bunny_impassible_doors = {
'GT Validation Block Path' 'GT Validation Block Path'
} }
bunny_impassible_if_trapped = {
'Hyrule Dungeon Armory Interior Key Door N', 'Eastern Pot Switch WN', 'Eastern Lobby NW',
'Eastern Lobby NE', 'Desert Compass Key Door WN', 'Tower Circle of Pots ES', 'PoD Mimics 2 SW',
'PoD Middle Cage S', 'Swamp Push Statue S', 'Skull 2 East Lobby WS', 'Skull Torch Room WS',
'Thieves Conveyor Maze WN', 'Thieves Conveyor Maze SW', 'Thieves Blocked Entry SW', 'Ice Bomb Jump NW',
'Ice Tall Hint EN', 'Ice Switch Room ES', 'Ice Switch Room NE', 'Mire Cross SW',
'Mire Tile Room SW', 'Mire Tile Room ES', 'TR Twin Pokeys NW', 'TR Torches WN', 'GT Hope Room WN',
'GT Speed Torch NE', 'GT Speed Torch WS', 'GT Torch Cross WN', 'GT Hidden Spikes SE', 'GT Conveyor Cross EN',
'GT Speed Torch WN', 'Ice Lobby SE'
}
def add_key_logic_rules(world, player): def add_key_logic_rules(world, player):
key_logic = world.key_logic[player] key_logic = world.key_logic[player]
+7
View File
@@ -737,6 +737,13 @@ class bidict(dict):
super(bidict, self).__delitem__(key) super(bidict, self).__delitem__(key)
class HexInt(int): pass
def hex_representer(dumper, data):
import yaml
return yaml.ScalarNode('tag:yaml.org,2002:int', f"{data:#0{4}x}")
if __name__ == '__main__': if __name__ == '__main__':
# make_new_base2current() # make_new_base2current()
# read_entrance_data(old_rom=sys.argv[1]) # read_entrance_data(old_rom=sys.argv[1])
Binary file not shown.
+2 -1
View File
@@ -1,8 +1,9 @@
meta: meta:
algorithm: balanced algorithm: balanced
players: 1 players: 1
seed: 42 seed: 41 # note to self: seed 42 had an interesting Swamp Palace problem
names: Lonk names: Lonk
notes: "Some notes specified by the user"
settings: settings:
1: 1:
door_shuffle: basic door_shuffle: basic
+4
View File
@@ -52,6 +52,9 @@
partial: 0 partial: 0
strict: 0 strict: 0
decoupledoors: off decoupledoors: off
door_self_loops:
on: 1
off: 1
dropshuffle: dropshuffle:
on: 1 on: 1
off: 1 off: 1
@@ -134,6 +137,7 @@
triforce_pool_min: 20 triforce_pool_min: 20
triforce_pool_max: 40 triforce_pool_max: 40
triforce_min_difference: 10 triforce_min_difference: 10
triforce_max_difference: 15
algorithm: algorithm:
balanced: 12 balanced: 12
vanilla_fill: 1 vanilla_fill: 1
+4
View File
@@ -31,6 +31,9 @@ key_logic_algorithm:
decoupledoors: decoupledoors:
off: 9 # more strict off: 9 # more strict
on: 1 on: 1
door_self_loops:
on: 1
off: 1
dropshuffle: dropshuffle:
on: 1 on: 1
off: 1 off: 1
@@ -86,6 +89,7 @@ triforce_goal_max: 30
triforce_pool_min: 30 triforce_pool_min: 30
triforce_pool_max: 40 triforce_pool_max: 40
triforce_min_difference: 10 triforce_min_difference: 10
triforce_max_difference: 12
map_shuffle: map_shuffle:
on: 1 on: 1
off: 1 off: 1
+6
View File
@@ -259,6 +259,10 @@
"action": "store_true", "action": "store_true",
"type": "bool" "type": "bool"
}, },
"door_self_loops": {
"action": "store_true",
"type": "bool"
},
"experimental": { "experimental": {
"action": "store_true", "action": "store_true",
"type": "bool" "type": "bool"
@@ -402,6 +406,7 @@
"triforce_goal_min": {}, "triforce_goal_min": {},
"triforce_goal_max": {}, "triforce_goal_max": {},
"triforce_min_difference": {}, "triforce_min_difference": {},
"triforce_max_difference": {},
"custom": { "custom": {
"type": "bool", "type": "bool",
"help": "suppress" "help": "suppress"
@@ -575,5 +580,6 @@
] ]
}, },
"outputname": {}, "outputname": {},
"notes": {},
"code": {} "code": {}
} }
+2
View File
@@ -266,6 +266,7 @@
"door_shuffle": [ "door_shuffle": [
"Select Door Shuffling Algorithm. (default: %(default)s)", "Select Door Shuffling Algorithm. (default: %(default)s)",
"Basic: Doors are mixed within a single dungeon.", "Basic: Doors are mixed within a single dungeon.",
"Paired Dungeon are paired (with one trio) and only mixed in those groups",
"Partitioned Doors are mixed in 3 partitions: L1-3+HC+AT, D1-4, D5-8", "Partitioned Doors are mixed in 3 partitions: L1-3+HC+AT, D1-4, D5-8",
"Crossed: Doors are mixed between all dungeons.", "Crossed: Doors are mixed between all dungeons.",
"Vanilla: All doors are connected the same way they were in the", "Vanilla: All doors are connected the same way they were in the",
@@ -299,6 +300,7 @@
"strict: Ensure small keys are available" "strict: Ensure small keys are available"
], ],
"decoupledoors" : [ "Door entrances and exits are decoupled" ], "decoupledoors" : [ "Door entrances and exits are decoupled" ],
"door_self_loops" : [ "Spiral stairs are allowed to self-loop" ],
"experimental": [ "Enable experimental features. (default: %(default)s)" ], "experimental": [ "Enable experimental features. (default: %(default)s)" ],
"dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ], "dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ],
"crystals_ganon": [ "crystals_ganon": [
+3 -1
View File
@@ -58,10 +58,12 @@
"randomizer.dungeon.smallkeyshuffle.universal": "Universal", "randomizer.dungeon.smallkeyshuffle.universal": "Universal",
"randomizer.dungeon.bigkeyshuffle": "Big Keys", "randomizer.dungeon.bigkeyshuffle": "Big Keys",
"randomizer.dungeon.decoupledoors": "Decouple Doors", "randomizer.dungeon.decoupledoors": "Decouple Doors",
"randomizer.dungeon.door_self_loops": "Allow Self-Looping Spiral Stairs",
"randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle", "randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle",
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla", "randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",
"randomizer.dungeon.dungeondoorshuffle.basic": "Basic", "randomizer.dungeon.dungeondoorshuffle.basic": "Basic",
"randomizer.dungeon.dungeondoorshuffle.paired": "Paired",
"randomizer.dungeon.dungeondoorshuffle.partitioned": "Partitioned", "randomizer.dungeon.dungeondoorshuffle.partitioned": "Partitioned",
"randomizer.dungeon.dungeondoorshuffle.crossed": "Crossed", "randomizer.dungeon.dungeondoorshuffle.crossed": "Crossed",
@@ -269,7 +271,7 @@
"randomizer.item.goal.triforcehunt": "Triforce Hunt", "randomizer.item.goal.triforcehunt": "Triforce Hunt",
"randomizer.item.goal.trinity": "Trinity", "randomizer.item.goal.trinity": "Trinity",
"randomizer.item.goal.crystals": "Crystals", "randomizer.item.goal.crystals": "Crystals",
"randomizer.item.goal.ganonhunt": "Triforce Hunt + Ganon", "randomizer.item.goal.ganonhunt": "Ganonhunt",
"randomizer.item.goal.completionist": "Completionist", "randomizer.item.goal.completionist": "Completionist",
"randomizer.item.crystals_gt": "Crystals to open GT", "randomizer.item.crystals_gt": "Crystals to open GT",
@@ -66,6 +66,12 @@
} }
}, },
"decoupledoors": { "decoupledoors": {
"type": "checkbox",
"config": {
"padx": [20,0]
}
},
"door_self_loops": {
"type": "checkbox", "type": "checkbox",
"config": { "config": {
"padx": [20,0], "padx": [20,0],
+15 -3
View File
@@ -4,6 +4,7 @@ import urllib.parse
import yaml import yaml
from typing import Any from typing import Any
from yaml.representer import Representer from yaml.representer import Representer
from Utils import HexInt, hex_representer
from collections import defaultdict from collections import defaultdict
from pathlib import Path from pathlib import Path
@@ -63,6 +64,7 @@ class CustomSettings(object):
args.suppress_rom = get_setting(meta['suppress_rom'], args.suppress_rom) args.suppress_rom = get_setting(meta['suppress_rom'], args.suppress_rom)
args.names = get_setting(meta['names'], args.names) args.names = get_setting(meta['names'], args.names)
args.race = get_setting(meta['race'], args.race) args.race = get_setting(meta['race'], args.race)
args.notes = get_setting(meta['user_notes'], args.notes)
self.player_range = range(1, args.multi + 1) self.player_range = range(1, args.multi + 1)
if 'settings' in self.file_source: if 'settings' in self.file_source:
for p in self.player_range: for p in self.player_range:
@@ -121,6 +123,7 @@ class CustomSettings(object):
args.trap_door_mode[p] = get_setting(settings['trap_door_mode'], args.trap_door_mode[p]) args.trap_door_mode[p] = get_setting(settings['trap_door_mode'], args.trap_door_mode[p])
args.key_logic_algorithm[p] = get_setting(settings['key_logic_algorithm'], args.key_logic_algorithm[p]) args.key_logic_algorithm[p] = get_setting(settings['key_logic_algorithm'], args.key_logic_algorithm[p])
args.decoupledoors[p] = get_setting(settings['decoupledoors'], args.decoupledoors[p]) args.decoupledoors[p] = get_setting(settings['decoupledoors'], args.decoupledoors[p])
args.door_self_loops[p] = get_setting(settings['door_self_loops'], args.door_self_loops[p])
args.dungeon_counters[p] = get_setting(settings['dungeon_counters'], args.dungeon_counters[p]) args.dungeon_counters[p] = get_setting(settings['dungeon_counters'], args.dungeon_counters[p])
args.crystals_gt[p] = get_setting(settings['crystals_gt'], args.crystals_gt[p]) args.crystals_gt[p] = get_setting(settings['crystals_gt'], args.crystals_gt[p])
args.crystals_ganon[p] = get_setting(settings['crystals_ganon'], args.crystals_ganon[p]) args.crystals_ganon[p] = get_setting(settings['crystals_ganon'], args.crystals_ganon[p])
@@ -152,6 +155,12 @@ class CustomSettings(object):
args.pseudoboots[p] = get_setting(settings['pseudoboots'], args.pseudoboots[p]) args.pseudoboots[p] = get_setting(settings['pseudoboots'], args.pseudoboots[p])
args.triforce_goal[p] = get_setting(settings['triforce_goal'], args.triforce_goal[p]) args.triforce_goal[p] = get_setting(settings['triforce_goal'], args.triforce_goal[p])
args.triforce_pool[p] = get_setting(settings['triforce_pool'], args.triforce_pool[p]) args.triforce_pool[p] = get_setting(settings['triforce_pool'], args.triforce_pool[p])
args.triforce_goal_min[p] = get_setting(settings['triforce_goal_min'], args.triforce_goal_min[p])
args.triforce_goal_max[p] = get_setting(settings['triforce_goal_max'], args.triforce_goal_max[p])
args.triforce_pool_min[p] = get_setting(settings['triforce_pool_min'], args.triforce_pool_min[p])
args.triforce_pool_max[p] = get_setting(settings['triforce_pool_max'], args.triforce_pool_max[p])
args.triforce_min_difference[p] = get_setting(settings['triforce_min_difference'], args.triforce_min_difference[p])
args.triforce_max_difference[p] = get_setting(settings['triforce_max_difference'], args.triforce_max_difference[p])
args.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p]) args.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p])
# mystery usage # mystery usage
@@ -221,14 +230,15 @@ class CustomSettings(object):
return self.file_source['drops'] return self.file_source['drops']
return None return None
def create_from_world(self, world, race): def create_from_world(self, world, settings):
self.player_range = range(1, world.players + 1) self.player_range = range(1, world.players + 1)
settings_dict, meta_dict = {}, {} settings_dict, meta_dict = {}, {}
self.world_rep['meta'] = meta_dict self.world_rep['meta'] = meta_dict
meta_dict['players'] = world.players meta_dict['players'] = world.players
meta_dict['algorithm'] = world.algorithm meta_dict['algorithm'] = world.algorithm
meta_dict['seed'] = world.seed meta_dict['seed'] = world.seed
meta_dict['race'] = race meta_dict['race'] = settings.race
meta_dict['user_notes'] = settings.notes
self.world_rep['settings'] = settings_dict self.world_rep['settings'] = settings_dict
for p in self.player_range: for p in self.player_range:
settings_dict[p] = {} settings_dict[p] = {}
@@ -247,6 +257,7 @@ class CustomSettings(object):
settings_dict[p]['trap_door_mode'] = world.trap_door_mode[p] settings_dict[p]['trap_door_mode'] = world.trap_door_mode[p]
settings_dict[p]['key_logic_algorithm'] = world.key_logic_algorithm[p] settings_dict[p]['key_logic_algorithm'] = world.key_logic_algorithm[p]
settings_dict[p]['decoupledoors'] = world.decoupledoors[p] settings_dict[p]['decoupledoors'] = world.decoupledoors[p]
settings_dict[p]['door_self_loops'] = world.door_self_loops[p]
settings_dict[p]['logic'] = world.logic[p] settings_dict[p]['logic'] = world.logic[p]
settings_dict[p]['mode'] = world.mode[p] settings_dict[p]['mode'] = world.mode[p]
settings_dict[p]['swords'] = world.swords[p] settings_dict[p]['swords'] = world.swords[p]
@@ -342,7 +353,7 @@ class CustomSettings(object):
for p in self.player_range: for p in self.player_range:
if p in world.owswaps and len(world.owswaps[p][0]) > 0: if p in world.owswaps and len(world.owswaps[p][0]) > 0:
flips[p] = {} flips[p] = {}
flips[p]['force_flip'] = list(f for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80) flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80)
flips[p]['force_flip'].sort() flips[p]['force_flip'].sort()
flips[p]['undefined_chance'] = 0 flips[p]['undefined_chance'] = 0
@@ -408,6 +419,7 @@ class CustomSettings(object):
def write_to_file(self, destination): def write_to_file(self, destination):
yaml.add_representer(defaultdict, Representer.represent_dict) yaml.add_representer(defaultdict, Representer.represent_dict)
yaml.add_representer(HexInt, hex_representer)
with open(destination, 'w') as file: with open(destination, 'w') as file:
yaml.dump(self.world_rep, file) yaml.dump(self.world_rep, file)
+1
View File
@@ -116,6 +116,7 @@ SETTINGSTOPROCESS = {
"door_type_mode": "door_type_mode", "door_type_mode": "door_type_mode",
"trap_door_mode": "trap_door_mode", "trap_door_mode": "trap_door_mode",
"decoupledoors": "decoupledoors", "decoupledoors": "decoupledoors",
"door_self_loops": "door_self_loops",
"experimental": "experimental", "experimental": "experimental",
"dungeon_counters": "dungeon_counters", "dungeon_counters": "dungeon_counters",
"mixed_travel": "mixed_travel", "mixed_travel": "mixed_travel",
+12 -4
View File
@@ -22,7 +22,7 @@ def generate_dungeon(builder, entrance_region_names, split_dungeon, world, playe
queue = collections.deque(proposed_map.items()) queue = collections.deque(proposed_map.items())
while len(queue) > 0: while len(queue) > 0:
a, b = queue.popleft() a, b = queue.popleft()
if world.decoupledoors[player]: if a == b or world.decoupledoors[player]:
connect_doors_one_way(a, b) connect_doors_one_way(a, b)
else: else:
connect_doors(a, b) connect_doors(a, b)
@@ -128,14 +128,14 @@ def create_random_proposal(doors_to_connect, world, player):
next_hook = random.choice(hooks_left) next_hook = random.choice(hooks_left)
primary_door = random.choice(primary_bucket[next_hook]) primary_door = random.choice(primary_bucket[next_hook])
opp_hook, secondary_door = type_map[next_hook], None opp_hook, secondary_door = type_map[next_hook], None
while (secondary_door is None or secondary_door == primary_door while (secondary_door is None or (secondary_door == primary_door and not world.door_self_loops[player])
or decouple_check(primary_bucket[next_hook], secondary_bucket[opp_hook], or decouple_check(primary_bucket[next_hook], secondary_bucket[opp_hook],
primary_door, secondary_door, world, player)): primary_door, secondary_door, world, player)):
secondary_door = random.choice(secondary_bucket[opp_hook]) secondary_door = random.choice(secondary_bucket[opp_hook])
proposal[primary_door] = secondary_door proposal[primary_door] = secondary_door
primary_bucket[next_hook].remove(primary_door) primary_bucket[next_hook].remove(primary_door)
secondary_bucket[opp_hook].remove(secondary_door) secondary_bucket[opp_hook].remove(secondary_door)
if not world.decoupledoors[player]: if primary_door != secondary_door and not world.decoupledoors[player]:
proposal[secondary_door] = primary_door proposal[secondary_door] = primary_door
primary_bucket[opp_hook].remove(secondary_door) primary_bucket[opp_hook].remove(secondary_door)
secondary_bucket[next_hook].remove(primary_door) secondary_bucket[next_hook].remove(primary_door)
@@ -200,11 +200,19 @@ def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_se
unvisted_bucket[opp_hook].sort(key=lambda d: d.name) unvisted_bucket[opp_hook].sort(key=lambda d: d.name)
new_door = random.choice(unvisted_bucket[opp_hook]) new_door = random.choice(unvisted_bucket[opp_hook])
old_target = proposed_map[attempt] old_target = proposed_map[attempt]
proposed_map[attempt] = new_door
if not world.decoupledoors[player]: if not world.decoupledoors[player]:
old_attempt = proposed_map[new_door] old_attempt = proposed_map[new_door]
else: else:
old_attempt = next(x for x in proposed_map if proposed_map[x] == new_door) old_attempt = next(x for x in proposed_map if proposed_map[x] == new_door)
# ensure nothing gets messed up when something loops with itself
if attempt == old_target and old_attempt == new_door:
old_attempt = new_door
old_target = attempt
elif attempt == old_target:
old_target = old_attempt
elif old_attempt == new_door:
old_attempt = old_target
proposed_map[attempt] = new_door
proposed_map[old_attempt] = old_target proposed_map[old_attempt] = old_target
if not world.decoupledoors[player]: if not world.decoupledoors[player]:
proposed_map[old_target] = old_attempt proposed_map[old_target] = old_attempt
+29 -14
View File
@@ -289,6 +289,7 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
bomb_shop_options = [x for x in bomb_shop_options if x not in ['Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)']] bomb_shop_options = [x for x in bomb_shop_options if x not in ['Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)']]
if avail.swapped and len(bomb_shop_options) > 1: if avail.swapped and len(bomb_shop_options) > 1:
bomb_shop_options = [x for x in bomb_shop_options if x != 'Big Bomb Shop'] bomb_shop_options = [x for x in bomb_shop_options if x != 'Big Bomb Shop']
bomb_shop_choice = random.choice(bomb_shop_options) bomb_shop_choice = random.choice(bomb_shop_options)
connect_entrance(bomb_shop_choice, bomb_shop, avail) connect_entrance(bomb_shop_choice, bomb_shop, avail)
rem_entrances.remove(bomb_shop_choice) rem_entrances.remove(bomb_shop_choice)
@@ -433,6 +434,8 @@ def do_blacksmith(entrances, exits, avail):
if avail.swapped: if avail.swapped:
blacksmith_options = [e for e in blacksmith_options if e not in Forbidden_Swap_Entrances] blacksmith_options = [e for e in blacksmith_options if e not in Forbidden_Swap_Entrances]
blacksmith_options = [x for x in blacksmith_options if x in entrances] blacksmith_options = [x for x in blacksmith_options if x in entrances]
assert len(blacksmith_options), 'No available entrances left to place Blacksmith'
blacksmith_choice = random.choice(blacksmith_options) blacksmith_choice = random.choice(blacksmith_options)
connect_entrance(blacksmith_choice, 'Blacksmiths Hut', avail) connect_entrance(blacksmith_choice, 'Blacksmiths Hut', avail)
entrances.remove(blacksmith_choice) entrances.remove(blacksmith_choice)
@@ -563,6 +566,7 @@ def do_dark_sanc(entrances, exits, avail):
choices = [e for e in avail.world.districts[avail.player]['Northwest Dark World'].entrances if e not in forbidden and e in entrances] choices = [e for e in avail.world.districts[avail.player]['Northwest Dark World'].entrances if e not in forbidden and e in entrances]
else: else:
choices = [e for e in get_starting_entrances(avail) if e not in forbidden and e in entrances] choices = [e for e in get_starting_entrances(avail) if e not in forbidden and e in entrances]
choice = random.choice(choices) choice = random.choice(choices)
entrances.remove(choice) entrances.remove(choice)
exits.remove('Dark Sanctuary Hint') exits.remove('Dark Sanctuary Hint')
@@ -596,6 +600,10 @@ def do_links_house(entrances, exits, avail, cross_world):
if avail.inverted: if avail.inverted:
dark_sanc_region = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player).connected_region.name dark_sanc_region = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player).connected_region.name
forbidden.extend(get_nearby_entrances(avail, dark_sanc_region)) forbidden.extend(get_nearby_entrances(avail, dark_sanc_region))
else:
if (avail.world.doorShuffle[avail.player] != 'vanilla' and avail.world.intensity[avail.player] > 2
and not avail.world.is_tile_swapped(0x1b, avail.player)):
forbidden.append('Hyrule Castle Entrance (South)')
if avail.swapped: if avail.swapped:
forbidden.append(links_house_vanilla) forbidden.append(links_house_vanilla)
forbidden.extend(Forbidden_Swap_Entrances) forbidden.extend(Forbidden_Swap_Entrances)
@@ -1381,13 +1389,17 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
elif cave[-1] == 'Spectacle Rock Cave Exit': # Spectacle rock only has one exit elif cave[-1] == 'Spectacle Rock Cave Exit': # Spectacle rock only has one exit
cave_entrances = [] cave_entrances = []
for cave_exit in rnd_cave[:-1]: for cave_exit in rnd_cave[:-1]:
entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in must_exit) if avail.swapped and cave_exit not in avail.exits:
cave_entrances.append(entrance) entrance = avail.world.get_entrance(cave_exit, avail.player).parent_region.entrances[0].name
entrances.remove(entrance) cave_entrances.append(entrance)
connect_two_way(entrance, cave_exit, avail) else:
if avail.swapped and combine_map[entrance] != cave_exit: entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in must_exit)
swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave) cave_entrances.append(entrance)
entrances.remove(swap_ent) entrances.remove(entrance)
connect_two_way(entrance, cave_exit, avail)
if avail.swapped and combine_map[entrance] != cave_exit:
swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave)
entrances.remove(swap_ent)
if entrance not in invalid_connections: if entrance not in invalid_connections:
invalid_connections[exit] = set() invalid_connections[exit] = set()
if all(entrance in invalid_connections for entrance in cave_entrances): if all(entrance in invalid_connections for entrance in cave_entrances):
@@ -1408,13 +1420,16 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
for cave in used_caves: for cave in used_caves:
if cave in cave_options: # check if we placed multiple entrances from this 3 or 4 exit if cave in cave_options: # check if we placed multiple entrances from this 3 or 4 exit
for cave_exit in cave: for cave_exit in cave:
entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)]) if avail.swapped and cave_exit not in avail.exits:
invalid_cave_connections[tuple(cave)] = set() continue
entrances.remove(entrance) else:
connect_two_way(entrance, cave_exit, avail) entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)])
if avail.swapped and combine_map[entrance] != cave_exit: invalid_cave_connections[tuple(cave)] = set()
swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave) entrances.remove(entrance)
entrances.remove(swap_ent) connect_two_way(entrance, cave_exit, avail)
if avail.swapped and combine_map[entrance] != cave_exit:
swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave)
entrances.remove(swap_ent)
cave_options.remove(cave) cave_options.remove(cave)
if avail.swapped: if avail.swapped:
entrances.extend(swap_forbidden) entrances.extend(swap_forbidden)
+9 -9
View File
@@ -98,6 +98,7 @@ def roll_settings(weights):
ret.trap_door_mode = get_choice('trap_door_mode') ret.trap_door_mode = get_choice('trap_door_mode')
ret.key_logic_algorithm = get_choice('key_logic_algorithm') ret.key_logic_algorithm = get_choice('key_logic_algorithm')
ret.decoupledoors = get_choice('decoupledoors') == 'on' ret.decoupledoors = get_choice('decoupledoors') == 'on'
ret.door_self_loops = get_choice('door_self_loops') == 'on'
ret.experimental = get_choice('experimental') == 'on' ret.experimental = get_choice('experimental') == 'on'
ret.collection_rate = get_choice('collection_rate') == 'on' ret.collection_rate = get_choice('collection_rate') == 'on'
@@ -138,15 +139,14 @@ def roll_settings(weights):
ret.crystals_gt = get_choice('tower_open') ret.crystals_gt = get_choice('tower_open')
ret.crystals_ganon = get_choice('ganon_open') ret.crystals_ganon = get_choice('ganon_open')
from ItemList import set_default_triforce ret.triforce_pool = get_choice_default('triforce_pool', default=0)
default_tf_goal, default_tf_pool = set_default_triforce(ret.goal, 0, 0) ret.triforce_goal = get_choice_default('triforce_goal', default=0)
goal_min = get_choice_default('triforce_goal_min', default=default_tf_goal) ret.triforce_pool_min = get_choice_default('triforce_pool_min', default=0)
goal_max = get_choice_default('triforce_goal_max', default=default_tf_goal) ret.triforce_pool_max = get_choice_default('triforce_pool_max', default=0)
pool_min = get_choice_default('triforce_pool_min', default=default_tf_pool) ret.triforce_goal_min = get_choice_default('triforce_goal_min', default=0)
pool_max = get_choice_default('triforce_pool_max', default=default_tf_pool) ret.triforce_goal_max = get_choice_default('triforce_goal_max', default=0)
ret.triforce_goal = random.randint(int(goal_min), int(goal_max)) ret.triforce_min_difference = get_choice_default('triforce_min_difference', default=0)
min_diff = get_choice_default('triforce_min_difference', default=(default_tf_pool-default_tf_goal)) ret.triforce_max_difference = get_choice_default('triforce_max_difference', default=10000)
ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max))
ret.mode = get_choice('world_state') ret.mode = get_choice('world_state')
if ret.mode == 'retro': if ret.mode == 'retro':
+125
View File
@@ -0,0 +1,125 @@
meta:
players: 1
settings:
1:
door_shuffle: basic
intensity: 3
door_type_mode: all
doors:
1:
doors:
PoD Mimics 2 SW:
type: Trap Door
# TR Twin Pokeys NW: # not possible due to trap flags
# type: Trap Door
Thieves Blocked Entry SW:
type: Trap Door
Hyrule Dungeon Armory Interior Key Door N:
type: Trap Door
Desert Compass Key Door WN:
type: Trap Door
TR Tile Room SE:
type: Trap Door
# Mire Cross SW: # not possible due to trap flags
# type: Trap Door
Tower Circle of Pots ES:
type: Trap Door
Eastern Single Eyegore ES:
type: Trap Door
Eastern Duo Eyegores SE:
type: Trap Door
Swamp Push Statue S:
type: Trap Door
# Skull 2 East Lobby WS: # currently not possible due to trap flags
# type: Trap Door
GT Hope Room WN :
type: Trap Door
# Eastern Courtyard Ledge S: # currently not possible due to trap flags
# type: Trap Door
Ice Switch Room ES :
type: Trap Door
Ice Switch Room NE :
type: Trap Door
Skull Torch Room WS :
type: Trap Door
GT Speed Torch NE :
type: Trap Door
GT Speed Torch WS :
type: Trap Door
GT Torch Cross WN :
type: Trap Door
Mire Tile Room SW :
type: Trap Door
Mire Tile Room ES :
type: Trap Door
TR Torches WN :
type: Trap Door
PoD Lobby N:
type: Trap Door
PoD Middle Cage S:
type: Trap Door
Ice Bomb Jump NW:
type: Trap Door
GT Hidden Spikes SE:
type: Trap Door
Ice Tall Hint EN:
type: Trap Door
GT Conveyor Cross EN:
type: Trap Door
Eastern Pot Switch WN:
type: Trap Door
Thieves Conveyor Maze WN:
type: Trap Door
# Thieves Conveyor Maze SW: #not possible due to 4 door limit
# type: Trap Door
Eastern Dark Square Key Door WN:
type: Trap Door
Eastern Lobby NW:
type: Trap Door
Eastern Lobby NE:
type: Trap Door
# Ice Cross Bottom SE: # not possible due to trap flags
# type: Trap Door
Desert Back Lobby S:
type: Trap Door
# Desert West S: need enough lobbies for basic, should otherwise work
# type: Trap Door
Desert West Lobby ES:
type: Trap Door
# Mire Hidden Shooters SE: # not possible due to trap flags
# type: Trap Door
# Mire Hidden Shooters ES: # not possible due to trap flags
# type: Trap Door
Mire Hidden Shooters WS:
type: Trap Door
Tower Dark Pits EN:
type: Trap Door
Tower Dark Maze ES:
type: Trap Door
TR Tongue Pull WS:
type: Trap Door
# Lower layer: not valid
# Sewers Pull Switch N:
# type: Trap Door
# PoD Sexy Statue W: # not possible due to trap flags and low layer too, so likely not an exception
# type: Trap Door
# Not valid due to disappearing somaria block
# Mire Dark Shooters SE:
# type: Trap Door
# These triggers don't open doors
# Ice Compass Room NE:
# type: Trap Door
# Hera Torches NE:
# type: Trap Door
# Mire Spikes WS:
# type: Trap Door
# Mire Spikes SW:
# type: Trap Door
# Mire Spikes NW:
# type: Trap Door
# Tower Room 03 WN:
# type: Trap Door