Merged in DR v1.2.0.20

This commit is contained in:
codemann8
2023-08-07 15:30:50 -05:00
21 changed files with 376 additions and 76 deletions

View File

@@ -96,6 +96,7 @@ class World(object):
self.rooms = []
self._room_cache = {}
self.dungeon_layouts = {}
self.dungeon_pool = {}
self.inaccessible_regions = {}
self.enabled_entrances = {}
self.key_logic = {}
@@ -164,6 +165,7 @@ class World(object):
set_player_attr('colorizepots', True)
set_player_attr('pot_pool', {})
set_player_attr('decoupledoors', False)
set_player_attr('door_self_loops', False)
set_player_attr('door_type_mode', 'original')
set_player_attr('trap_door_mode', 'optional')
set_player_attr('key_logic_algorithm', 'default')
@@ -2023,6 +2025,7 @@ class Door(object):
self.dest = None
self.blocked = False # Indicates if the door is normally blocked off as an exit. (Sanc door or always closed)
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.smallKey = False # There's a small 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
def no_exit(self):
self.blocked = self.blocked_orig = True
self.blocked = self.blocked_orig = self.trapped = True
return self
def no_entrance(self):
@@ -2869,6 +2872,7 @@ class Spoiler(object):
'trap_door_mode': self.world.trap_door_mode,
'key_logic': self.world.key_logic_algorithm,
'decoupledoors': self.world.decoupledoors,
'door_self_loops': self.world.door_self_loops,
'dungeon_counters': self.world.dungeon_counters,
'item_pool': self.world.difficulty,
'item_functionality': self.world.difficulty_adjustments,
@@ -3115,6 +3119,7 @@ class Spoiler(object):
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('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('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player])
outfile.write('\n')
@@ -3396,7 +3401,7 @@ class Pot(object):
# 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,
'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10}
@@ -3421,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,
'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}
# byte 6: CCCC CPAA (crystals ganon, pyramid, access
# byte 6: ?CCC CPAA (crystals ganon, pyramid, access
access_mode = {"items": 0, "locations": 1, "none": 2}
# byte 7: B?MC DDEE (big, ?, maps, compass, door_type, enemies)
@@ -3489,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]]),
((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),
((8 if w.crystals_ganon_orig[p] == "random" else int(w.crystals_ganon_orig[p])) << 3)
@@ -3515,8 +3521,8 @@ class Settings(object):
(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]]),
((0x80 if w.pseudoboots[p] else 0) | overworld_map_mode[w.overworld_map[p]] << 6
| trap_door_mode[w.trap_door_mode[p]] << 4 | key_logic_algo[w.key_logic_algorithm[p]]),
((0x80 if w.pseudoboots[p] else 0) | overworld_map_mode[w.overworld_map[p]] << 5
| trap_door_mode[w.trap_door_mode[p]] << 3 | key_logic_algo[w.key_logic_algorithm[p]]),
])
return base64.b64encode(code, "+-".encode()).decode()
@@ -3554,12 +3560,13 @@ class Settings(object):
args.dropshuffle[p] = True if settings[4] & 0x10 else False
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]
cgt = (settings[5] & 0xf8) >> 3
cgt = (settings[5] & 0x78) >> 3
args.crystals_gt[p] = "random" if cgt == 8 else cgt
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.openpyramid[p] = True if settings[6] & 0x4 else False
@@ -3599,8 +3606,8 @@ class Settings(object):
if len(settings) > 14:
args.pseudoboots[p] = True if settings[14] & 0x80 else False
args.overworld_map[p] = r(overworld_map_mode)[(settings[14] & 0x60) >> 6]
args.trap_door_mode[p] = r(trap_door_mode)[(settings[14] & 0x14) >> 4]
args.overworld_map[p] = r(overworld_map_mode)[(settings[14] & 0x60) >> 5]
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]

3
CLI.py
View File

@@ -142,7 +142,7 @@ def parse_cli(argv, no_defaults=False):
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx',
'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)
if player == 1:
setattr(ret, name, {1: value})
@@ -227,6 +227,7 @@ def parse_settings():
"trap_door_mode": "optional",
"key_logic_algorithm": "default",
"decoupledoors": False,
"door_self_loops": False,
"experimental": False,
"dungeon_counters": "default",
"mixed_travel": "prevent",

