Ice Palace added
Dynamic logical doors added for ice cross w/ push block Improved crystal switch pathing Minor update to PoD
This commit is contained in:
@@ -959,6 +959,8 @@ class Door(object):
|
||||
self.ugly = False # Indicates that it can't be seen from the front (e.g. back of a big key door)
|
||||
self.crystal = CrystalBarrier.Null # How your crystal state changes if you use this door
|
||||
self.req_event = None # if a dungeon event is required for this door - swamp palace mostly
|
||||
self.controller = None
|
||||
self.dependents = []
|
||||
|
||||
def getAddress(self):
|
||||
if self.type == DoorType.Normal:
|
||||
|
||||
652
DoorShuffle.py
652
DoorShuffle.py
@@ -5,11 +5,11 @@ import logging
|
||||
import operator as op
|
||||
|
||||
from functools import reduce
|
||||
from BaseClasses import RegionType, Door, DoorType, Direction, Sector, Polarity, pol_idx, pol_inc
|
||||
from BaseClasses import RegionType, Door, DoorType, Direction, Sector, Polarity
|
||||
from Dungeons import hyrule_castle_regions, eastern_regions, desert_regions, hera_regions, tower_regions, pod_regions
|
||||
from Dungeons import dungeon_regions, region_starts, split_region_starts, dungeon_keys, dungeon_bigs
|
||||
from RoomData import DoorKind, PairedDoor
|
||||
from DungeonGenerator import ExplorationState, extend_reachable_state, convert_regions, generate_dungeon
|
||||
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon
|
||||
|
||||
|
||||
def link_doors(world, player):
|
||||
@@ -112,33 +112,6 @@ oppositemap = {
|
||||
Direction.Down: Direction.Up,
|
||||
}
|
||||
|
||||
similar_directions = {
|
||||
Direction.South: [Direction.South],
|
||||
Direction.North: [Direction.North],
|
||||
Direction.West: [Direction.West],
|
||||
Direction.East: [Direction.East],
|
||||
Direction.Up: [Direction.Down, Direction.Up],
|
||||
Direction.Down: [Direction.Down, Direction.Up],
|
||||
}
|
||||
|
||||
paired_directions = {
|
||||
Direction.South: [Direction.North],
|
||||
Direction.North: [Direction.South],
|
||||
Direction.West: [Direction.East],
|
||||
Direction.East: [Direction.West],
|
||||
Direction.Up: [Direction.Down, Direction.Up],
|
||||
Direction.Down: [Direction.Down, Direction.Up],
|
||||
}
|
||||
|
||||
allied_directions = {
|
||||
Direction.South: [Direction.North, Direction.South],
|
||||
Direction.North: [Direction.North, Direction.South],
|
||||
Direction.West: [Direction.East, Direction.West],
|
||||
Direction.East: [Direction.East, Direction.West],
|
||||
Direction.Up: [Direction.Down, Direction.Up],
|
||||
Direction.Down: [Direction.Down, Direction.Up],
|
||||
|
||||
}
|
||||
|
||||
def switch_dir(direction):
|
||||
return oppositemap[direction]
|
||||
@@ -235,8 +208,41 @@ def pair_existing_key_doors(world, player, door_a, door_b):
|
||||
# for paired_door in world.paired_doors[player]:
|
||||
# paired_door.pair = False
|
||||
|
||||
|
||||
def within_dungeon(world, player):
|
||||
fix_big_key_doors_with_ugly_smalls(world, player)
|
||||
overworld_prep(world, player)
|
||||
dungeon_sectors = []
|
||||
for key in dungeon_regions.keys():
|
||||
sector_list = convert_to_sectors(dungeon_regions[key], world, player)
|
||||
if key in split_region_starts.keys():
|
||||
split_sectors = split_up_sectors(sector_list, split_region_starts[key])
|
||||
for idx, sub_sector_list in enumerate(split_sectors):
|
||||
dungeon_sectors.append((key, sub_sector_list, split_region_starts[key][idx]))
|
||||
# todo: shuffable entrances like pinball, left pit need to be added to entrance list
|
||||
else:
|
||||
dungeon_sectors.append((key, sector_list, region_starts[key]))
|
||||
|
||||
dungeon_layouts = []
|
||||
for key, sector_list, entrance_list in dungeon_sectors:
|
||||
ds = generate_dungeon(sector_list, entrance_list, world, player)
|
||||
ds.name = key
|
||||
dungeon_layouts.append((ds, entrance_list))
|
||||
|
||||
combine_layouts(dungeon_layouts)
|
||||
world.dungeon_layouts[player] = {}
|
||||
for sector, entrances in dungeon_layouts:
|
||||
world.dungeon_layouts[player][sector.name] = (sector, entrances)
|
||||
|
||||
remove_inaccessible_entrances(world, player)
|
||||
paths = determine_required_paths(world)
|
||||
check_required_paths(paths, world, player)
|
||||
|
||||
# shuffle_key_doors for dungeons
|
||||
for sector, entrances in world.dungeon_layouts[player].values():
|
||||
shuffle_key_doors(sector, entrances, world, player)
|
||||
|
||||
|
||||
def within_dungeon_legacy(world, player):
|
||||
# TODO: The "starts" regions need access logic
|
||||
# Aerinon's note: I think this is handled already by ER Rules - may need to check correct requirements
|
||||
dungeon_region_starts_es = ['Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Sewers Secret Room']
|
||||
@@ -260,6 +266,7 @@ def within_dungeon(world, player):
|
||||
for key in dungeon_regions.keys():
|
||||
world.dungeon_layouts[player][key] = (key, region_starts[key])
|
||||
|
||||
|
||||
def shuffle_dungeon(world, player, start_region_names, dungeon_region_names):
|
||||
logger = logging.getLogger('')
|
||||
# Part one - generate a random layout
|
||||
@@ -422,8 +429,8 @@ def doors_fit_mandatory_pair(pair_list, a, b):
|
||||
# goals:
|
||||
# 1. have enough chests to be interesting (2 more than dungeon items)
|
||||
# 2. have a balanced amount of regions added (check)
|
||||
# 3. prevent soft locks due to key usage
|
||||
# 4. rules in place to affect item placement (lamp, keys, etc.)
|
||||
# 3. prevent soft locks due to key usage (algorithm written)
|
||||
# 4. rules in place to affect item placement (lamp, keys, etc. -- in rules)
|
||||
# 5. to be complete -- all doors linked (check, somewhat)
|
||||
# 6. avoid deadlocks/dead end dungeon (check)
|
||||
# 7. certain paths through dungeon must be possible - be able to reach goals (check)
|
||||
@@ -445,10 +452,10 @@ def cross_dungeon(world, player):
|
||||
dungeon_sectors.append((name, sector_list, region_starts[name]))
|
||||
# todo - adjust dungeon item pools -- ?
|
||||
dungeon_layouts = []
|
||||
for key, sector_list, entrance_list in dungeon_sectors:
|
||||
ds = shuffle_dungeon_no_repeats_new(world, player, sector_list, entrance_list)
|
||||
ds.name = key
|
||||
dungeon_layouts.append((ds, entrance_list))
|
||||
# for key, sector_list, entrance_list in dungeon_sectors:
|
||||
# ds = shuffle_dungeon_no_repeats_new(world, player, sector_list, entrance_list)
|
||||
# ds.name = key
|
||||
# dungeon_layouts.append((ds, entrance_list))
|
||||
|
||||
combine_layouts(dungeon_layouts)
|
||||
|
||||
@@ -490,13 +497,6 @@ def experiment(world, player):
|
||||
shuffle_key_doors(sector, entrances, world, player)
|
||||
|
||||
|
||||
def convert_regions(region_names, world, player):
|
||||
region_list = []
|
||||
for name in region_names:
|
||||
region_list.append(world.get_region(name, player))
|
||||
return region_list
|
||||
|
||||
|
||||
def convert_to_sectors(region_names, world, player):
|
||||
region_list = convert_regions(region_names, world, player)
|
||||
sectors = []
|
||||
@@ -523,7 +523,7 @@ def convert_to_sectors(region_names, world, player):
|
||||
new_sector = False
|
||||
else:
|
||||
door = world.check_for_door(ext.name, player)
|
||||
if door is not None:
|
||||
if door is not None and door.controller is None:
|
||||
outstanding_doors.append(door)
|
||||
if new_sector:
|
||||
sector = Sector()
|
||||
@@ -686,495 +686,10 @@ def find_proposal(proposal, buckets, candidates):
|
||||
return proposal
|
||||
|
||||
|
||||
def extend_state_backward(search_regions, state, world, player):
|
||||
local_state = state.copy()
|
||||
for region in search_regions:
|
||||
local_state.visit_region(region)
|
||||
local_state.add_all_entrance_doors_check_unattached(region, world, player)
|
||||
while len(local_state.avail_doors) > 0:
|
||||
explorable_door = local_state.next_avail_door()
|
||||
entrance = world.get_entrance(explorable_door.door.name, player)
|
||||
connect_region = entrance.parent_region
|
||||
if connect_region is not None:
|
||||
if valid_region_to_explore(connect_region, world) and not local_state.visited(connect_region):
|
||||
local_state.visit_region(connect_region)
|
||||
local_state.add_all_entrance_doors_check_unattached(connect_region, world, player)
|
||||
return local_state
|
||||
|
||||
|
||||
# code below is for an algorithm without restarts
|
||||
# todo: this sometimes generates two independent parts - that could be valid if the entrances are accessible
|
||||
# todo: prevent crystal barrier dead ends
|
||||
def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_region_names):
|
||||
logger = logging.getLogger('')
|
||||
random.shuffle(available_sectors)
|
||||
for sector in available_sectors:
|
||||
random.shuffle(sector.outstanding_doors)
|
||||
|
||||
entrance_regions = convert_regions(entrance_region_names, world, player)
|
||||
|
||||
state = extend_reachable_state(entrance_regions, ExplorationState(), world, player)
|
||||
# Loop until all available doors are used
|
||||
while len(state.unattached_doors) > 0:
|
||||
# Pick a random available door to connect
|
||||
explorable_door = random.choice(state.unattached_doors)
|
||||
door = explorable_door.door
|
||||
sector = find_sector_for_door(door, available_sectors)
|
||||
if sector is None:
|
||||
state.unattached_doors.remove(explorable_door)
|
||||
continue
|
||||
sector.outstanding_doors.remove(door)
|
||||
# door_connected = False
|
||||
logger.debug('Linking %s', door.name)
|
||||
# Find an available region that has a compatible door
|
||||
reachable_doors = [d.door for d in state.unattached_doors]
|
||||
compatibles = find_all_compatible_door_in_sectors_ex(door, available_sectors, reachable_doors)
|
||||
while len(compatibles) > 0:
|
||||
connect_sector, connect_door = compatibles.pop()
|
||||
logger.debug(' Found possible new sector via %s', connect_door.name)
|
||||
# Check if valid
|
||||
if is_valid(door, connect_door, sector, connect_sector, available_sectors, reachable_doors, state, world, player):
|
||||
# Apply connection and add the new region's doors to the available list
|
||||
maybe_connect_two_way(world, door, connect_door, player)
|
||||
state.unattached_doors.remove(explorable_door)
|
||||
connect_sector.outstanding_doors.remove(connect_door)
|
||||
if sector != connect_sector: # combine if not the same
|
||||
available_sectors.remove(connect_sector)
|
||||
sector.outstanding_doors.extend(connect_sector.outstanding_doors)
|
||||
sector.regions.extend(connect_sector.regions)
|
||||
if not door.blocked:
|
||||
connect_region = world.get_entrance(door.dest.name, player).parent_region
|
||||
state = extend_reachable_state([connect_region], state, world, player)
|
||||
break # skips else block below
|
||||
logger.debug(' Not Linking %s to %s', door.name, connect_door.name)
|
||||
if len(compatibles) == 0: # time to try again
|
||||
if len(state.unattached_doors) <= 1:
|
||||
raise Exception('Rejected last option due to dead end... infinite loop ensues')
|
||||
else:
|
||||
# If there's no available region with a door, use an internal connection
|
||||
compatibles = find_all_compatible_door_in_list(door, reachable_doors)
|
||||
while len(compatibles) > 0:
|
||||
connect_door = compatibles.pop()
|
||||
logger.debug(' Adding loop via %s', connect_door.name)
|
||||
connect_sector = find_sector_for_door(connect_door, available_sectors)
|
||||
# Check if valid
|
||||
if is_loop_valid(door, connect_door, sector, connect_sector, available_sectors):
|
||||
maybe_connect_two_way(world, door, connect_door, player)
|
||||
state.unattached_doors.remove(explorable_door)
|
||||
state.unattached_doors[:] = [ed for ed in state.unattached_doors if ed.door != connect_door]
|
||||
connect_sector = find_sector_for_door(connect_door, available_sectors)
|
||||
connect_sector.outstanding_doors.remove(connect_door)
|
||||
if sector != connect_sector: # combine if not the same
|
||||
available_sectors.remove(connect_sector)
|
||||
sector.outstanding_doors.extend(connect_sector.outstanding_doors)
|
||||
sector.regions.extend(connect_sector.regions)
|
||||
break # skips else block with exception
|
||||
else:
|
||||
logger.debug(' Not Linking %s to %s', door.name, connect_door.name)
|
||||
sector.outstanding_doors.insert(0, door)
|
||||
if len(state.unattached_doors) <= 2:
|
||||
raise Exception('Rejected last option due to likely improper loops...')
|
||||
else:
|
||||
raise Exception('Nothing is apparently compatible with %s' % door.name)
|
||||
# Check that we used everything, we failed otherwise
|
||||
if len(available_sectors) != 1:
|
||||
logger.warning('Failed to add all regions/doors to dungeon, generation will likely fail.')
|
||||
return available_sectors[0]
|
||||
|
||||
|
||||
def valid_region_to_explore(region, world):
|
||||
return region.type == RegionType.Dungeon or region.name in world.inaccessible_regions
|
||||
|
||||
|
||||
def find_sector_for_door(door, sectors):
|
||||
for sector in sectors:
|
||||
if door in sector.outstanding_doors:
|
||||
return sector
|
||||
return None
|
||||
|
||||
|
||||
def find_all_compatible_door_in_sectors_ex(door, sectors, reachable_doors):
|
||||
result = []
|
||||
for sector in sectors:
|
||||
for proposed_door in sector.outstanding_doors:
|
||||
if doors_compatible_ignore_keys(door, proposed_door) and proposed_door not in reachable_doors:
|
||||
result.append((sector, proposed_door))
|
||||
return result
|
||||
|
||||
|
||||
def find_all_compatible_door_in_list(door, doors):
|
||||
result = []
|
||||
for proposed_door in doors:
|
||||
if proposed_door != door and doors_compatible_ignore_keys(door, proposed_door):
|
||||
result.append(proposed_door)
|
||||
return result
|
||||
|
||||
|
||||
# this method also assumes that sectors have been built appropriately - so only randomizable doors get here
|
||||
def doors_compatible_ignore_keys(a, b):
|
||||
if a.type != b.type:
|
||||
return False
|
||||
if a.type == DoorType.SpiralStairs and a != b:
|
||||
return True
|
||||
return a.direction == switch_dir(b.direction)
|
||||
|
||||
|
||||
# todo: path checking needed?
|
||||
def is_valid(door_a, door_b, sector_a, sector_b, available_sectors, reachable_doors, state, world, player):
|
||||
if len(available_sectors) == 1:
|
||||
return True
|
||||
if len(available_sectors) <= 2 and sector_a != sector_b:
|
||||
return True
|
||||
elif not are_there_outstanding_doors_of_type(door_a, door_b, sector_a, sector_b, available_sectors):
|
||||
return False
|
||||
elif early_loop_dies(door_a, sector_a, sector_b, available_sectors):
|
||||
return False
|
||||
elif logical_dead_end_3(door_a, door_b, state, world, player, available_sectors, reachable_doors):
|
||||
return False
|
||||
elif door_a.blocked and door_b.blocked: # I can't see this going well unless we are in loop generation...
|
||||
return False
|
||||
elif pinball_exception(door_a, door_b, sector_a):
|
||||
return True
|
||||
elif not door_a.blocked and not door_b.blocked and sector_a != sector_b:
|
||||
return sector_a.outflow() + sector_b.outflow() - 1 > 0 # door_a has been removed already, so a.outflow is reduced by one
|
||||
elif door_a.blocked or door_b.blocked and sector_a != sector_b:
|
||||
return sector_a.outflow() + sector_b.outflow() > 0
|
||||
elif sector_a == sector_b and not door_a.blocked and not door_b.blocked:
|
||||
return sector_a.outflow() - 1 > 0
|
||||
elif sector_a == sector_b and door_a.blocked or door_b.blocked:
|
||||
return sector_a.outflow() > 0
|
||||
return False # not sure how we got here, but it's a bad idea
|
||||
|
||||
|
||||
def is_loop_valid(door_a, door_b, sector_a, sector_b, available_sectors):
|
||||
if len(available_sectors) == 1:
|
||||
return True
|
||||
if len(available_sectors) == 2 and door_b not in sector_a.outstanding_doors:
|
||||
return True
|
||||
elif not door_a.blocked and not door_b.blocked and sector_a != sector_b:
|
||||
return sector_a.outflow() + sector_b.outflow() - 1 > 0 # door_a has been removed already, so a.outflow is reduced by one
|
||||
elif door_a.blocked or door_b.blocked and sector_a != sector_b:
|
||||
return sector_a.outflow() + sector_b.outflow() > 0
|
||||
elif sector_a == sector_b and not door_a.blocked and not door_b.blocked:
|
||||
return sector_a.outflow() - 1 > 0
|
||||
elif sector_a == sector_b and door_a.blocked or door_b.blocked:
|
||||
return sector_a.outflow() > 0
|
||||
return True # I think this is always true. Both blocked but we're connecting loops now, so dead end?
|
||||
|
||||
|
||||
def are_there_outstanding_doors_of_type(door_a, door_b, sector_a, sector_b, available_sectors):
|
||||
idx = pol_idx[door_a.direction][0]
|
||||
diff = sum_vector(available_sectors, lambda x: x.magnitude())[idx]-sector_b.magnitude()[idx]
|
||||
if diff == 0:
|
||||
return True # this case will cover all the doors of that DirectionPair
|
||||
only_neutral_left = True
|
||||
for sector in available_sectors:
|
||||
if sector != sector_b and sector.polarity()[idx] != 0:
|
||||
only_neutral_left = False
|
||||
break
|
||||
|
||||
if only_neutral_left:
|
||||
hooks_left = False
|
||||
for door in sector_a.outstanding_doors:
|
||||
if door != door_a and door != door_b and pol_idx[door.direction][0] == idx:
|
||||
hooks_left = True
|
||||
break
|
||||
if not hooks_left:
|
||||
for door in sector_b.outstanding_doors:
|
||||
if door != door_b and door != door_a and pol_idx[door.direction][0] == idx:
|
||||
hooks_left = True
|
||||
break
|
||||
return hooks_left
|
||||
return True
|
||||
|
||||
|
||||
def early_loop_dies(door_a, sector_a, sector_b, available_sectors):
|
||||
other_sectors = list(available_sectors)
|
||||
other_sectors.remove(sector_a)
|
||||
if sector_a != sector_b:
|
||||
other_sectors.remove(sector_b)
|
||||
current_pol = sector_a.polarity() + sector_b.polarity()
|
||||
current_mag = sum_vector([sector_a, sector_b], lambda x: x.magnitude())
|
||||
else:
|
||||
current_pol = sector_a.polarity()
|
||||
current_mag = sector_a.magnitude()
|
||||
idx, inc = pol_idx[door_a.direction]
|
||||
current_pol.vector[idx] = pol_inc[inc](current_pol[idx])
|
||||
current_mag[idx] -= 1
|
||||
other_mag = sum_vector(other_sectors, lambda x: x.magnitude())
|
||||
# other_polarity = reduce(lambda x, y: x+y, map(lambda x: x.polarity(), other_sectors))
|
||||
ttl_magnitude = 0
|
||||
for i in range(len(current_mag)):
|
||||
ttl_magnitude += 0 if current_pol[i] == 0 and other_mag[i] == 0 else current_mag[i]
|
||||
if ttl_magnitude == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def logical_dead_end(door_a, door_b, state, world, player, available_sectors, reachable_doors):
|
||||
region = world.get_entrance(door_b.name, player).parent_region
|
||||
new_state = extend_reachable_state([region], state, world, player)
|
||||
new_state.unattached_doors[:] = [x for x in new_state.unattached_doors if x.door not in [door_a, door_b]]
|
||||
if len(new_state.unattached_doors) == 0:
|
||||
return True
|
||||
d_type = door_a.type
|
||||
directions = paired_directions[door_a.direction]
|
||||
hook_directions = similar_directions[door_a.direction]
|
||||
number_of_hooks = 0
|
||||
opposing_hooks = 0
|
||||
for door in new_state.unattached_doors:
|
||||
if door.door.type == d_type and door.door.direction in hook_directions:
|
||||
number_of_hooks += 1
|
||||
if door.door.type == d_type and door.door.direction in directions:
|
||||
opposing_hooks += 1
|
||||
hooks_needed = 0
|
||||
visited_regions = set()
|
||||
outstanding_doors_of_type = 0
|
||||
outstanding_hooks = 0
|
||||
only_dead_ends = True
|
||||
for sector in available_sectors:
|
||||
for door in sector.outstanding_doors:
|
||||
if door_of_interest(door, door_b, d_type, directions, hook_directions, new_state):
|
||||
needed = True
|
||||
if door.direction in directions:
|
||||
outstanding_doors_of_type += 1
|
||||
region = world.get_entrance(door.name, player).parent_region
|
||||
local_state = extend_state_backward([region], ExplorationState(), world, player)
|
||||
if len(local_state.non_door_entrances) > 0 and not door.blocked:
|
||||
needed = False
|
||||
else:
|
||||
for exp_d in local_state.unattached_doors:
|
||||
if different_direction(exp_d.door, d_type, directions, hook_directions, reachable_doors):
|
||||
needed = False
|
||||
break
|
||||
region_set = set(local_state.visited_orange+local_state.visited_blue)
|
||||
if needed and len(visited_regions.intersection(region_set)) == 0 and door.direction in directions:
|
||||
hooks_needed += 1
|
||||
visited_regions.update(region_set)
|
||||
elif door.direction in hook_directions and not door.blocked:
|
||||
if opposing_hooks > 0 and more_than_one_hook(local_state, hook_directions):
|
||||
needed = False
|
||||
if not needed:
|
||||
outstanding_hooks += 1
|
||||
visited_regions.update(region_set)
|
||||
if not needed:
|
||||
only_dead_ends = False
|
||||
if outstanding_doors_of_type > 0 and ((number_of_hooks == 0 and only_dead_ends) or hooks_needed > number_of_hooks + outstanding_hooks):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def logical_dead_end_3(door_a, door_b, state, world, player, available_sectors, reachable_doors):
|
||||
region = world.get_entrance(door_b.name, player).parent_region
|
||||
new_state = extend_reachable_state([region], state, world, player)
|
||||
new_state.unattached_doors[:] = [x for x in new_state.unattached_doors if x.door not in [door_a, door_b]]
|
||||
if len(new_state.unattached_doors) == 0:
|
||||
return True
|
||||
current_hooks = defaultdict(lambda: 0)
|
||||
hooks_needed = defaultdict(set)
|
||||
outstanding_hooks = defaultdict(lambda: 0)
|
||||
outstanding_total = defaultdict(lambda: 0)
|
||||
potential_hooks = []
|
||||
only_dead_ends_vector = [True, True, True]
|
||||
avail_dirs = set()
|
||||
for exp_d in new_state.unattached_doors:
|
||||
hook_key = hook_id(exp_d.door)
|
||||
current_hooks[hook_key] += 1
|
||||
avail_dirs.update(allied_directions[exp_d.door.direction])
|
||||
for sector in available_sectors:
|
||||
for door in sector.outstanding_doors:
|
||||
if door != door_b and not state.in_door_list_ic(door, new_state.unattached_doors):
|
||||
opp_hook_key = opp_hook_id(door)
|
||||
outstanding_total[opp_hook_key] += 1
|
||||
if door.blocked:
|
||||
hooks_needed[opp_hook_key].add(door)
|
||||
else:
|
||||
dead_end, cross_interaction = True, False
|
||||
region = world.get_entrance(door.name, player).parent_region
|
||||
local_state = extend_state_backward([region], ExplorationState(), world, player)
|
||||
if len(local_state.non_door_entrances) > 0:
|
||||
dead_end = False # not a dead end
|
||||
cross_interaction = True
|
||||
elif len(local_state.unattached_doors) > 1:
|
||||
dead_end = False
|
||||
for exp_d in local_state.unattached_doors:
|
||||
if cross_door_interaction(exp_d.door, door, reachable_doors, avail_dirs):
|
||||
cross_interaction = True
|
||||
break
|
||||
if dead_end:
|
||||
hooks_needed[opp_hook_key].add(door)
|
||||
else:
|
||||
door_set = set([x.door for x in local_state.unattached_doors if x.door != door])
|
||||
potential_hooks.append((door, door_set))
|
||||
if cross_interaction:
|
||||
only_dead_ends_vector[pol_idx[door.direction][0]] = False
|
||||
logically_valid = False
|
||||
satisfying_hooks = []
|
||||
while not logically_valid:
|
||||
check_good = True
|
||||
for key in [Direction.North, Direction.South, Direction.East, Direction.West, DoorType.SpiralStairs]:
|
||||
dir = key if isinstance(key, Direction) else Direction.Up
|
||||
vector_idx = pol_idx[dir][0]
|
||||
ttl_hooks = current_hooks[key] + current_hooks[switch_dir(dir) if isinstance(key, Direction) else DoorType.SpiralStairs]
|
||||
if outstanding_total[key] > 0 and ttl_hooks == 0 and only_dead_ends_vector[vector_idx]:
|
||||
return True # no way to get to part of the dungeon
|
||||
hooks_wanted = hooks_needed[key]
|
||||
if outstanding_total[key] > 0 and len(hooks_wanted) > current_hooks[key] + outstanding_hooks[key]:
|
||||
check_good = False
|
||||
fixer = find_fixer(potential_hooks, key, hooks_wanted, [])
|
||||
if fixer is None:
|
||||
return True # couldn't find a fix
|
||||
else:
|
||||
apply_fix(fixer, potential_hooks, outstanding_hooks, hooks_needed, satisfying_hooks, key)
|
||||
if check_good and len(satisfying_hooks) > 0:
|
||||
check_good = False
|
||||
votes = defaultdict(lambda: 0) # I really don't like this as other votes could lead to a valid configuration
|
||||
door_dict = {}
|
||||
for hook_set in satisfying_hooks:
|
||||
if len(hook_set) == 0:
|
||||
return True # unsatisfiable condition
|
||||
for hookable in hook_set:
|
||||
votes[hookable.name] += 1
|
||||
door_dict[hookable.name] = hookable
|
||||
winner = None
|
||||
for door_name in votes.keys():
|
||||
if winner is None or votes[door_name] > votes[winner]:
|
||||
winner = door_name
|
||||
winning_hook = door_dict[winner]
|
||||
key = opp_hook_id(winning_hook)
|
||||
hooks_needed[key].add(winning_hook)
|
||||
satisfying_hooks[:] = [x for x in satisfying_hooks if winning_hook not in x]
|
||||
logically_valid = check_good
|
||||
return False # no logical dead ends!
|
||||
|
||||
# potential_fixers = []
|
||||
# skip = False
|
||||
# for hookable in hook_set:
|
||||
# key = opp_hook_id(hookable)
|
||||
# if len(hooks_needed[key]) < current_hooks[key] + outstanding_hooks[key]:
|
||||
# hooks_needed[key].add(hookable)
|
||||
# hooks_to_remove.append(hook_set)
|
||||
# hooks_to_remove.extend([x for x in satisfying_hooks if hookable in x])
|
||||
# skip = True
|
||||
# break
|
||||
# fixer = find_fixer(potential_hooks, key, hooks_needed[key], hook_set)
|
||||
# if fixer is not None:
|
||||
# potential_fixers.append(fixer)
|
||||
# if skip:
|
||||
# break
|
||||
# if len(potential_fixers) == 0:
|
||||
# return True # can't find fixers for this set
|
||||
# elif len(potential_fixers) >= 1:
|
||||
# check_good = False
|
||||
# fixer = potential_fixers[0] # just pick the first for now
|
||||
# apply_fix(fixer, potential_hooks, outstanding_hooks, hooks_needed, satisfying_hooks, hook_id(fixer[0]))
|
||||
# hooks_to_remove.append(hook_set)
|
||||
# # what's left - multiple set with multiple available fixes
|
||||
# if len(hooks_to_remove) == 0:
|
||||
# return True # can't seem to make progress yet
|
||||
# satisfying_hooks[:] = [x for x in satisfying_hooks if x not in hooks_to_remove]
|
||||
# logically_valid = check_good
|
||||
# return False # no logical dead ends!
|
||||
|
||||
|
||||
def hook_id(door):
|
||||
if door.type == DoorType.Normal:
|
||||
return door.direction
|
||||
if door.type == DoorType.SpiralStairs:
|
||||
return door.type
|
||||
return 'Some new door type'
|
||||
|
||||
|
||||
def opp_hook_id(door):
|
||||
if door.type == DoorType.Normal:
|
||||
return switch_dir(door.direction)
|
||||
if door.type == DoorType.SpiralStairs:
|
||||
return door.type
|
||||
return 'Some new door type'
|
||||
|
||||
|
||||
def find_fixer(potential_hooks, key, hooks_wanted, invalid_options):
|
||||
fixer = None
|
||||
for door, door_set in potential_hooks:
|
||||
if match_hook_key(door, key) and (len(hooks_wanted) > 1 or len(hooks_wanted.union(door_set)) > 1) and door not in invalid_options:
|
||||
if fixer is None or len(door_set) > len(fixer[1]): # choose the one with most options
|
||||
fixer = (door, door_set)
|
||||
return fixer
|
||||
|
||||
|
||||
def apply_fix(fixer, potential_hooks, outstanding_hooks, hooks_needed, satisfying_hooks, key):
|
||||
outstanding_hooks[key] += 1
|
||||
potential_hooks.remove(fixer)
|
||||
winnow_potential_hooks(potential_hooks, fixer[0]) #
|
||||
winnow_satisfying_hooks(satisfying_hooks, fixer[0])
|
||||
if len(fixer[1]) == 1:
|
||||
new_need = fixer[1].pop()
|
||||
hooks_needed[opp_hook_id(new_need)].add(new_need)
|
||||
# removes any hooks that are now fulfilled
|
||||
satisfying_hooks[:] = [x for x in satisfying_hooks if new_need not in x]
|
||||
else:
|
||||
satisfying_hooks.append(fixer[1])
|
||||
|
||||
|
||||
def match_hook_key(door, key):
|
||||
if isinstance(key, DoorType):
|
||||
return door.type == key
|
||||
if isinstance(key, Direction):
|
||||
return door.direction == key
|
||||
return False
|
||||
|
||||
|
||||
def winnow_potential_hooks(hooks, door_removal):
|
||||
for door, door_set in hooks:
|
||||
if door_removal in door_set:
|
||||
door_set.remove(door_removal)
|
||||
hooks[:] = [x for x in hooks if len(x[1]) > 0]
|
||||
|
||||
|
||||
def winnow_satisfying_hooks(satisfying, door_removal):
|
||||
for hook_set in satisfying:
|
||||
if door_removal in hook_set:
|
||||
hook_set.remove(door_removal)
|
||||
|
||||
|
||||
def door_of_interest(door, door_b, d_type, directions, hook_directions, state):
|
||||
if door == door_b or door.type != d_type:
|
||||
return False
|
||||
if door.direction not in directions and door.direction not in hook_directions:
|
||||
return False
|
||||
return not state.in_door_list_ic(door, state.unattached_doors)
|
||||
|
||||
|
||||
def different_direction(door, d_type, directions, hook_directions, reachable_doors):
|
||||
if door in reachable_doors:
|
||||
return False
|
||||
return door.type != d_type or (door.direction not in directions and door.direction not in hook_directions)
|
||||
|
||||
|
||||
def cross_door_interaction(door, original_door, reachable_doors, avail_dir):
|
||||
if door in reachable_doors or door == original_door:
|
||||
return False
|
||||
if door.type == original_door.type and door.direction in allied_directions[original_door.direction]: # revisit if cross-type linking ever happens
|
||||
return False
|
||||
return door.direction in avail_dir
|
||||
|
||||
|
||||
def more_than_one_hook(state, hook_directions):
|
||||
cnt = 0
|
||||
for exp_d in state.unattached_doors:
|
||||
if exp_d.door.direction in hook_directions:
|
||||
cnt += 1
|
||||
return cnt > 1
|
||||
|
||||
|
||||
def pinball_exception(door_a, door_b, sector_a):
|
||||
if door_a.name == 'Skull Pot Prison SE' and door_b.name == 'Skull Pinball NE':
|
||||
for r in sector_a.regions:
|
||||
if r.name == 'Skull 2 East Lobby':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def shuffle_key_doors(dungeon_sector, entrances, world, player):
|
||||
start_regions = convert_regions(entrances, world, player)
|
||||
# count number of key doors - this could be a table?
|
||||
@@ -1381,8 +896,9 @@ def validate_key_layout_r(state, flat_proposal, checked_states, key_logic, world
|
||||
for exp_door in state.small_doors:
|
||||
state_copy = state.copy()
|
||||
state_copy.opened_doors.append(exp_door.door)
|
||||
state_copy.avail_doors.append(exp_door)
|
||||
state_copy.small_doors.remove(exp_door)
|
||||
doors_to_open = [x for x in state_copy.small_doors if x.door == exp_door.door]
|
||||
state_copy.small_doors[:] = [x for x in state_copy.small_doors if x.door != exp_door.door]
|
||||
state_copy.avail_doors.extend(doors_to_open)
|
||||
dest_door = exp_door.door.dest
|
||||
if dest_door in flat_proposal:
|
||||
state_copy.opened_doors.append(dest_door)
|
||||
@@ -1521,6 +1037,7 @@ def determine_required_paths(world):
|
||||
'Swamp Palace': ['Swamp Boss'],
|
||||
'Skull Woods': ['Skull Boss'],
|
||||
'Thieves Town': ['Thieves Boss', ('Thieves Blind\'s Cell', 'Thieves Boss')],
|
||||
'Ice Palace': ['Ice Boss'],
|
||||
}
|
||||
if world.shuffle == 'vanilla':
|
||||
# paths['Skull Woods'].remove('Skull Boss') # is this necessary?
|
||||
@@ -1647,6 +1164,8 @@ logical_connections = [
|
||||
('Hera Big Chest Landing Exit', 'Hera 4F'),
|
||||
('PoD Arena Main Crystal Path', 'PoD Arena Crystal'),
|
||||
('PoD Arena Crystal Path', 'PoD Arena Main'),
|
||||
('PoD Arena Main Orange Barrier', 'PoD Arena North'),
|
||||
('PoD Arena North Drop Down', 'PoD Arena Main'),
|
||||
('PoD Arena Bridge Drop Down', 'PoD Arena Main'),
|
||||
('PoD Map Balcony Drop Down', 'PoD Sexy Statue'),
|
||||
('PoD Basement Ledge Drop Down', 'PoD Stalfos Basement'),
|
||||
@@ -1694,6 +1213,24 @@ logical_connections = [
|
||||
('Thieves Blocked Entry Path', 'Thieves Basement Block'),
|
||||
('Thieves Conveyor Bridge Block Path', 'Thieves Conveyor Block'),
|
||||
('Thieves Conveyor Block Path', 'Thieves Conveyor Bridge'),
|
||||
# ('Ice Cross Left Push Block', ''), # todo: vanilla connections
|
||||
# ('Ice Cross Right Push Block Bottom', ''),
|
||||
# ('Ice Cross Bottom Push Block Right', ''),
|
||||
# ('Ice Cross Top Push Block Right', ''),
|
||||
('Ice Cross Bottom Push Block Left', 'Ice Floor Switch'),
|
||||
('Ice Cross Right Push Block Top', 'Ice Bomb Drop'),
|
||||
('Ice Cross Top Push Block Left', 'Ice Floor Switch'),
|
||||
('Ice Big Key Push Block', 'Ice Dead End'),
|
||||
('Ice Bomb Jump Ledge Orange Barrier', 'Ice Bomb Jump Catwalk'),
|
||||
('Ice Bomb Jump Catwalk Orange Barrier', 'Ice Bomb Jump Ledge'),
|
||||
('Ice Hookshot Ledge Path', 'Ice Hookshot Balcony'),
|
||||
('Ice Hookshot Balcony Path', 'Ice Hookshot Ledge'),
|
||||
('Ice Crystal Right Orange Barrier', 'Ice Crystal Left'),
|
||||
('Ice Crystal Left Orange Barrier', 'Ice Crystal Right'),
|
||||
('Ice Crystal Left Blue Barrier', 'Ice Crystal Block'),
|
||||
('Ice Crystal Block Exit', 'Ice Crystal Left'),
|
||||
('Ice Big Chest Landing Push Blocks', 'Ice Big Chest View'),
|
||||
# ('', ''),
|
||||
# ('', ''),
|
||||
]
|
||||
|
||||
@@ -1728,6 +1265,12 @@ spiral_staircases = [
|
||||
('Swamp Behind Waterfall Up Stairs', 'Swamp C Down Stairs'),
|
||||
('Thieves Spike Switch Up Stairs', 'Thieves Attic Down Stairs'),
|
||||
('Thieves Conveyor Maze Down Stairs', 'Thieves Basement Block Up Stairs'),
|
||||
('Ice Jelly Key Down Stairs', 'Ice Floor Switch Up Stairs'),
|
||||
('Ice Narrow Corridor Down Stairs', 'Ice Pengator Trap Up Stairs'),
|
||||
('Ice Spike Room Up Stairs', 'Ice Hammer Block Down Stairs'),
|
||||
('Ice Spike Room Down Stairs', 'Ice Spikeball Up Stairs'),
|
||||
('Ice Lonely Freezor Down Stairs', 'Iced T Up Stairs'),
|
||||
('Ice Backwards Room Down Stairs', 'Ice Anti-Fairy Up Stairs'),
|
||||
# ('', ''),
|
||||
]
|
||||
|
||||
@@ -1779,6 +1322,15 @@ falldown_pits = [
|
||||
('Swamp Attic Right Pit', 'Swamp Barrier Ledge'),
|
||||
('Swamp Attic Left Pit', 'Swamp West Ledge'),
|
||||
('Skull Final Drop Hole', 'Skull Boss'),
|
||||
('Ice Bomb Drop Hole', 'Ice Stalfos Hint'),
|
||||
('Ice Falling Square Hole', 'Ice Tall Hint'),
|
||||
('Ice Freezors Hole', 'Ice Big Chest View'),
|
||||
('Ice Freezors Ledge Hole', 'Ice Big Chest View'),
|
||||
('Ice Freezors Bomb Hole', 'Ice Big Chest Landing'),
|
||||
('Ice Crystal Block Hole', 'Ice Switch Room'),
|
||||
('Ice Crystal Right Blue Hole', 'Ice Switch Room'),
|
||||
('Ice Backwards Room Hole', 'Ice Fairy'),
|
||||
('Ice Antechamber Hole', 'Ice Boss'),
|
||||
# ('', ''),
|
||||
]
|
||||
|
||||
@@ -1789,11 +1341,16 @@ dungeon_warps = [
|
||||
('PoD Warp Room Warp', 'PoD Warp Hint'),
|
||||
('PoD Stalfos Basement Warp', 'PoD Warp Room'),
|
||||
('PoD Callback Warp', 'PoD Dark Alley'),
|
||||
('Ice Fairy Warp', 'Ice Anti-Fairy'),
|
||||
# ('', ''),
|
||||
]
|
||||
|
||||
ladders = [
|
||||
('PoD Bow Statue Down Ladder', 'PoD Dark Pegs Up Ladder')
|
||||
('PoD Bow Statue Down Ladder', 'PoD Dark Pegs Up Ladder'),
|
||||
('Ice Big Key Down Ladder', 'Ice Tongue Pull Up Ladder'),
|
||||
('Ice Firebar Down Ladder', 'Ice Freezors Up Ladder'),
|
||||
# ('', ''),
|
||||
# ('', ''),
|
||||
]
|
||||
|
||||
interior_doors = [
|
||||
@@ -1878,6 +1435,20 @@ interior_doors = [
|
||||
('Thieves Lonely Zazak ES', 'Thieves Blind\'s Cell WS'),
|
||||
('Thieves Conveyor Bridge WS', 'Thieves Big Chest Room ES'),
|
||||
('Thieves Conveyor Block WN', 'Thieves Trap EN'),
|
||||
('Ice Lobby WS', 'Ice Jelly Key ES'),
|
||||
('Ice Floor Switch ES', 'Ice Cross Left WS'),
|
||||
('Ice Cross Top NE', 'Ice Bomb Drop SE'),
|
||||
('Ice Pengator Switch ES', 'Ice Dead End WS'),
|
||||
('Ice Stalfos Hint SE', 'Ice Conveyor NE'),
|
||||
('Ice Bomb Jump EN', 'Ice Narrow Corridor WN'),
|
||||
('Ice Spike Cross WS', 'Ice Firebar ES'),
|
||||
('Ice Spike Cross NE', 'Ice Falling Square SE'),
|
||||
('Ice Hammer Block ES', 'Ice Tongue Pull WS'),
|
||||
('Ice Freezors Ledge ES', 'Ice Tall Hint WS'),
|
||||
('Ice Hookshot Balcony SW', 'Ice Spikeball NW'),
|
||||
('Ice Crystal Right NE', 'Ice Backwards Room SE'),
|
||||
('Ice Crystal Left WS', 'Ice Big Chest View ES'),
|
||||
('Ice Anti-Fairy SE', 'Ice Switch Room NE'),
|
||||
# ('', ''),
|
||||
]
|
||||
|
||||
@@ -1965,8 +1536,18 @@ default_door_connections = [
|
||||
('Thieves Triple Bypass EN', 'Thieves Conveyor Maze WN'),
|
||||
('Thieves Basement Block WN', 'Thieves Conveyor Bridge EN'),
|
||||
('Thieves Lonely Zazak WS', 'Thieves Conveyor Bridge ES'),
|
||||
# ('', ''),
|
||||
# ('', ''),
|
||||
('Ice Cross Bottom SE', 'Ice Compass Room NE'),
|
||||
('Ice Cross Right ES', 'Ice Pengator Switch WS'),
|
||||
('Ice Conveyor SW', 'Ice Bomb Jump NW'),
|
||||
('Ice Pengator Trap NE', 'Ice Spike Cross SE'),
|
||||
('Ice Spike Cross ES', 'Ice Spike Room WS'),
|
||||
('Ice Tall Hint SE', 'Ice Lonely Freezor NW'),
|
||||
('Ice Tall Hint EN', 'Ice Hookshot Ledge WN'),
|
||||
('Iced T EN', 'Ice Catwalk WN'),
|
||||
('Ice Catwalk NW', 'Ice Many Pots SW'),
|
||||
('Ice Many Pots WS', 'Ice Crystal Right ES'),
|
||||
('Ice Switch Room ES', 'Ice Refill WS'),
|
||||
('Ice Switch Room SE', 'Ice Antechamber NE'),
|
||||
# ('', ''),
|
||||
]
|
||||
|
||||
@@ -2026,5 +1607,8 @@ dungeon_x_idx_to_name = {
|
||||
4: 'Agahnims Tower',
|
||||
5: 'Palace of Darkness',
|
||||
6: 'Swamp Palace',
|
||||
7: 'Skull Woods',
|
||||
8: 'Thieves Town',
|
||||
9: 'Ice Palace'
|
||||
# etc
|
||||
}
|
||||
|
||||
142
Doors.py
142
Doors.py
@@ -286,6 +286,8 @@ def create_doors(world, player):
|
||||
create_door(player, 'PoD Arena Main NW', Nrml).dir(Direction.North, 0x2a, Left, High).small_key().pos(1),
|
||||
create_door(player, 'PoD Arena Main NE', Nrml).dir(Direction.North, 0x2a, Right, High).no_exit().trap(0x4).pos(0),
|
||||
create_door(player, 'PoD Arena Main Crystal Path', Lgcl),
|
||||
create_door(player, 'PoD Arena Main Orange Barrier', Lgcl),
|
||||
create_door(player, 'PoD Arena North Drop Down', Lgcl),
|
||||
create_door(player, 'PoD Arena Crystals E', Nrml).dir(Direction.East, 0x2a, Mid, High).pos(3),
|
||||
create_door(player, 'PoD Arena Crystal Path', Lgcl),
|
||||
create_door(player, 'PoD Arena Bridge Drop Down', Lgcl),
|
||||
@@ -554,11 +556,106 @@ def create_doors(world, player):
|
||||
create_door(player, 'Thieves Conveyor Block WN', Intr).dir(Direction.West, 0x44, Top, High).pos(0),
|
||||
create_door(player, 'Thieves Trap EN', Intr).dir(Direction.East, 0x44, Left, Top).pos(0),
|
||||
|
||||
create_door(player, 'Ice Lobby WS', Intr).dir(Direction.West, 0x0e, Bot, High).pos(1),
|
||||
create_door(player, 'Ice Jelly Key ES', Intr).dir(Direction.East, 0x0e, Bot, High).pos(1),
|
||||
create_door(player, 'Ice Jelly Key Down Stairs', Sprl).dir(Direction.Down, 0x0e, 0, HTH).ss(X, 0x11, 0x80, True, True).small_key().pos(0),
|
||||
create_door(player, 'Ice Floor Switch Up Stairs', Sprl).dir(Direction.Up, 0x1e, 0, HTH).ss(X, 0x1a, 0x6c, True, True),
|
||||
create_door(player, 'Ice Floor Switch ES', Intr).dir(Direction.East, 0x1e, Bot, High).pos(1),
|
||||
create_door(player, 'Ice Cross Left WS', Intr).dir(Direction.West, 0x1e, Bot, High).pos(1),
|
||||
create_door(player, 'Ice Cross Top NE', Intr).dir(Direction.North, 0x1e, Right, High).pos(2),
|
||||
create_door(player, 'Ice Bomb Drop SE', Intr).dir(Direction.South, 0x1e, Right, High).pos(2),
|
||||
create_door(player, 'Ice Cross Left Push Block', Lgcl), # dynamic
|
||||
create_door(player, 'Ice Cross Bottom Push Block Left', Lgcl),
|
||||
create_door(player, 'Ice Cross Bottom Push Block Right', Lgcl), # dynamic
|
||||
create_door(player, 'Ice Cross Right Push Block Top', Lgcl),
|
||||
create_door(player, 'Ice Cross Right Push Block Bottom', Lgcl), # dynamic
|
||||
create_door(player, 'Ice Cross Top Push Block Left', Lgcl),
|
||||
create_door(player, 'Ice Cross Top Push Block Right', Lgcl), # dynamic
|
||||
create_door(player, 'Ice Cross Bottom SE', Nrml).dir(Direction.South, 0x1e, Right, High).pos(3),
|
||||
create_door(player, 'Ice Cross Right ES', Nrml).dir(Direction.East, 0x1e, Bot, High).pos(0),
|
||||
create_door(player, 'Ice Bomb Drop Hole', Hole),
|
||||
create_door(player, 'Ice Compass Room NE', Nrml).dir(Direction.North, 0x2e, Right, High).pos(0),
|
||||
create_door(player, 'Ice Pengator Switch WS', Nrml).dir(Direction.West, 0x1f, Bot, High).pos(0),
|
||||
create_door(player, 'Ice Pengator Switch ES', Intr).dir(Direction.East, 0x1f, Bot, High).pos(1),
|
||||
create_door(player, 'Ice Dead End WS', Intr).dir(Direction.West, 0x1f, Bot, High).pos(1),
|
||||
create_door(player, 'Ice Big Key Push Block', Lgcl),
|
||||
create_door(player, 'Ice Big Key Down Ladder', Lddr),
|
||||
create_door(player, 'Ice Stalfos Hint SE', Intr).dir(Direction.South, 0x3e, Right, High).pos(0),
|
||||
create_door(player, 'Ice Conveyor NE', Intr).dir(Direction.North, 0x3e, Right, High).no_exit().pos(0),
|
||||
create_door(player, 'Ice Conveyor SW', Nrml).dir(Direction.South, 0x3e, Left, High).small_key().pos(1),
|
||||
create_door(player, 'Ice Bomb Jump NW', Nrml).dir(Direction.North, 0x4e, Left, High).small_key().pos(1),
|
||||
create_door(player, 'Ice Bomb Jump Ledge Orange Barrier', Lgcl),
|
||||
create_door(player, 'Ice Bomb Jump Catwalk Orange Barrier', Lgcl),
|
||||
create_door(player, 'Ice Bomb Jump EN', Intr).dir(Direction.East, 0x4e, Top, High).pos(0),
|
||||
create_door(player, 'Ice Narrow Corridor WN', Intr).dir(Direction.West, 0x4e, Top, High).pos(0),
|
||||
create_door(player, 'Ice Narrow Corridor Down Stairs', Sprl).dir(Direction.Down, 0x4e, 0, HTH).ss(S, 0x52, 0xc0, True, True),
|
||||
create_door(player, 'Ice Pengator Trap Up Stairs', Sprl).dir(Direction.Up, 0x6e, 0, HTH).ss(S, 0x5a, 0xac, True, True),
|
||||
create_door(player, 'Ice Pengator Trap NE', Nrml).dir(Direction.North, 0x6e, Right, High).pos(0),
|
||||
create_door(player, 'Ice Spike Cross SE', Nrml).dir(Direction.South, 0x5e, Right, High).pos(2),
|
||||
create_door(player, 'Ice Spike Cross ES', Nrml).dir(Direction.East, 0x5e, Bot, High).small_key().pos(0),
|
||||
create_door(player, 'Ice Spike Cross WS', Intr).dir(Direction.West, 0x5e, Bot, High).pos(3),
|
||||
create_door(player, 'Ice Firebar ES', Intr).dir(Direction.East, 0x5e, Bot, High).pos(3),
|
||||
create_door(player, 'Ice Firebar Down Ladder', Lddr),
|
||||
create_door(player, 'Ice Spike Cross NE', Intr).dir(Direction.North, 0x5e, Right, High).pos(1),
|
||||
create_door(player, 'Ice Falling Square SE', Intr).dir(Direction.South, 0x5e, Right, High).pos(1),
|
||||
create_door(player, 'Ice Falling Square Hole', Hole),
|
||||
create_door(player, 'Ice Spike Room WS', Nrml).dir(Direction.West, 0x5f, Bot, High).small_key().pos(0),
|
||||
create_door(player, 'Ice Spike Room Up Stairs', Sprl).dir(Direction.Up, 0x5f, 4, HTH).ss(Z, 0x11, 0x48, True, True),
|
||||
create_door(player, 'Ice Spike Room Down Stairs', Sprl).dir(Direction.Down, 0x5f, 3, HTH).ss(Z, 0x1a, 0xa4, True, True),
|
||||
create_door(player, 'Ice Hammer Block Down Stairs', Sprl).dir(Direction.Down, 0x3f, 0, HTH).ss(Z, 0x11, 0xb8, True, True),
|
||||
create_door(player, 'Ice Hammer Block ES', Intr).dir(Direction.East, 0x3f, Bot, High).pos(0),
|
||||
create_door(player, 'Ice Tongue Pull WS', Intr).dir(Direction.West, 0x3f, Bot, High).pos(0),
|
||||
create_door(player, 'Ice Tongue Pull Up Ladder', Lddr),
|
||||
create_door(player, 'Ice Freezors Up Ladder', Lddr),
|
||||
create_door(player, 'Ice Freezors Hole', Hole),
|
||||
create_door(player, 'Ice Freezors Bomb Hole', Hole), # combine these two? -- they have to lead to the same spot
|
||||
create_door(player, 'Ice Freezors Ledge Hole', Hole),
|
||||
create_door(player, 'Ice Freezors Ledge ES', Intr).dir(Direction.East, 0x7e, Bot, High).pos(2),
|
||||
create_door(player, 'Ice Tall Hint WS', Intr).dir(Direction.West, 0x7e, Bot, High).pos(1),
|
||||
create_door(player, 'Ice Tall Hint EN', Nrml).dir(Direction.East, 0x7e, Top, High).pos(1),
|
||||
create_door(player, 'Ice Tall Hint SE', Nrml).dir(Direction.South, 0x7e, Right, High).small_key().pos(0),
|
||||
create_door(player, 'Ice Hookshot Ledge WN', Nrml).dir(Direction.West, 0x7f, Top, High).pos(0),
|
||||
create_door(player, 'Ice Hookshot Ledge Path', Lgcl),
|
||||
create_door(player, 'Ice Hookshot Balcony Path', Lgcl),
|
||||
create_door(player, 'Ice Hookshot Balcony SW', Intr).dir(Direction.South, 0x7f, Left, High).pos(1),
|
||||
create_door(player, 'Ice Spikeball NW', Intr).dir(Direction.North, 0x7f, Left, High).pos(1),
|
||||
create_door(player, 'Ice Spikeball Up Stairs', Sprl).dir(Direction.Up, 0x7f, 0, HTH).ss(Z, 0x1a, 0x34, True, True),
|
||||
create_door(player, 'Ice Lonely Freezor NE', Nrml).dir(Direction.North, 0x8e, Right, High).small_key().pos(0),
|
||||
create_door(player, 'Ice Lonely Freezor Down Stairs', Sprl).dir(Direction.Up, 0x8e, 0, HTH).ss(S, 0x11, 0x50, True, True),
|
||||
create_door(player, 'Iced T EN', Nrml).dir(Direction.East, 0xae, Top, High).pos(0),
|
||||
create_door(player, 'Iced T Up Stairs', Sprl).dir(Direction.Up, 0xae, 0, HTH).ss(S, 0x1a, 0x3c, True, True),
|
||||
create_door(player, 'Ice Catwalk WN', Nrml).dir(Direction.West, 0xaf, Top, High).pos(1),
|
||||
create_door(player, 'Ice Catwalk NW', Nrml).dir(Direction.North, 0xaf, Left, High).pos(0),
|
||||
create_door(player, 'Ice Many Pots SW', Nrml).dir(Direction.South, 0x9f, Left, High).pos(1),
|
||||
create_door(player, 'Ice Many Pots WS', Nrml).dir(Direction.West, 0x9f, Bot, High).pos(0),
|
||||
create_door(player, 'Ice Crystal Right ES', Nrml).dir(Direction.East, 0x9e, Bot, High).pos(3),
|
||||
create_door(player, 'Ice Crystal Right Orange Barrier', Lgcl),
|
||||
create_door(player, 'Ice Crystal Right Blue Hole', Hole), # combine holes again??
|
||||
create_door(player, 'Ice Crystal Left Orange Barrier', Lgcl),
|
||||
create_door(player, 'Ice Crystal Left Blue Barrier', Lgcl),
|
||||
create_door(player, 'Ice Crystal Block Exit', Lgcl),
|
||||
create_door(player, 'Ice Crystal Block Hole', Hole),
|
||||
create_door(player, 'Ice Crystal Left WS', Intr).dir(Direction.West, 0x9e, Bot, High).pos(2),
|
||||
create_door(player, 'Ice Big Chest View ES', Intr).dir(Direction.East, 0x9e, Bot, High).pos(2),
|
||||
create_door(player, 'Ice Big Chest Landing Push Blocks', Lgcl),
|
||||
create_door(player, 'Ice Crystal Right NE', Intr).dir(Direction.North, 0x9e, Right, High).big_key().pos(1),
|
||||
create_door(player, 'Ice Backwards Room SE', Intr).dir(Direction.South, 0x9e, Right, High).pos(1),
|
||||
create_door(player, 'Ice Backwards Room Down Stairs', Sprl).dir(Direction.Down, 0x9e, 0, HTH).ss(S, 0x11, 0x80, True, True).small_key().pos(0),
|
||||
create_door(player, 'Ice Backwards Room Hole', Hole),
|
||||
create_door(player, 'Ice Anti-Fairy Up Stairs', Sprl).dir(Direction.Up, 0xbe, 0, HTH).ss(S, 0x1a, 0x6c, True, True),
|
||||
create_door(player, 'Ice Anti-Fairy SE', Intr).dir(Direction.South, 0xbe, Right, High).pos(2),
|
||||
create_door(player, 'Ice Switch Room NE', Intr).dir(Direction.North, 0xbe, Right, High).pos(2),
|
||||
create_door(player, 'Ice Switch Room ES', Nrml).dir(Direction.East, 0xbe, Bot, High).small_key().pos(1),
|
||||
create_door(player, 'Ice Switch Room SE', Nrml).dir(Direction.South, 0xbe, Right, High).pos(0),
|
||||
create_door(player, 'Ice Refill WS', Nrml).dir(Direction.West, 0xbf, Bot, High).small_key().pos(0),
|
||||
create_door(player, 'Ice Fairy Warp', Warp),
|
||||
create_door(player, 'Ice Antechamber NE', Nrml).dir(Direction.North, 0xce, Right, High).pos(0),
|
||||
create_door(player, 'Ice Antechamber Hole', Hole),
|
||||
|
||||
# Door Templates
|
||||
# create_door(player, '', Nrml).dir(Direction.North, 0x00, Mid, High).pos(),
|
||||
# create_door(player, '', Intr).dir(Direction.North, 0x00, Left, High).pos(),
|
||||
# create_door(player, '', Sprl).dir(Direction.North, 0x00, 0, High).ss(),
|
||||
# create_door(player, '', Open).dir(Direction.North, 0x00, None, High),
|
||||
# create_door(player, '', Nrml).dir(Direction.North, 0x00, Right, High).pos(),
|
||||
# create_door(player, '', Intr).dir(Direction.North, 0x00, Right, High).pos(),
|
||||
# create_door(player, '', Sprl).dir(Direction.Up, 0x00, 0, HTH).ss(Q, 0x0, 0x0),
|
||||
# create_door(player, '', Lgcl),
|
||||
# create_door(player, '', Hole),
|
||||
# create_door(player, '', Warp),
|
||||
@@ -579,6 +676,7 @@ def create_doors(world, player):
|
||||
world.get_door('Swamp Drain Right Switch', player).event('Swamp Drain')
|
||||
world.get_door('Swamp Flooded Room Ladder', player).event('Swamp Drain')
|
||||
|
||||
|
||||
# crystal switches and barriers
|
||||
world.get_door('Hera Lobby Down Stairs', player).c_switch()
|
||||
world.get_door('Hera Lobby Key Stairs', player).c_switch()
|
||||
@@ -597,8 +695,7 @@ def create_doors(world, player):
|
||||
|
||||
world.get_door('PoD Arena Main SW', player).c_switch()
|
||||
world.get_door('PoD Arena Bridge SE', player).c_switch()
|
||||
world.get_door('PoD Arena Main NW', player).barrier(CrystalBarrier.Orange)
|
||||
world.get_door('PoD Arena Main NE', player).barrier(CrystalBarrier.Orange)
|
||||
world.get_door('PoD Arena Main Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
||||
# maybe you can cross this way with blue up??
|
||||
world.get_door('PoD Arena Main Crystal Path', player).barrier(CrystalBarrier.Blue)
|
||||
world.get_door('PoD Arena Crystals E', player).barrier(CrystalBarrier.Blue)
|
||||
@@ -628,6 +725,26 @@ def create_doors(world, player):
|
||||
world.get_door('Thieves Hellway Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
||||
world.get_door('Thieves Hellway Crystal Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
||||
|
||||
world.get_door('Ice Bomb Drop SE', player).c_switch()
|
||||
world.get_door('Ice Conveyor SW', player).c_switch()
|
||||
world.get_door('Ice Refill WS', player).c_switch()
|
||||
world.get_door('Ice Bomb Drop Hole', player).barrier(CrystalBarrier.Orange) # not required to hit switch w/ bomb
|
||||
world.get_door('Ice Bomb Jump Ledge Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
||||
world.get_door('Ice Bomb Jump Catwalk Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
||||
world.get_door('Ice Crystal Right Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
||||
world.get_door('Ice Crystal Right Blue Hole', player).barrier(CrystalBarrier.Blue)
|
||||
world.get_door('Ice Crystal Left Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
||||
world.get_door('Ice Crystal Left Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||
world.get_door('Ice Backwards Room Hole', player).barrier(CrystalBarrier.Blue)
|
||||
|
||||
# nifty dynamic logical doors:
|
||||
south_controller = world.get_door('Ice Cross Bottom SE', player)
|
||||
east_controller = world.get_door('Ice Cross Right ES', player)
|
||||
controller_door(south_controller, world.get_door('Ice Cross Left Push Block', player))
|
||||
controller_door(south_controller, world.get_door('Ice Cross Right Push Block Bottom', player))
|
||||
controller_door(east_controller, world.get_door('Ice Cross Bottom Push Block Right', player))
|
||||
controller_door(east_controller, world.get_door('Ice Cross Top Push Block Right', player))
|
||||
|
||||
|
||||
def create_paired_doors(world, player):
|
||||
world.paired_doors[player] = [
|
||||
@@ -646,16 +763,16 @@ def create_paired_doors(world, player):
|
||||
PairedDoor('Swamp Pot Row WS', 'Swamp Trench 1 Approach ES'), # Swamp key door early room $38
|
||||
PairedDoor('PoD Middle Cage N', 'PoD Pit Room S'),
|
||||
# PairedDoor('', ''), # GT moldorm key door
|
||||
# PairedDoor('', ''), # Ice BJ key door
|
||||
PairedDoor('Ice Conveyor SW', 'Ice Bomb Jump NW'), # Ice BJ key door
|
||||
PairedDoor('Desert Tiles 2 SE', 'Desert Beamos Hall NE'),
|
||||
PairedDoor('Skull 3 Lobby NW', 'Skull Star Pits SW'), # Skull 3 key door
|
||||
PairedDoor('Skull 1 Lobby WS', 'Skull Pot Prison ES'), # Skull 1 key door - pot prison to big chest
|
||||
PairedDoor('Skull Map Room SE', 'Skull Pinball NE'), # Skull 1 - pinball key door
|
||||
# PairedDoor('', ''), # gt main big key door
|
||||
# PairedDoor('', ''), # ice door to spike chest
|
||||
PairedDoor('Ice Spike Cross ES', 'Ice Spike Room WS'), # ice door to spike chest
|
||||
# PairedDoor('', ''), # gt right side key door to cape bridge
|
||||
# PairedDoor('', ''), # gt bombable to rando room
|
||||
# PairedDoor('', ''), # ice's big icy room key door to lonely freezor
|
||||
PairedDoor('Ice Tall Hint SE', 'Ice Lonely Freezor NE'), # ice's big icy room key door to lonely freezor
|
||||
PairedDoor('Eastern Courtyard N', 'Eastern Darkness S'),
|
||||
# PairedDoor('', ''), # mire fishbone key door
|
||||
# PairedDoor('', ''), # mire big key door to bridges
|
||||
@@ -664,7 +781,7 @@ def create_paired_doors(world, player):
|
||||
PairedDoor('Eastern Dark Square Key Door WN', 'Eastern Cannonball Ledge Key Door EN'),
|
||||
PairedDoor('Thieves BK Corner NW', 'Thieves Pot Alcove Bottom SW'), # TT random bomb to pots
|
||||
PairedDoor('Thieves BK Corner NW', 'Thieves Hallway SE'), # TT big key door
|
||||
# PairedDoor('', ''), # Ice last key door to crystal switch
|
||||
PairedDoor('Ice Switch Room ES', 'Ice Refill WS'), # Ice last key door to crystal switch
|
||||
# PairedDoor('', ''), # mire hub key door to attic
|
||||
# PairedDoor('', ''), # mire hub key door to map
|
||||
# PairedDoor('', ''), # tr last key door to switch maze
|
||||
@@ -672,6 +789,11 @@ def create_paired_doors(world, player):
|
||||
]
|
||||
|
||||
|
||||
def controller_door(controller, dependent):
|
||||
dependent.controller = controller
|
||||
controller.dependents.append(dependent)
|
||||
|
||||
|
||||
def create_door(player, name, door_type):
|
||||
return Door(player, name, door_type)
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ class GraphPiece:
|
||||
|
||||
def __init__(self):
|
||||
self.hanger_info = None
|
||||
self.hanger_crystal = None
|
||||
self.hooks = {}
|
||||
self.visited_regions = set()
|
||||
|
||||
@@ -91,35 +92,22 @@ def gen_dungeon_info(available_sectors, entrance_regions, proposed_map, valid_do
|
||||
original_state = extend_reachable_state_improved(entrance_regions, ExplorationState(), proposed_map, valid_doors, world, player)
|
||||
dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map)
|
||||
doors_to_connect = set()
|
||||
blue_hooks = []
|
||||
hanger_set = set()
|
||||
o_state_cache = {}
|
||||
for sector in available_sectors:
|
||||
for door in sector.outstanding_doors:
|
||||
doors_to_connect.add(door)
|
||||
if not door.stonewall and door not in proposed_map.keys():
|
||||
hanger_set.add(door)
|
||||
parent = parent_region(door, world, player).parent_region
|
||||
o_state = extend_reachable_state_improved([parent], ExplorationState(), proposed_map, valid_doors, world, player)
|
||||
o_state_cache[door.name] = o_state
|
||||
piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map)
|
||||
dungeon[door.name] = piece
|
||||
for hook, crystal in piece.hooks.items():
|
||||
if crystal == CrystalBarrier.Blue or crystal == CrystalBarrier.Either:
|
||||
h_type = hook_from_door(hook)
|
||||
if h_type not in blue_hooks:
|
||||
blue_hooks.append(h_type) # todo: specific hooks and valid path to c_switch
|
||||
if len(blue_hooks) > 0:
|
||||
for sector in available_sectors:
|
||||
for door in sector.outstanding_doors:
|
||||
h_type = hanger_from_door(door)
|
||||
if not door.stonewall and door not in proposed_map.keys() and h_type in blue_hooks:
|
||||
parent = parent_region(door, world, player).parent_region
|
||||
blue_start = ExplorationState(CrystalBarrier.Blue)
|
||||
b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, valid_doors, world, player)
|
||||
o_state = o_state_cache[door.name]
|
||||
dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map)
|
||||
check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, valid_doors, world, player)
|
||||
|
||||
# catalog hooks: Dict<Hook, Set<>
|
||||
# and hangers:
|
||||
# catalog hooks: Dict<Hook, Set<Door, Crystal, Door>>
|
||||
# and hangers: Dict<Hang, Set<Door>>
|
||||
avail_hooks = defaultdict(set)
|
||||
hangers = defaultdict(set)
|
||||
for key, piece in dungeon.items():
|
||||
@@ -136,6 +124,46 @@ def gen_dungeon_info(available_sectors, entrance_regions, proposed_map, valid_do
|
||||
return dungeon, hangers, avail_hooks
|
||||
|
||||
|
||||
def check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, valid_doors, world, player):
|
||||
not_blue = set()
|
||||
not_blue.update(hanger_set)
|
||||
doors_to_check = set()
|
||||
doors_to_check.update(hanger_set) # doors to check, check everything on first pass
|
||||
blue_hooks = []
|
||||
blue_hangers = []
|
||||
new_blues = True
|
||||
while new_blues:
|
||||
new_blues = False
|
||||
for door in doors_to_check:
|
||||
piece = dungeon[door.name]
|
||||
for hook, crystal in piece.hooks.items():
|
||||
if crystal == CrystalBarrier.Blue or crystal == CrystalBarrier.Either:
|
||||
h_type = hook_from_door(hook)
|
||||
if h_type not in blue_hooks:
|
||||
new_blues = True
|
||||
blue_hooks.append(h_type)
|
||||
if piece.hanger_crystal == CrystalBarrier.Either:
|
||||
h_type = hanger_from_door(piece.hanger_info)
|
||||
if h_type not in blue_hangers:
|
||||
new_blues = True
|
||||
blue_hangers.append(h_type)
|
||||
doors_to_check = set()
|
||||
for door in not_blue: # am I now blue?
|
||||
hang_type = hanger_from_door(door) # am I hangable on a hook?
|
||||
hook_type = hook_from_door(door) # am I hookable onto a hanger?
|
||||
if (hang_type in blue_hooks and not door.stonewall) or hook_type in blue_hangers:
|
||||
explore_blue_state(door, dungeon, o_state_cache[door.name], proposed_map, valid_doors, world, player)
|
||||
doors_to_check.add(door)
|
||||
not_blue.difference_update(doors_to_check)
|
||||
|
||||
|
||||
def explore_blue_state(door, dungeon, o_state, proposed_map, valid_doors, world, player):
|
||||
parent = parent_region(door, world, player).parent_region
|
||||
blue_start = ExplorationState(CrystalBarrier.Blue)
|
||||
b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, valid_doors, world, player)
|
||||
dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map)
|
||||
|
||||
|
||||
def make_a_choice(dungeon, hangers, avail_hooks, prev_choices):
|
||||
# choose a hanger
|
||||
all_hooks = set()
|
||||
@@ -268,14 +296,21 @@ def create_graph_piece_from_state(door, o_state, b_state, proposed_map):
|
||||
if all_unattached[d] != exp_d.crystal:
|
||||
if all_unattached[d] == CrystalBarrier.Orange and exp_d.crystal == CrystalBarrier.Blue:
|
||||
all_unattached[d] = CrystalBarrier.Null
|
||||
else:
|
||||
elif all_unattached[d] == CrystalBarrier.Blue and exp_d.crystal == CrystalBarrier.Orange:
|
||||
# the swapping case
|
||||
logging.getLogger('').warning('Mismatched state @ %s (o:%s b:%s)', d.name, all_unattached[d], exp_d.crystal)
|
||||
elif all_unattached[d] == CrystalBarrier.Either:
|
||||
all_unattached[d] = exp_d.crystal # pessimism, and if not this, leave it alone
|
||||
else:
|
||||
all_unattached[exp_d.door] = exp_d.crystal
|
||||
h_crystal = door.crystal if door is not None else None
|
||||
for d, crystal in all_unattached.items():
|
||||
if (door is None or d != door) and not d.blocked and d not in proposed_map.keys():
|
||||
graph_piece.hooks[d] = crystal
|
||||
if d == door:
|
||||
h_crystal = crystal
|
||||
graph_piece.hanger_info = door
|
||||
graph_piece.hanger_crystal = h_crystal
|
||||
graph_piece.visited_regions.update(o_state.visited_blue)
|
||||
graph_piece.visited_regions.update(o_state.visited_orange)
|
||||
graph_piece.visited_regions.update(b_state.visited_blue)
|
||||
@@ -328,6 +363,15 @@ def connect_doors(a, b, world, player):
|
||||
connect_one_way(world, a.name, b.name, player)
|
||||
else:
|
||||
connect_two_way(world, a.name, b.name, player)
|
||||
dep_doors, target = [], None
|
||||
if len(a.dependents) > 0:
|
||||
dep_doors, target = a.dependents, b
|
||||
elif len(b.dependents) > 0:
|
||||
dep_doors, target = b.dependents, a
|
||||
if target is not None:
|
||||
target_region = world.get_entrance(target.name, player).parent_region
|
||||
for dep in dep_doors:
|
||||
connect_simple_door(dep, target_region, world, player)
|
||||
return
|
||||
# If we failed to account for a type, panic
|
||||
raise RuntimeError('Unknown door type ' + a.type.name)
|
||||
@@ -376,6 +420,11 @@ def connect_one_way(world, entrancename, exitname, player):
|
||||
y.dest = x
|
||||
|
||||
|
||||
def connect_simple_door(exit_door, region, world, player):
|
||||
world.get_entrance(exit_door.name, player).connect(region)
|
||||
exit_door.dest = region
|
||||
|
||||
|
||||
class ExplorationState(object):
|
||||
|
||||
def __init__(self, init_crystal=CrystalBarrier.Orange):
|
||||
@@ -518,9 +567,15 @@ class ExplorationState(object):
|
||||
def add_all_doors_check_proposed(self, region, proposed_map, valid_doors, world, player):
|
||||
for door in get_dungeon_doors(region, world, player):
|
||||
if self.can_traverse(door):
|
||||
if door.dest is None and not self.in_door_list_ic(door, self.unattached_doors)\
|
||||
and door not in proposed_map.keys() and door in valid_doors:
|
||||
if door.controller is not None:
|
||||
door = door.controller
|
||||
if door.dest is None and door not in proposed_map.keys() and door in valid_doors:
|
||||
if not self.in_door_list_ic(door, self.unattached_doors):
|
||||
self.append_door_to_list(door, self.unattached_doors)
|
||||
else:
|
||||
other = self.find_door_in_list(door, self.unattached_doors)
|
||||
if self.crystal != other.crystal:
|
||||
other.crystal = CrystalBarrier.Either
|
||||
elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door, self.event_doors):
|
||||
self.append_door_to_list(door, self.event_doors)
|
||||
elif not self.in_door_list(door, self.avail_doors):
|
||||
@@ -589,6 +644,13 @@ class ExplorationState(object):
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def find_door_in_list(door, door_list):
|
||||
for d in door_list:
|
||||
if d.door == door:
|
||||
return d
|
||||
return None
|
||||
|
||||
def append_door_to_list(self, door, door_list):
|
||||
if door.crystal == CrystalBarrier.Null:
|
||||
door_list.append(ExplorableDoor(door, self.crystal))
|
||||
|
||||
24
Dungeons.py
24
Dungeons.py
@@ -23,7 +23,7 @@ def create_dungeons(world, player):
|
||||
TT = make_dungeon('Thieves Town', 'Blind', thieves_regions, ItemFactory('Big Key (Thieves Town)', player), [ItemFactory('Small Key (Thieves Town)', player)], ItemFactory(['Map (Thieves Town)', 'Compass (Thieves Town)'], player))
|
||||
SW = make_dungeon('Skull Woods', 'Mothula', skull_regions, ItemFactory('Big Key (Skull Woods)', player), ItemFactory(['Small Key (Skull Woods)'] * 2, player), ItemFactory(['Map (Skull Woods)', 'Compass (Skull Woods)'], player))
|
||||
SP = make_dungeon('Swamp Palace', 'Arrghus', swamp_regions, ItemFactory('Big Key (Swamp Palace)', player), [ItemFactory('Small Key (Swamp Palace)', player)], ItemFactory(['Map (Swamp Palace)', 'Compass (Swamp Palace)'], player))
|
||||
IP = make_dungeon('Ice Palace', 'Kholdstare', ['Ice Palace (Entrance)', 'Ice Palace (Main)', 'Ice Palace (East)', 'Ice Palace (East Top)', 'Ice Palace (Kholdstare)'], ItemFactory('Big Key (Ice Palace)', player), ItemFactory(['Small Key (Ice Palace)'] * 2, player), ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player))
|
||||
IP = make_dungeon('Ice Palace', 'Kholdstare', ice_regions, ItemFactory('Big Key (Ice Palace)', player), ItemFactory(['Small Key (Ice Palace)'] * 2, player), ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player))
|
||||
MM = make_dungeon('Misery Mire', 'Vitreous', ['Misery Mire (Entrance)', 'Misery Mire (Main)', 'Misery Mire (West)', 'Misery Mire (Final Area)', 'Misery Mire (Vitreous)'], ItemFactory('Big Key (Misery Mire)', player), ItemFactory(['Small Key (Misery Mire)'] * 3, player), ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player))
|
||||
TR = make_dungeon('Turtle Rock', 'Trinexx', ['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)', 'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'], ItemFactory('Big Key (Turtle Rock)', player), ItemFactory(['Small Key (Turtle Rock)'] * 4, player), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player))
|
||||
|
||||
@@ -203,7 +203,7 @@ tower_regions = [
|
||||
]
|
||||
|
||||
pod_regions = [
|
||||
'PoD Lobby', 'PoD Left Cage', 'PoD Middle Cage', 'PoD Shooter Room', 'PoD Pit Room', 'PoD Arena Main',
|
||||
'PoD Lobby', 'PoD Left Cage', 'PoD Middle Cage', 'PoD Shooter Room', 'PoD Pit Room', 'PoD Arena Main', 'PoD Arena North',
|
||||
'PoD Arena Crystal', 'PoD Arena Bridge', 'PoD Arena Ledge', 'PoD Sexy Statue', 'PoD Map Balcony', 'PoD Conveyor',
|
||||
'PoD Mimics 1', 'PoD Jelly Hall', 'PoD Warp Hint', 'PoD Warp Room', 'PoD Stalfos Basement', 'PoD Basement Ledge',
|
||||
'PoD Big Key Landing', 'PoD Falling Bridge', 'PoD Dark Maze', 'PoD Big Chest Balcony', 'PoD Compass Room',
|
||||
@@ -241,6 +241,16 @@ thieves_regions = [
|
||||
'Thieves Conveyor Block', 'Thieves Big Chest Room', 'Thieves Trap'
|
||||
]
|
||||
|
||||
ice_regions = [
|
||||
'Ice Lobby', 'Ice Jelly Key', 'Ice Floor Switch', 'Ice Cross Left', 'Ice Cross Bottom', 'Ice Cross Right',
|
||||
'Ice Cross Top', 'Ice Compass Room', 'Ice Pengator Switch', 'Ice Dead End', 'Ice Big Key', 'Ice Bomb Drop',
|
||||
'Ice Stalfos Hint', 'Ice Conveyor', 'Ice Bomb Jump Ledge', 'Ice Bomb Jump Catwalk', 'Ice Narrow Corridor',
|
||||
'Ice Pengator Trap', 'Ice Spike Cross', 'Ice Firebar', 'Ice Falling Square', 'Ice Spike Room', 'Ice Hammer Block',
|
||||
'Ice Tongue Pull', 'Ice Freezors', 'Ice Freezors Ledge', 'Ice Tall Hint', 'Ice Hookshot Ledge',
|
||||
'Ice Hookshot Balcony', 'Ice Spikeball', 'Ice Lonely Freezor', 'Iced T', 'Ice Catwalk', 'Ice Many Pots',
|
||||
'Ice Crystal Right', 'Ice Crystal Left', 'Ice Crystal Block', 'Ice Big Chest View', 'Ice Big Chest Landing',
|
||||
'Ice Backwards Room', 'Ice Anti-Fairy', 'Ice Switch Room', 'Ice Refill', 'Ice Fairy', 'Ice Antechamber', 'Ice Boss'
|
||||
]
|
||||
|
||||
dungeon_regions = {
|
||||
'Hyrule Castle': hyrule_castle_regions,
|
||||
@@ -252,7 +262,7 @@ dungeon_regions = {
|
||||
'Swamp Palace': swamp_regions,
|
||||
'Skull Woods': skull_regions,
|
||||
'Thieves Town': thieves_regions,
|
||||
# 'Ice':
|
||||
'Ice Palace': ice_regions,
|
||||
# 'Mire':
|
||||
# 'TR':
|
||||
# 'GT':
|
||||
@@ -269,7 +279,7 @@ region_starts = {
|
||||
'Skull Woods': ['Skull 1 Lobby', 'Skull 2 East Lobby', 'Skull 2 West Lobby', 'Skull 3 Lobby', 'Skull Pot Circle',
|
||||
'Skull Pinball', 'Skull Left Drop', 'Skull Back Drop'],
|
||||
'Thieves Town': ['Thieves Lobby'],
|
||||
# ['Ice Lobby'],
|
||||
'Ice Palace': ['Ice Lobby'],
|
||||
# ['Mire Lobby'],
|
||||
# ['TR Main Lobby', 'TR Eye Trap', 'TR Big Chest', 'TR Laser Bridge'],
|
||||
# ['GT Lobby']
|
||||
@@ -296,7 +306,8 @@ dungeon_keys = {
|
||||
'Palace of Darkness': 'Small Key (Palace of Darkness)',
|
||||
'Swamp Palace': 'Small Key (Swamp Palace)',
|
||||
'Skull Woods': 'Small Key (Skull Woods)',
|
||||
'Thieves Town': 'Small Key (Thieves Town)'
|
||||
'Thieves Town': 'Small Key (Thieves Town)',
|
||||
'Ice Palace': 'Small Key (Ice Palace)',
|
||||
}
|
||||
|
||||
dungeon_bigs = {
|
||||
@@ -308,6 +319,7 @@ dungeon_bigs = {
|
||||
'Palace of Darkness': 'Big Key (Palace of Darkness)',
|
||||
'Swamp Palace': 'Big Key (Swamp Palace)',
|
||||
'Skull Woods': 'Big Key (Skull Woods)',
|
||||
'Thieves Town': 'Big Key (Thieves Town)'
|
||||
'Thieves Town': 'Big Key (Thieves Town)',
|
||||
'Ice Palace': 'Big Key (Ice Palace)',
|
||||
}
|
||||
|
||||
|
||||
@@ -2947,10 +2947,6 @@ mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central
|
||||
('Cave 45 Mirror Spot', 'Cave 45 Ledge'),
|
||||
('Graveyard Ledge Mirror Spot', 'Graveyard Ledge'),
|
||||
|
||||
('Ice Palace Entrance Room', 'Ice Palace (Main)'),
|
||||
('Ice Palace (East)', 'Ice Palace (East)'),
|
||||
('Ice Palace (East Top)', 'Ice Palace (East Top)'),
|
||||
('Ice Palace (Kholdstare)', 'Ice Palace (Kholdstare)'),
|
||||
('Misery Mire Entrance Gap', 'Misery Mire (Main)'),
|
||||
('Misery Mire (West)', 'Misery Mire (West)'),
|
||||
('Misery Mire Big Key Door', 'Misery Mire (Final Area)'),
|
||||
@@ -3069,10 +3065,6 @@ inverted_mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia
|
||||
('Skull Woods First Section (Top) One-Way Path', 'Skull Woods First Section'),
|
||||
('Skull Woods Second Section (Drop)', 'Skull Woods Second Section'),
|
||||
('Blind Fight', 'Blind Fight'),
|
||||
('Ice Palace Entrance Room', 'Ice Palace (Main)'),
|
||||
('Ice Palace (East)', 'Ice Palace (East)'),
|
||||
('Ice Palace (East Top)', 'Ice Palace (East Top)'),
|
||||
('Ice Palace (Kholdstare)', 'Ice Palace (Kholdstare)'),
|
||||
('Misery Mire Entrance Gap', 'Misery Mire (Main)'),
|
||||
('Misery Mire (West)', 'Misery Mire (West)'),
|
||||
('Misery Mire Big Key Door', 'Misery Mire (Final Area)'),
|
||||
@@ -3520,7 +3512,7 @@ default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Main L
|
||||
('Skull Woods Second Section Exit (West)', 'Skull Woods Forest (West)'),
|
||||
('Skull Woods Final Section', 'Skull 3 Lobby'),
|
||||
('Skull Woods Final Section Exit', 'Skull Woods Forest (West)'),
|
||||
('Ice Palace', 'Ice Palace (Entrance)'),
|
||||
('Ice Palace', 'Ice Lobby'),
|
||||
('Ice Palace Exit', 'Dark Lake Hylia Central Island'),
|
||||
('Misery Mire', 'Misery Mire (Entrance)'),
|
||||
('Misery Mire Exit', 'Dark Desert'),
|
||||
@@ -3574,7 +3566,7 @@ inverted_default_dungeon_connections = [('Desert Palace Entrance (South)', 'Dese
|
||||
('Skull Woods Second Section Exit (West)', 'Skull Woods Forest (West)'),
|
||||
('Skull Woods Final Section', 'Skull Woods Final Section (Entrance)'),
|
||||
('Skull Woods Final Section Exit', 'Skull Woods Forest (West)'),
|
||||
('Ice Palace', 'Ice Palace (Entrance)'),
|
||||
('Ice Palace', 'Ice Lobby'),
|
||||
('Misery Mire', 'Misery Mire (Entrance)'),
|
||||
('Misery Mire Exit', 'Dark Desert'),
|
||||
('Palace of Darkness', 'PoD Lobby'),
|
||||
|
||||
@@ -192,6 +192,9 @@ def generate_itempool(world, player):
|
||||
world.push_item(world.get_location('Revealing Light', player), ItemFactory('Maiden Unmasked', player), False)
|
||||
world.get_location('Revealing Light', player).event = True
|
||||
world.get_location('Revealing Light', player).locked = True
|
||||
world.push_item(world.get_location('Ice Block Drop', player), ItemFactory('Convenient Block', player), False)
|
||||
world.get_location('Ice Block Drop', player).event = True
|
||||
world.get_location('Ice Block Drop', player).locked = True
|
||||
|
||||
# set up item pool
|
||||
if world.custom:
|
||||
|
||||
3
Items.py
3
Items.py
@@ -178,4 +178,5 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
|
||||
'Shining Light': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
||||
'Maiden Rescued': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
||||
'Maiden Unmasked': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
||||
}
|
||||
'Convenient Block': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
||||
}
|
||||
|
||||
2
Main.py
2
Main.py
@@ -92,7 +92,7 @@ def main(args, seed=None):
|
||||
for player in range(1, world.players + 1):
|
||||
all_state = world.get_all_state(keys=True)
|
||||
for bossregion in ['Eastern Boss', 'Desert Boss', 'Hera Boss', 'Tower Agahnim 1', 'PoD Boss', 'Swamp Boss',
|
||||
'Skull Boss', 'Thieves Boss']:
|
||||
'Skull Boss', 'Thieves Boss', 'Ice Boss']:
|
||||
if world.get_region(bossregion, player) not in all_state.reachable_regions[player]:
|
||||
raise Exception(bossregion + ' missing from generation')
|
||||
|
||||
|
||||
66
Regions.py
66
Regions.py
@@ -193,12 +193,6 @@ def create_regions(world, player):
|
||||
create_lw_region(player, 'Mimic Cave Ledge', None, ['Mimic Cave']),
|
||||
create_cave_region(player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']),
|
||||
|
||||
create_dungeon_region(player, 'Ice Palace (Entrance)', 'Ice Palace', None, ['Ice Palace Entrance Room', 'Ice Palace Exit']),
|
||||
create_dungeon_region(player, 'Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Compass Chest', 'Ice Palace - Freezor Chest',
|
||||
'Ice Palace - Big Chest', 'Ice Palace - Iced T Room'], ['Ice Palace (East)', 'Ice Palace (Kholdstare)']),
|
||||
create_dungeon_region(player, 'Ice Palace (East)', 'Ice Palace', ['Ice Palace - Spike Room'], ['Ice Palace (East Top)']),
|
||||
create_dungeon_region(player, 'Ice Palace (East Top)', 'Ice Palace', ['Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']),
|
||||
create_dungeon_region(player, 'Ice Palace (Kholdstare)', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize']),
|
||||
create_dungeon_region(player, 'Misery Mire (Entrance)', 'Misery Mire', None, ['Misery Mire Entrance Gap', 'Misery Mire Exit']),
|
||||
create_dungeon_region(player, 'Misery Mire (Main)', 'Misery Mire', ['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby',
|
||||
'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest'], ['Misery Mire (West)', 'Misery Mire Big Key Door']),
|
||||
@@ -365,7 +359,8 @@ def create_regions(world, player):
|
||||
create_dungeon_region(player, 'PoD Middle Cage', 'Palace of Darkness', None, ['PoD Middle Cage S', 'PoD Middle Cage SE', 'PoD Middle Cage N', 'PoD Middle Cage Down Stairs']),
|
||||
create_dungeon_region(player, 'PoD Shooter Room', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['PoD Shooter Room Up Stairs']),
|
||||
create_dungeon_region(player, 'PoD Pit Room', 'Palace of Darkness', None, ['PoD Pit Room S', 'PoD Pit Room NW', 'PoD Pit Room NE', 'PoD Pit Room Freefall', 'PoD Pit Room Bomb Hole']),
|
||||
create_dungeon_region(player, 'PoD Arena Main', 'Palace of Darkness', None, ['PoD Arena Main SW', 'PoD Arena Main NW', 'PoD Arena Main NE', 'PoD Arena Main Crystal Path']),
|
||||
create_dungeon_region(player, 'PoD Arena Main', 'Palace of Darkness', None, ['PoD Arena Main SW', 'PoD Arena Main Crystal Path', 'PoD Arena Main Orange Barrier']),
|
||||
create_dungeon_region(player, 'PoD Arena North', 'Palace of Darkness', None, ['PoD Arena Main NW', 'PoD Arena Main NE', 'PoD Arena North Drop Down']),
|
||||
create_dungeon_region(player, 'PoD Arena Crystal', 'Palace of Darkness', None, ['PoD Arena Crystals E', 'PoD Arena Crystal Path']),
|
||||
create_dungeon_region(player, 'PoD Arena Bridge', 'Palace of Darkness', ['Palace of Darkness - The Arena - Bridge'], ['PoD Arena Bridge SE', 'PoD Arena Bridge Drop Down']),
|
||||
create_dungeon_region(player, 'PoD Arena Ledge', 'Palace of Darkness', ['Palace of Darkness - The Arena - Ledge'], ['PoD Arena Ledge ES']),
|
||||
@@ -503,6 +498,53 @@ def create_regions(world, player):
|
||||
create_dungeon_region(player, 'Thieves Trap', 'Thieves\' Town', None, ['Thieves Trap EN']),
|
||||
|
||||
# ice
|
||||
create_dungeon_region(player, 'Ice Lobby', 'Ice Palace', None, ['Ice Palace Exit', 'Ice Lobby WS']),
|
||||
create_dungeon_region(player, 'Ice Jelly Key', 'Ice Palace', ['Ice Palace - Jelly Key Drop'], ['Ice Jelly Key ES', 'Ice Jelly Key Down Stairs']),
|
||||
create_dungeon_region(player, 'Ice Floor Switch', 'Ice Palace', None, ['Ice Floor Switch Up Stairs', 'Ice Floor Switch ES']),
|
||||
create_dungeon_region(player, 'Ice Cross Left', 'Ice Palace', None, ['Ice Cross Left WS', 'Ice Cross Left Push Block']),
|
||||
create_dungeon_region(player, 'Ice Cross Bottom', 'Ice Palace', None, ['Ice Cross Bottom SE', 'Ice Cross Bottom Push Block Left', 'Ice Cross Bottom Push Block Right']),
|
||||
create_dungeon_region(player, 'Ice Cross Right', 'Ice Palace', None, ['Ice Cross Right ES', 'Ice Cross Right Push Block Top', 'Ice Cross Right Push Block Bottom']),
|
||||
create_dungeon_region(player, 'Ice Cross Top', 'Ice Palace', None, ['Ice Cross Top NE', 'Ice Cross Top Push Block Left', 'Ice Cross Top Push Block Right']),
|
||||
create_dungeon_region(player, 'Ice Compass Room', 'Ice Palace', ['Ice Palace - Compass Chest'], ['Ice Compass Room NE']),
|
||||
create_dungeon_region(player, 'Ice Pengator Switch', 'Ice Palace', None, ['Ice Pengator Switch WS', 'Ice Pengator Switch ES']),
|
||||
create_dungeon_region(player, 'Ice Dead End', 'Ice Palace', None, ['Ice Dead End WS']),
|
||||
create_dungeon_region(player, 'Ice Big Key', 'Ice Palace', ['Ice Palace - Big Key Chest'], ['Ice Big Key Push Block', 'Ice Big Key Down Ladder']),
|
||||
create_dungeon_region(player, 'Ice Bomb Drop', 'Ice Palace', None, ['Ice Bomb Drop SE', 'Ice Bomb Drop Hole']),
|
||||
create_dungeon_region(player, 'Ice Stalfos Hint', 'Ice Palace', None, ['Ice Stalfos Hint SE']),
|
||||
create_dungeon_region(player, 'Ice Conveyor', 'Ice Palace', ['Ice Palace - Conveyor Key Drop'], ['Ice Conveyor NE', 'Ice Conveyor SW']),
|
||||
create_dungeon_region(player, 'Ice Bomb Jump Ledge', 'Ice Palace', None, ['Ice Bomb Jump NW', 'Ice Bomb Jump Ledge Orange Barrier']),
|
||||
create_dungeon_region(player, 'Ice Bomb Jump Catwalk', 'Ice Palace', None, ['Ice Bomb Jump Catwalk Orange Barrier', 'Ice Bomb Jump EN']),
|
||||
create_dungeon_region(player, 'Ice Narrow Corridor', 'Ice Palace', None, ['Ice Narrow Corridor WN', 'Ice Narrow Corridor Down Stairs']),
|
||||
create_dungeon_region(player, 'Ice Pengator Trap', 'Ice Palace', None, ['Ice Pengator Trap Up Stairs', 'Ice Pengator Trap NE']),
|
||||
create_dungeon_region(player, 'Ice Spike Cross', 'Ice Palace', None, ['Ice Spike Cross SE', 'Ice Spike Cross WS', 'Ice Spike Cross ES', 'Ice Spike Cross NE']),
|
||||
create_dungeon_region(player, 'Ice Firebar', 'Ice Palace', None, ['Ice Firebar ES', 'Ice Firebar Down Ladder']),
|
||||
create_dungeon_region(player, 'Ice Falling Square', 'Ice Palace', None, ['Ice Falling Square SE', 'Ice Falling Square Hole']),
|
||||
create_dungeon_region(player, 'Ice Spike Room', 'Ice Palace', ['Ice Palace - Spike Room'], ['Ice Spike Room WS', 'Ice Spike Room Up Stairs', 'Ice Spike Room Down Stairs']),
|
||||
create_dungeon_region(player, 'Ice Hammer Block', 'Ice Palace', ['Ice Palace - Hammer Block Key Drop', 'Ice Palace - Map Chest'], ['Ice Hammer Block Down Stairs', 'Ice Hammer Block ES']),
|
||||
create_dungeon_region(player, 'Ice Tongue Pull', 'Ice Palace', None, ['Ice Tongue Pull Up Ladder', 'Ice Tongue Pull WS']),
|
||||
create_dungeon_region(player, 'Ice Freezors', 'Ice Palace', ['Ice Palace - Freezor Chest'], ['Ice Freezors Up Ladder', 'Ice Freezors Hole', 'Ice Freezors Bomb Hole']),
|
||||
create_dungeon_region(player, 'Ice Freezors Ledge', 'Ice Palace', None, ['Ice Freezors Ledge ES', 'Ice Freezors Ledge Hole']),
|
||||
create_dungeon_region(player, 'Ice Tall Hint', 'Ice Palace', None, ['Ice Tall Hint WS', 'Ice Tall Hint SE', 'Ice Tall Hint EN']),
|
||||
create_dungeon_region(player, 'Ice Hookshot Ledge', 'Ice Palace', None, ['Ice Hookshot Ledge WN', 'Ice Hookshot Ledge Path']),
|
||||
create_dungeon_region(player, 'Ice Hookshot Balcony', 'Ice Palace', None, ['Ice Hookshot Balcony SW', 'Ice Hookshot Balcony Path']),
|
||||
create_dungeon_region(player, 'Ice Spikeball', 'Ice Palace', None, ['Ice Spikeball NW', 'Ice Spikeball Up Stairs']),
|
||||
create_dungeon_region(player, 'Ice Lonely Freezor', 'Ice Palace', None, ['Ice Lonely Freezor NE', 'Ice Lonely Freezor Down Stairs']),
|
||||
create_dungeon_region(player, 'Iced T', 'Ice Palace', ['Ice Palace - Iced T Room'], ['Iced T Up Stairs', 'Iced T EN']),
|
||||
create_dungeon_region(player, 'Ice Catwalk', 'Ice Palace', None, ['Ice Catwalk WN', 'Ice Catwalk NW']),
|
||||
create_dungeon_region(player, 'Ice Many Pots', 'Ice Palace', ['Ice Palace - Many Pots Pot Key'], ['Ice Many Pots SW', 'Ice Many Pots WS']),
|
||||
create_dungeon_region(player, 'Ice Crystal Right', 'Ice Palace', None, ['Ice Crystal Right ES', 'Ice Crystal Right NE', 'Ice Crystal Right Orange Barrier', 'Ice Crystal Right Blue Hole']),
|
||||
create_dungeon_region(player, 'Ice Crystal Left', 'Ice Palace', None, ['Ice Crystal Left WS', 'Ice Crystal Left Orange Barrier', 'Ice Crystal Left Blue Barrier']),
|
||||
create_dungeon_region(player, 'Ice Crystal Block', 'Ice Palace', ['Ice Block Drop'], ['Ice Crystal Block Hole', 'Ice Crystal Block Exit']),
|
||||
create_dungeon_region(player, 'Ice Big Chest View', 'Ice Palace', None, ['Ice Big Chest View ES']),
|
||||
create_dungeon_region(player, 'Ice Big Chest Landing', 'Ice Palace', ['Ice Palace - Big Chest'], ['Ice Big Chest Landing Push Blocks']),
|
||||
create_dungeon_region(player, 'Ice Backwards Room', 'Ice Palace', None, ['Ice Backwards Room SE', 'Ice Backwards Room Down Stairs', 'Ice Backwards Room Hole']),
|
||||
create_dungeon_region(player, 'Ice Anti-Fairy', 'Ice Palace', None, ['Ice Anti-Fairy Up Stairs', 'Ice Anti-Fairy SE']),
|
||||
create_dungeon_region(player, 'Ice Switch Room', 'Ice Palace', None, ['Ice Switch Room NE', 'Ice Switch Room ES', 'Ice Switch Room SE']),
|
||||
create_dungeon_region(player, 'Ice Refill', 'Ice Palace', None, ['Ice Refill WS']),
|
||||
create_dungeon_region(player, 'Ice Fairy', 'Ice Palace', None, ['Ice Fairy Warp']),
|
||||
create_dungeon_region(player, 'Ice Antechamber', 'Ice Palace', None, ['Ice Antechamber NE', 'Ice Antechamber Hole']),
|
||||
create_dungeon_region(player, 'Ice Boss', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize']),
|
||||
|
||||
# mire
|
||||
# tr
|
||||
# gt
|
||||
@@ -635,7 +677,11 @@ key_only_locations = {
|
||||
'Skull Woods - West Lobby Pot Key': 'Small Key (Skull Woods)',
|
||||
'Skull Woods - Spike Corner Key Drop': 'Small Key (Skull Woods)',
|
||||
'Thieves\' Town - Hallway Pot Key': 'Small Key (Thieves Town)',
|
||||
'Thieves\' Town - Spike Switch Pot Key': 'Small Key (Thieves Town)'
|
||||
'Thieves\' Town - Spike Switch Pot Key': 'Small Key (Thieves Town)',
|
||||
'Ice Palace - Jelly Key Drop': 'Small Key (Ice Palace)',
|
||||
'Ice Palace - Conveyor Key Drop': 'Small Key (Ice Palace)',
|
||||
'Ice Palace - Hammer Block Key Drop': 'Small Key (Ice Palace)',
|
||||
'Ice Palace - Many Pots Pot Key': 'Small Key (Ice Palace)',
|
||||
}
|
||||
|
||||
dungeon_events = [
|
||||
@@ -644,7 +690,8 @@ dungeon_events = [
|
||||
'Swamp Drain',
|
||||
'Attic Cracked Floor',
|
||||
'Suspicious Maiden',
|
||||
'Revealing Light'
|
||||
'Revealing Light',
|
||||
'Ice Block Drop'
|
||||
]
|
||||
|
||||
flooded_keys_reverse = {
|
||||
@@ -883,6 +930,7 @@ location_table = {'Mushroom': (0x180013, False, 'in the woods'),
|
||||
'Attic Cracked Floor': (None, False, None),
|
||||
'Suspicious Maiden': (None, False, None),
|
||||
'Revealing Light': (None, False, None),
|
||||
'Ice Block Drop': (None, False, None),
|
||||
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], True, 'Eastern Palace'),
|
||||
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], True, 'Desert Palace'),
|
||||
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], True, 'Tower of Hera'),
|
||||
|
||||
2
Rom.py
2
Rom.py
@@ -18,7 +18,7 @@ from EntranceShuffle import door_addresses
|
||||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = '78b74698bf286f0cc6d9c2563dd7d59e'
|
||||
RANDOMIZERBASEHASH = '3aee2a56ccbb16e5a26be7cd57202e41'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
|
||||
126
Rules.py
126
Rules.py
@@ -259,7 +259,6 @@ 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
|
||||
add_key_logic_rules(world, player) # todo - vanilla shuffle rules
|
||||
|
||||
# Eastern Palace
|
||||
# Eyegore room needs a bow
|
||||
@@ -367,18 +366,30 @@ def global_rules(world, player):
|
||||
set_rule(world.get_location('Thieves\' Town - Boss', player), lambda state: state.has('Maiden Unmasked', player) and world.get_location('Thieves\' Town - Boss', 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_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player))
|
||||
if world.accessibility == 'locations':
|
||||
forbid_item(world.get_location('Ice Palace - Big Chest', player), 'Big Key (Ice Palace)', 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))
|
||||
set_rule(world.get_entrance('Ice Antechamber Hole', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
|
||||
# todo: ohko rules for spike room - could split into two regions instead of these, but can_take_damage is usually true
|
||||
set_rule(world.get_entrance('Ice Spike Room WS', player), lambda state: state.world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))
|
||||
set_rule(world.get_entrance('Ice Spike Room Up Stairs', player), lambda state: state.world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))
|
||||
set_rule(world.get_entrance('Ice Spike Room Down Stairs', player), lambda state: state.world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))
|
||||
set_rule(world.get_location('Ice Palace - Spike Room', player), lambda state: state.world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))
|
||||
set_rule(world.get_entrance('Ice Hookshot Ledge Path', player), lambda state: state.has('Hookshot', player))
|
||||
set_rule(world.get_entrance('Ice Hookshot Balcony Path', player), lambda state: state.has('Hookshot', 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))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Boss', player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Prize', player))
|
||||
|
||||
add_key_logic_rules(world, player) # todo - vanilla shuffle rules
|
||||
# End of door rando rules.
|
||||
|
||||
add_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player))
|
||||
|
||||
set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.can_melt_things(player))
|
||||
set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player))
|
||||
set_rule(world.get_entrance('Ice Palace (Kholdstare)', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state.has_key('Small Key (Ice Palace)', player, 2) or (state.has('Cane of Somaria', player) and state.has_key('Small Key (Ice Palace)', player, 1))))
|
||||
# TODO: investigate change from VT. Changed to hookshot or 2 keys (no checking for big key in specific chests)
|
||||
set_rule(world.get_entrance('Ice Palace (East)', player), lambda state: (state.has('Hookshot', player) or (item_in_locations(state, 'Big Key (Ice Palace)', player, [('Ice Palace - Spike Room', player), ('Ice Palace - Big Key Chest', player), ('Ice Palace - Map Chest', player)]) and state.has_key('Small Key (Ice Palace)', player))) and (state.world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player)))
|
||||
set_rule(world.get_entrance('Ice Palace (East Top)', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', 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))
|
||||
for location in ['Ice Palace - Big Chest', 'Ice Palace - Boss']:
|
||||
forbid_item(world.get_location(location, player), 'Big Key (Ice Palace)', player)
|
||||
|
||||
@@ -1693,102 +1704,5 @@ def add_key_logic_rules(world, player):
|
||||
forbid_item(location, d_logic.bk_name, player)
|
||||
|
||||
|
||||
def generate_key_logic(dungeon_name, small_key_name, world, player):
|
||||
sector, start_region_names = world.dungeon_layouts[player][dungeon_name]
|
||||
logger = logging.getLogger('')
|
||||
# Now that the dungeon layout is done, we need to search again to generate key logic.
|
||||
# TODO: This assumes all start doors are accessible, which isn't always true.
|
||||
state = ExplorationState()
|
||||
# available_doors = [] # Doors to explore
|
||||
# visited_regions = set() # Regions we've been to and don't need to expand
|
||||
current_kr = 0 # Key regions are numbered, starting at 0
|
||||
# door_krs = {} # Map of key door name to KR it lives in
|
||||
kr_parents = {} # Key region to parent map
|
||||
# kr_location_counts = defaultdict(int) # Number of locations in each key region
|
||||
# Everything in a start region is in key region 0.
|
||||
for name in start_region_names:
|
||||
region = world.get_region(name, player)
|
||||
state.visit_region(region, current_kr)
|
||||
state.add_all_doors_check_key_region(region, current_kr, world, player)
|
||||
# Search into the dungeon
|
||||
# logger.debug('Begin key region search. %s', small_key_name)
|
||||
while len(state.avail_doors) > 0:
|
||||
# Open as many non-key doors as possible before opening a key door.
|
||||
# This guarantees that we're only exploring one key region at a time.
|
||||
state.avail_doors.sort(key=state.key_door_sort)
|
||||
explorable_door = state.next_avail_door()
|
||||
door = explorable_door.door
|
||||
local_kr = state.door_krs[door.name]
|
||||
# logger.debug(' kr %s: Door %s', local_kr, door.name)
|
||||
connect_region = world.get_entrance(door.name, player).connected_region
|
||||
# Bail early if we've been here before or the door is blocked
|
||||
if not state.validate(door, connect_region, world):
|
||||
continue
|
||||
# Once we open a key door, we need a new region.
|
||||
if door.smallKey and door not in state.opened_doors: # we tend to open doors in a DFS manner
|
||||
current_kr += 1
|
||||
kr_parents[current_kr] = local_kr
|
||||
local_kr = current_kr
|
||||
state.opened_doors.append(door)
|
||||
if door.dest.smallKey:
|
||||
state.opened_doors.append(door.dest)
|
||||
# logger.debug('%s: New KR %s', door.name, current_kr)
|
||||
# Account for the new region
|
||||
state.visit_region(connect_region, local_kr)
|
||||
state.add_all_doors_check_key_region(connect_region, local_kr, world, player)
|
||||
# kr_location_counts[local_kr] += len(exit.locations)
|
||||
# Now that we have doors divided up into key regions, we can analyze the map
|
||||
# Invert the door -> kr map into one that lists doors by region.
|
||||
kr_doors = defaultdict(list)
|
||||
region_krs = {}
|
||||
for door_name in state.door_krs:
|
||||
kr = state.door_krs[door_name]
|
||||
entrance = world.get_entrance(door_name, player)
|
||||
door = world.check_for_door(door_name, player)
|
||||
ent_name = entrance.parent_region.name
|
||||
if ent_name in region_krs.keys():
|
||||
region_krs[entrance.parent_region.name] = min(region_krs[entrance.parent_region.name], kr)
|
||||
else:
|
||||
region_krs[entrance.parent_region.name] = kr
|
||||
if door.smallKey and not door.blocked:
|
||||
kr_doors[kr].append(entrance)
|
||||
kr_keys = defaultdict(int) # Number of keys each region needs
|
||||
for kr in range(0, current_kr + 1):
|
||||
logic_doors = []
|
||||
keys = 0
|
||||
for door in kr_doors[kr]:
|
||||
dest_kr = region_krs[door.connected_region.name]
|
||||
if dest_kr > kr: # may be the case if dest_kr != parent_kr of kr
|
||||
# This door heads deeper into the dungeon. It needs a full key, and logic
|
||||
keys += 1
|
||||
logic_doors.append(door)
|
||||
elif dest_kr == kr:
|
||||
# This door doesn't get us any deeper, but it's possible to waste a key.
|
||||
# If we're going to see its sibling in this search, add half a key
|
||||
actual_door = world.get_door(door.name, player)
|
||||
if actual_door.type == DoorType.SpiralStairs:
|
||||
keys += 1
|
||||
else:
|
||||
keys += 0.5
|
||||
logic_doors.append(door) # this may still need logic
|
||||
# Add key count from parent region
|
||||
if kr in kr_parents:
|
||||
keys += kr_keys[kr_parents[kr]]
|
||||
kr_keys[kr] = keys
|
||||
# Generate logic
|
||||
for door in logic_doors:
|
||||
logger.debug(' %s in kr %s needs %s keys', door.name, kr, keys)
|
||||
add_rule(world.get_entrance(door.name, player), create_key_rule(small_key_name, player, keys))
|
||||
|
||||
|
||||
def create_key_rule(small_key_name, player, keys):
|
||||
return lambda state: state.has_key(small_key_name, player, keys)
|
||||
|
||||
|
||||
def get_doors(world, region, player):
|
||||
res = []
|
||||
for exit in region.exits:
|
||||
door = world.check_for_door(exit.name, player)
|
||||
if door is not None:
|
||||
res.append(door)
|
||||
return res
|
||||
|
||||
@@ -7,9 +7,8 @@ RecordStairType: {
|
||||
|
||||
SpiralWarp: {
|
||||
lda $040c : cmp.b #$ff : beq .abort ; abort if not in dungeon
|
||||
cmp #$14 : beq .check ; hera is okay
|
||||
cmp #$16 : beq .check ; thieves is okay
|
||||
cmp #$0e : bcs .abort ; abort if not supported yet -- todo: this needs to be altered/removed as more dungeons are implemented
|
||||
cmp #$0e : beq .abort ; mire is not okay
|
||||
cmp #$18 : bcs .abort ; abort if not supported yet -- todo: this needs to be altered/removed as more dungeons are implemented
|
||||
.check
|
||||
lda $045e : cmp #$5e : beq .gtg ; abort if not spiral - intended room is in A!
|
||||
cmp #$5f : beq .gtg
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user