View File

@@ -88,6 +88,7 @@ def link_doors_prep(world, player):
find_inaccessible_regions(world, player)
create_dungeon_pool(world, player)
if world.intensity[player] >= 3 and world.doorShuffle[player] != 'vanilla':
choose_portals(world, player)
else:
@@ -133,6 +134,20 @@ def create_dungeon_pool(world, player):
pool = None
if world.doorShuffle[player] == 'basic':
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':
groups = [['Hyrule Castle', 'Eastern Palace', 'Desert Palace', 'Tower of Hera', 'Agahnims Tower'],
['Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town'],
@@ -143,38 +158,17 @@ def create_dungeon_pool(world, player):
elif world.doorShuffle[player] != 'vanilla':
logging.getLogger('').error('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):
pool = create_dungeon_pool(world, player)
pool = world.dungeon_pool[player]
if pool:
main_dungeon_pool(pool, world, player)
if world.doorShuffle[player] != 'vanilla':
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):
logger = logging.getLogger('')
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):
if world.doorShuffle[player] != ['vanilla']:
shuffle_flag = world.doorShuffle[player] != 'basic'
allowed = {}
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}
allowed = {name: set(group[0]) for group in world.dungeon_pool[player] for name in group[0]}
# 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']
@@ -593,7 +577,7 @@ def customizer_portals(master_door_list, world, player):
assigned_doors.add(door)
# restricts connected doors to the customized portals
if assigned_doors:
pool = create_dungeon_pool(world, player)
pool = world.dungeon_pool[player]
if pool:
pool_map = {}
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])
remaining -= len(custom_trap_doors[dungeon])
ttl += len(builder.candidates.trap)
if ttl == 0:
if ttl == 0 and all(len(custom_trap_doors[dungeon]) == 0 for dungeon in pool):
continue
for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon]
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)
remaining -= suggested
suggestion_map[dungeon] = suggested
@@ -1997,6 +1981,9 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_
remaining = max(0, remaining)
for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon]
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))
cand_len = max(0, len(builder.candidates.small) - builder.key_drop_cnt)
@@ -2227,9 +2214,10 @@ def find_valid_trap_combination(builder, suggested, start_regions, paths, world,
sample_list = build_sample_list(combinations, 1000)
proposal = kth_combination(sample_list[itr], trap_door_pool, trap_doors_needed)
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)
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
if itr >= len(sample_list):
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
if portal_entrance_region not in builder.path_entrances:
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'):
excluded[region] = None
if portal is None:
@@ -2359,10 +2353,12 @@ def reassign_trap_doors(trap_map, world, player):
elif kind in [DoorKind.Trap2, DoorKind.TrapTriggerable]:
room.change(d.doorListPos, DoorKind.Normal)
d.blocked = False
d.trapped = False
# connect_one_way(world, d.name, d.dest.name, player)
elif d.type is DoorType.Normal and d not in traps:
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
d.blocked = False
d.trapped = False
for d in traps:
change_door_to_trap(d, world, 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]:
new_kind = DoorKind.TrapTriggerable
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
verify_door_list_pos(d, room, world, player, pos)
d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1, 3: 0x8}[d.doorListPos]
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 = None
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)
d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1}[d.doorListPos]
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 = 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):
if world.door_type_mode[player] != 'original': # big, all, chaos
# traverse dungeon and find candidates

View File

@@ -1677,10 +1677,24 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p
random.shuffle(sector_list)
orig_location_set = build_orig_location_set(dungeon_map)
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())}
next_sector = sector_list.pop()
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:
break
choices[choice].append(next_sector)
@@ -1691,7 +1705,7 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p
valid = True
for d_name, idx in d_idx.items():
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:
valid = False
break
@@ -1699,7 +1713,6 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p
if len(sector_list) == 0:
choices = defaultdict(list)
sector_list = list(free_location_sectors)
else:
next_sector = sector_list.pop()
else:
choices[choice].remove(next_sector)
@@ -1709,7 +1722,7 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p
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 = []
totals = []
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.update(set().union(*(s.chest_location_set for s in choices[dungeon_builder])))
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:
population.append(dungeon_builder)
choice = random.choice(population) if len(population) > 0 else None
@@ -1775,7 +1788,7 @@ def count_reserved_locations(world, player, proposed_set):
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 = []
some_c_switches_present = False
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:
some_c_switches_present = True
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:
raise GenerationException('No crystal switches to assign. Ref %s' % next(iter(dungeon_map.keys())))
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_crystal(dungeon_map, candidate_sectors, global_pole)
crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors)
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers,
global_pole, len(crystal_barriers) > 0)
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole)
ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole)
for sector in leftover:
if sector.polarity().is_neutral():

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.classes.CustomSettings import CustomSettings
version_number = '1.2.0.19'
version_number = '1.2.0.20'
version_branch = '-u'
__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.key_logic_algorithm = args.key_logic_algorithm.copy()
world.decoupledoors = args.decoupledoors.copy()
world.door_self_loops = args.door_self_loops.copy()
world.experimental = args.experimental.copy()
world.dungeon_counters = args.dungeon_counters.copy()
world.fish = fish
@@ -519,6 +520,7 @@ def copy_world(world):
ret.beemizer = world.beemizer.copy()
ret.intensity = world.intensity.copy()
ret.decoupledoors = world.decoupledoors.copy()
ret.door_self_loops = world.door_self_loops.copy()
ret.experimental = world.experimental.copy()
ret.shopsanity = world.shopsanity.copy()
ret.dropshuffle = world.dropshuffle.copy()

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 - 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 - 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 - 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)'],

View File

@@ -109,6 +109,15 @@ These are now independent of retro mode and have three options: None, Random, an
# 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

2
Rom.py
View File

@@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '32f6a9f479f6ccb7e66e9906ff0d0e4c'
RANDOMIZERBASEHASH = 'da111397d4118054e5ab4b9375cfb9e4'
class JsonRom(object):

View File

@@ -383,11 +383,18 @@ def global_rules(world, player):
# Start of door rando rules
# 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
# 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 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))
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.
set_defeat_dungeon_boss_rule(world.get_location('Eastern Palace - Prize', player))
@@ -412,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 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 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_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 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))
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 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))
@@ -467,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 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))
hidden_pits_door = world.get_door('Skull Small Hall WS', player)
@@ -504,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_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_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))
@@ -518,6 +534,12 @@ def global_rules(world, 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:
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 - Prize', player))
@@ -538,8 +560,15 @@ def global_rules(world, 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 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 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 - Prize', player))
@@ -555,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 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))
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 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))
@@ -574,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_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 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:
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 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))
@@ -612,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 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))
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))
# 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
@@ -871,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 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:
if bombable:
add_rule(world.get_entrance(killdoor, player), lambda state: (state.can_use_bombs(player) or state.can_kill_most_things(player)))
else:
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('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
('Hyrule Castle - Map Guard Key Drop', True),
@@ -1400,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('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))
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('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player))
@@ -1413,7 +1476,7 @@ def swordless_rules(world, 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
# todo: new traps
std_kill_rooms = {
'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
@@ -1444,6 +1507,18 @@ std_kill_rooms = {
'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE'] # Wizzrobes. Bombs don't work
} # 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):
parent = world.get_region(parent_name, player)
target = world.get_region(target_name, player)
@@ -1496,6 +1571,10 @@ def standard_rules(world, player):
if region.name in std_kill_rooms:
for ent in std_kill_rooms[region.name]:
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_entrance('Hyrule Castle Throne Room Tapestry', player), lambda state: state.has('Zelda Herself', player))
@@ -1686,6 +1765,11 @@ def set_bunny_rules(world, player, inverted):
if bunny_exit.connected_region and is_bunny(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 doors_to_check if x.type in [DoorType.Normal, DoorType.Interior] and not x.blocked]
for door in doors_to_check:
@@ -1817,6 +1901,17 @@ bunny_impassible_doors = {
'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):
key_logic = world.key_logic[player]

Binary file not shown.

View File

@@ -52,6 +52,9 @@
partial: 0
strict: 0
decoupledoors: off
door_self_loops:
on: 1
off: 1
dropshuffle:
on: 1
off: 1

View File

@@ -31,6 +31,9 @@ key_logic_algorithm:
decoupledoors:
off: 9 # more strict
on: 1
door_self_loops:
on: 1
off: 1
dropshuffle:
on: 1
off: 1

View File

@@ -259,6 +259,10 @@
"action": "store_true",
"type": "bool"
},
"door_self_loops": {
"action": "store_true",
"type": "bool"
},
"experimental": {
"action": "store_true",
"type": "bool"

View File

@@ -266,6 +266,7 @@
"door_shuffle": [
"Select Door Shuffling Algorithm. (default: %(default)s)",
"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",
"Crossed: Doors are mixed between all dungeons.",
"Vanilla: All doors are connected the same way they were in the",
@@ -299,6 +300,7 @@
"strict: Ensure small keys are available"
],
"decoupledoors" : [ "Door entrances and exits are decoupled" ],
"door_self_loops" : [ "Spiral stairs are allowed to self-loop" ],
"experimental": [ "Enable experimental features. (default: %(default)s)" ],
"dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ],
"crystals_ganon": [

View File

@@ -58,10 +58,12 @@
"randomizer.dungeon.smallkeyshuffle.universal": "Universal",
"randomizer.dungeon.bigkeyshuffle": "Big Keys",
"randomizer.dungeon.decoupledoors": "Decouple Doors",
"randomizer.dungeon.door_self_loops": "Allow Self-Looping Spiral Stairs",
"randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle",
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",
"randomizer.dungeon.dungeondoorshuffle.basic": "Basic",
"randomizer.dungeon.dungeondoorshuffle.paired": "Paired",
"randomizer.dungeon.dungeondoorshuffle.partitioned": "Partitioned",
"randomizer.dungeon.dungeondoorshuffle.crossed": "Crossed",

View File

@@ -66,6 +66,12 @@
}
},
"decoupledoors": {
"type": "checkbox",
"config": {
"padx": [20,0]
}
},
"door_self_loops": {
"type": "checkbox",
"config": {
"padx": [20,0],

View File

@@ -123,6 +123,7 @@ class CustomSettings(object):
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.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.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])
@@ -256,6 +257,7 @@ class CustomSettings(object):
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]['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]['mode'] = world.mode[p]
settings_dict[p]['swords'] = world.swords[p]

View File

@@ -116,6 +116,7 @@ SETTINGSTOPROCESS = {
"door_type_mode": "door_type_mode",
"trap_door_mode": "trap_door_mode",
"decoupledoors": "decoupledoors",
"door_self_loops": "door_self_loops",
"experimental": "experimental",
"dungeon_counters": "dungeon_counters",
"mixed_travel": "mixed_travel",

View File

@@ -22,7 +22,7 @@ def generate_dungeon(builder, entrance_region_names, split_dungeon, world, playe
queue = collections.deque(proposed_map.items())
while len(queue) > 0:
a, b = queue.popleft()
if world.decoupledoors[player]:
if a == b or world.decoupledoors[player]:
connect_doors_one_way(a, b)
else:
connect_doors(a, b)
@@ -128,14 +128,14 @@ def create_random_proposal(doors_to_connect, world, player):
next_hook = random.choice(hooks_left)
primary_door = random.choice(primary_bucket[next_hook])
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],
primary_door, secondary_door, world, player)):
secondary_door = random.choice(secondary_bucket[opp_hook])
proposal[primary_door] = secondary_door
primary_bucket[next_hook].remove(primary_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
primary_bucket[opp_hook].remove(secondary_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)
new_door = random.choice(unvisted_bucket[opp_hook])
old_target = proposed_map[attempt]
proposed_map[attempt] = new_door
if not world.decoupledoors[player]:
old_attempt = proposed_map[new_door]
else:
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
if not world.decoupledoors[player]:
proposed_map[old_target] = old_attempt

View File

@@ -98,6 +98,7 @@ def roll_settings(weights):
ret.trap_door_mode = get_choice('trap_door_mode')
ret.key_logic_algorithm = get_choice('key_logic_algorithm')
ret.decoupledoors = get_choice('decoupledoors') == 'on'
ret.door_self_loops = get_choice('door_self_loops') == 'on'
ret.experimental = get_choice('experimental') == 'on'
ret.collection_rate = get_choice('collection_rate') == 'on'

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