Merge remote-tracking branch 'origin/DoorDev' into DoorDev

This commit is contained in:
aerinon
2020-01-06 21:30:25 -07:00
10 changed files with 208 additions and 56 deletions

View File

@@ -2,7 +2,9 @@ import copy
from enum import Enum, unique from enum import Enum, unique
import logging import logging
import json import json
from collections import OrderedDict from collections import OrderedDict, deque
from EntranceShuffle import door_addresses
from _vendor.collections_extended import bag from _vendor.collections_extended import bag
from Utils import int16_as_bytes from Utils import int16_as_bytes
from Tables import normal_offset_table, spiral_offset_table from Tables import normal_offset_table, spiral_offset_table
@@ -355,6 +357,7 @@ class CollectionState(object):
self.prog_items = bag() self.prog_items = bag()
self.world = parent self.world = parent
self.reachable_regions = {player: set() for player in range(1, parent.players + 1)} self.reachable_regions = {player: set() for player in range(1, parent.players + 1)}
self.colored_regions = {player: {} for player in range(1, parent.players + 1)}
self.events = [] self.events = []
self.path = {} self.path = {}
self.locations_checked = set() self.locations_checked = set()
@@ -366,6 +369,7 @@ class CollectionState(object):
player_regions = [region for region in self.world.regions if region.player == player] player_regions = [region for region in self.world.regions if region.player == player]
self.stale[player] = False self.stale[player] = False
rrp = self.reachable_regions[player] rrp = self.reachable_regions[player]
ccr = self.colored_regions[player]
new_regions = True new_regions = True
reachable_regions_count = len(rrp) reachable_regions_count = len(rrp)
while new_regions: while new_regions:
@@ -373,13 +377,53 @@ class CollectionState(object):
for candidate in possible: for candidate in possible:
if candidate.can_reach_private(self): if candidate.can_reach_private(self):
rrp.add(candidate) rrp.add(candidate)
if candidate.type == RegionType.Dungeon:
c_switch_present = False
for ext in candidate.exits:
door = self.world.check_for_door(ext.name, player)
if door is not None and door.crystal == CrystalBarrier.Either:
c_switch_present = True
if c_switch_present:
ccr[candidate] = CrystalBarrier.Either
self.spread_crystal_access(candidate, CrystalBarrier.Either, rrp, ccr, player)
else:
for entrance in candidate.entrances:
door = self.world.check_for_door(entrance.name, player)
if door is None or entrance.parent_region.type != RegionType.Dungeon:
ccr[candidate] = CrystalBarrier.Orange
if entrance.parent_region in ccr.keys():
color_type = ccr[entrance.parent_region]
current_type = ccr[candidate] if candidate in ccr.keys() else None
ccr[candidate] = color_type if current_type is None or color_type == current_type else CrystalBarrier.Either
new_regions = len(rrp) > reachable_regions_count new_regions = len(rrp) > reachable_regions_count
reachable_regions_count = len(rrp) reachable_regions_count = len(rrp)
def spread_crystal_access(self, region, crystal, rrp, ccr, player):
queue = deque([(region, crystal)])
visited = set()
while len(queue) > 0:
region, crystal = queue.popleft()
visited.add(region)
for ext in region.exits:
connect = ext.connected_region
if connect not in visited and connect is not None and connect.type == RegionType.Dungeon:
if connect in rrp and ext.can_reach(self) and connect:
door = self.world.check_for_door(ext.name, player)
current_crystal = ccr[connect]
if door is not None and not door.blocked and current_crystal != crystal and current_crystal != CrystalBarrier.Either:
if door.crystal in [CrystalBarrier.Either, CrystalBarrier.Null]:
ccr[connect] = crystal
queue.append((connect, crystal))
else:
queue.append((connect, door.crystal))
if door.crystal != current_crystal:
ccr[connect] = CrystalBarrier.Either
def copy(self): def copy(self):
ret = CollectionState(self.world) ret = CollectionState(self.world)
ret.prog_items = self.prog_items.copy() ret.prog_items = self.prog_items.copy()
ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in range(1, self.world.players + 1)} ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in range(1, self.world.players + 1)}
ret.colored_regions = {player: copy.copy(self.colored_regions[player]) for player in range(1, self.world.players + 1)}
ret.events = copy.copy(self.events) ret.events = copy.copy(self.events)
ret.path = copy.copy(self.path) ret.path = copy.copy(self.path)
ret.locations_checked = copy.copy(self.locations_checked) ret.locations_checked = copy.copy(self.locations_checked)
@@ -400,6 +444,15 @@ class CollectionState(object):
return spot.can_reach(self) return spot.can_reach(self)
def sweep_for_crystal_access(self):
for player, rrp in self.reachable_regions.items():
dungeon_regions = [x for x in rrp if x.type == RegionType.Dungeon]
ccr = self.colored_regions[player]
for region in dungeon_regions:
if region in ccr.keys():
self.spread_crystal_access(region, ccr[region], rrp, ccr, player)
self.stale[player] = True
def sweep_for_events(self, key_only=False, locations=None): def sweep_for_events(self, key_only=False, locations=None):
# this may need improvement # this may need improvement
new_locations = True new_locations = True
@@ -415,6 +468,18 @@ class CollectionState(object):
self.collect(event.item, True, event) self.collect(event.item, True, event)
new_locations = len(reachable_events) > checked_locations new_locations = len(reachable_events) > checked_locations
checked_locations = len(reachable_events) checked_locations = len(reachable_events)
if new_locations:
self.sweep_for_crystal_access()
def can_reach_blue(self, region, player):
if region not in self.colored_regions[player].keys():
return False
return self.colored_regions[player][region] in [CrystalBarrier.Blue, CrystalBarrier.Either]
def can_reach_orange(self, region, player):
if region not in self.colored_regions[player].keys():
return False
return self.colored_regions[player][region] in [CrystalBarrier.Orange, CrystalBarrier.Either]
def _do_not_flood_the_keys(self, reachable_events): def _do_not_flood_the_keys(self, reachable_events):
adjusted_checks = list(reachable_events) adjusted_checks = list(reachable_events)
@@ -699,7 +764,7 @@ class CollectionState(object):
class RegionType(Enum): class RegionType(Enum):
LightWorld = 1 LightWorld = 1
DarkWorld = 2 DarkWorld = 2
Cave = 3 # Also includes Houses Cave = 3 # Also includes Houses
Dungeon = 4 Dungeon = 4
@property @property
@@ -1244,10 +1309,9 @@ class ShopType(Enum):
UpgradeShop = 2 UpgradeShop = 2
class Shop(object): class Shop(object):
def __init__(self, region, room_id, default_door_id, type, shopkeeper_config, replaceable): def __init__(self, region, room_id, type, shopkeeper_config, replaceable):
self.region = region self.region = region
self.room_id = room_id self.room_id = room_id
self.default_door_id = default_door_id
self.type = type self.type = type
self.inventory = [None, None, None] self.inventory = [None, None, None]
self.shopkeeper_config = shopkeeper_config self.shopkeeper_config = shopkeeper_config
@@ -1265,10 +1329,8 @@ class Shop(object):
# [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index] # [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index]
entrances = self.region.entrances entrances = self.region.entrances
config = self.item_count config = self.item_count
if len(entrances) == 1 and entrances[0].addresses: if len(entrances) == 1 and entrances[0].name in door_addresses:
door_id = entrances[0].addresses+1 door_id = door_addresses[entrances[0].name][0]+1
elif self.default_door_id is not None:
door_id = self.default_door_id
else: else:
door_id = 0 door_id = 0
config |= 0x40 # ignore door id config |= 0x40 # ignore door id

View File

@@ -1034,7 +1034,7 @@ def reassign_key_doors(builder, proposal, world, player):
room.delete(d.doorListPos) room.delete(d.doorListPos)
else: else:
if len(room.doorList) > 1: if len(room.doorList) > 1:
room.mirror(d.doorListPos) room.mirror(d.doorListPos) # todo: I don't think this works for crossed - maybe it will
else: else:
room.delete(d.doorListPos) room.delete(d.doorListPos)
d.smallKey = False d.smallKey = False

View File

@@ -1084,11 +1084,12 @@ def create_doors(world, player):
world.get_door('Hera Startile Wide Holes', player).c_switch() world.get_door('Hera Startile Wide Holes', player).c_switch()
world.get_door('PoD Arena Main SW', player).c_switch() world.get_door('PoD Arena Main SW', player).c_switch()
world.get_door('PoD Arena Bonk Path', player).c_switch()
world.get_door('PoD Arena Bridge SE', player).c_switch() world.get_door('PoD Arena Bridge SE', player).c_switch()
world.get_door('PoD Arena Bridge Drop Down', player).c_switch()
world.get_door('PoD Arena Main Orange Barrier', 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?? # 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 Main Crystal Path', player).barrier(CrystalBarrier.Blue)
world.get_door('PoD Arena Crystals E', player).barrier(CrystalBarrier.Blue)
world.get_door('PoD Arena Crystal Path', player).barrier(CrystalBarrier.Blue) world.get_door('PoD Arena Crystal Path', player).barrier(CrystalBarrier.Blue)
world.get_door('PoD Sexy Statue W', player).c_switch() world.get_door('PoD Sexy Statue W', player).c_switch()
world.get_door('PoD Sexy Statue NW', player).c_switch() world.get_door('PoD Sexy Statue NW', player).c_switch()
@@ -1185,6 +1186,7 @@ def create_doors(world, player):
world.get_door('GT Hookshot Entry Blue Barrier', player).barrier(CrystalBarrier.Blue) world.get_door('GT Hookshot Entry Blue Barrier', player).barrier(CrystalBarrier.Blue)
world.get_door('GT Double Switch Orange Barrier', player).barrier(CrystalBarrier.Orange) world.get_door('GT Double Switch Orange Barrier', player).barrier(CrystalBarrier.Orange)
world.get_door('GT Double Switch Orange Barrier 2', player).barrier(CrystalBarrier.Orange) world.get_door('GT Double Switch Orange Barrier 2', player).barrier(CrystalBarrier.Orange)
world.get_door('GT Double Switch Orange Path', player).barrier(CrystalBarrier.Orange)
world.get_door('GT Double Switch Key Orange Path', player).barrier(CrystalBarrier.Orange) world.get_door('GT Double Switch Key Orange Path', player).barrier(CrystalBarrier.Orange)
world.get_door('GT Double Switch Key Blue Path', player).barrier(CrystalBarrier.Blue) world.get_door('GT Double Switch Key Blue Path', player).barrier(CrystalBarrier.Blue)
world.get_door('GT Double Switch Blue Barrier', player).barrier(CrystalBarrier.Blue) world.get_door('GT Double Switch Blue Barrier', player).barrier(CrystalBarrier.Blue)

37
Gui.py
View File

@@ -90,6 +90,34 @@ def guiMain(args=None):
fileDialogFrame = Frame(rightHalfFrame) fileDialogFrame = Frame(rightHalfFrame)
heartbeepFrame = Frame(fileDialogFrame)
heartbeepVar = StringVar()
heartbeepVar.set('normal')
heartbeepOptionMenu = OptionMenu(heartbeepFrame, heartbeepVar, 'double', 'normal', 'half', 'quarter', 'off')
heartbeepOptionMenu.pack(side=RIGHT)
heartbeepLabel = Label(heartbeepFrame, text='Heartbeep sound rate')
heartbeepLabel.pack(side=LEFT, padx=(0,52))
heartcolorFrame = Frame(fileDialogFrame)
heartcolorVar = StringVar()
heartcolorVar.set('red')
heartcolorOptionMenu = OptionMenu(heartcolorFrame, heartcolorVar, 'red', 'blue', 'green', 'yellow', 'random')
heartcolorOptionMenu.pack(side=RIGHT)
heartcolorLabel = Label(heartcolorFrame, text='Heart color')
heartcolorLabel.pack(side=LEFT, padx=(0,127))
fastMenuFrame = Frame(fileDialogFrame)
fastMenuVar = StringVar()
fastMenuVar.set('normal')
fastMenuOptionMenu = OptionMenu(fastMenuFrame, fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half')
fastMenuOptionMenu.pack(side=RIGHT)
fastMenuLabel = Label(fastMenuFrame, text='Menu speed')
fastMenuLabel.pack(side=LEFT, padx=(0,100))
heartbeepFrame.pack(expand=True, anchor=E)
heartcolorFrame.pack(expand=True, anchor=E)
fastMenuFrame.pack(expand=True, anchor=E)
romDialogFrame = Frame(fileDialogFrame) romDialogFrame = Frame(fileDialogFrame)
baseRomLabel = Label(romDialogFrame, text='Base Rom') baseRomLabel = Label(romDialogFrame, text='Base Rom')
romVar = StringVar() romVar = StringVar()
@@ -144,7 +172,7 @@ def guiMain(args=None):
modeVar.set('open') modeVar.set('open')
modeOptionMenu = OptionMenu(modeFrame, modeVar, 'standard', 'open', 'inverted') modeOptionMenu = OptionMenu(modeFrame, modeVar, 'standard', 'open', 'inverted')
modeOptionMenu.pack(side=RIGHT) modeOptionMenu.pack(side=RIGHT)
modeLabel = Label(modeFrame, text='Game Mode') modeLabel = Label(modeFrame, text='Game mode')
modeLabel.pack(side=LEFT) modeLabel.pack(side=LEFT)
logicFrame = Frame(drowDownFrame) logicFrame = Frame(drowDownFrame)
@@ -208,7 +236,7 @@ def guiMain(args=None):
difficultyVar.set('normal') difficultyVar.set('normal')
difficultyOptionMenu = OptionMenu(difficultyFrame, difficultyVar, 'normal', 'hard', 'expert') difficultyOptionMenu = OptionMenu(difficultyFrame, difficultyVar, 'normal', 'hard', 'expert')
difficultyOptionMenu.pack(side=RIGHT) difficultyOptionMenu.pack(side=RIGHT)
difficultyLabel = Label(difficultyFrame, text='Game difficulty') difficultyLabel = Label(difficultyFrame, text='Difficulty: item pool')
difficultyLabel.pack(side=LEFT) difficultyLabel.pack(side=LEFT)
timerFrame = Frame(drowDownFrame) timerFrame = Frame(drowDownFrame)
@@ -1137,10 +1165,15 @@ def guiMain(args=None):
if args.seed: if args.seed:
seedVar.set(str(args.seed)) seedVar.set(str(args.seed))
modeVar.set(args.mode) modeVar.set(args.mode)
swordsVar.set(args.swords)
difficultyVar.set(args.difficulty) difficultyVar.set(args.difficulty)
itemFuncVar.set(args.item_functionality)
timerVar.set(args.timer) timerVar.set(args.timer)
progressiveVar.set(args.progressive) progressiveVar.set(args.progressive)
accessibilityVar.set(args.accessibility)
goalVar.set(args.goal) goalVar.set(args.goal)
crystalsGTVar.set(args.crystals_gt)
crystalsGanonVar.set(args.crystals_ganon)
algorithmVar.set(args.algorithm) algorithmVar.set(args.algorithm)
shuffleVar.set(args.shuffle) shuffleVar.set(args.shuffle)
doorShuffleVar.set(args.door_shuffle) doorShuffleVar.set(args.door_shuffle)

View File

@@ -302,16 +302,16 @@ def create_inverted_regions(world, player):
create_cave_region(player, 'The Sky', 'A Dark Sky', None, ['DDM Landing','NEDW Landing', 'WDW Landing', 'SDW Landing', 'EDW Landing', 'DD Landing', 'DLHL Landing']) create_cave_region(player, 'The Sky', 'A Dark Sky', None, ['DDM Landing','NEDW Landing', 'WDW Landing', 'SDW Landing', 'EDW Landing', 'DD Landing', 'DLHL Landing'])
] ]
for region_name, (room_id, default_door_id, shopkeeper, replaceable) in shop_table.items(): for region_name, (room_id, shopkeeper, replaceable) in shop_table.items():
region = world.get_region(region_name, player) region = world.get_region(region_name, player)
shop = Shop(region, room_id, default_door_id, ShopType.Shop, shopkeeper, replaceable) shop = Shop(region, room_id, ShopType.Shop, shopkeeper, replaceable)
region.shop = shop region.shop = shop
world.shops.append(shop) world.shops.append(shop)
for index, (item, price) in enumerate(default_shop_contents[region_name]): for index, (item, price) in enumerate(default_shop_contents[region_name]):
shop.add_inventory(index, item, price) shop.add_inventory(index, item, price)
region = world.get_region('Capacity Upgrade', player) region = world.get_region('Capacity Upgrade', player)
shop = Shop(region, 0x0115, 0x5D, ShopType.UpgradeShop, 0x04, True) shop = Shop(region, 0x0115, ShopType.UpgradeShop, 0x04, True)
region.shop = shop region.shop = shop
world.shops.append(shop) world.shops.append(shop)
shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7) shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7)
@@ -373,18 +373,18 @@ def mark_dark_world_regions(world):
seen.add(exit.connected_region) seen.add(exit.connected_region)
queue.append(exit.connected_region) queue.append(exit.connected_region)
# (room_id, default_door_id, shopkeeper, replaceable) # (room_id, shopkeeper, replaceable)
shop_table = { shop_table = {
'Cave Shop (Dark Death Mountain)': (0x0112, 0x6E, 0xC1, True), 'Cave Shop (Dark Death Mountain)': (0x0112, 0xC1, True),
'Red Shield Shop': (0x0110, 0x75, 0xC1, True), 'Red Shield Shop': (0x0110, 0xC1, True),
'Dark Lake Hylia Shop': (0x010F, 0x74, 0xC1, True), 'Dark Lake Hylia Shop': (0x010F, 0xC1, True),
'Dark World Lumberjack Shop': (0x010F, 0x57, 0xC1, True), 'Dark World Lumberjack Shop': (0x010F, 0xC1, True),
'Village of Outcasts Shop': (0x010F, 0x60, 0xC1, True), 'Village of Outcasts Shop': (0x010F, 0xC1, True),
'Dark World Potion Shop': (0x010F, 0x6F, 0xC1, True), 'Dark World Potion Shop': (0x010F, 0xC1, True),
'Light World Death Mountain Shop': (0x00FF, None, 0xA0, True), 'Light World Death Mountain Shop': (0x00FF, 0xA0, True),
'Kakariko Shop': (0x011F, 0x46, 0xA0, True), 'Kakariko Shop': (0x011F, 0xA0, True),
'Cave Shop (Lake Hylia)': (0x0112, 0x58, 0xA0, True), 'Cave Shop (Lake Hylia)': (0x0112, 0xA0, True),
'Potion Shop': (0x0109, 0x4C, 0xFF, False), 'Potion Shop': (0x0109, 0xFF, False),
# Bomb Shop not currently modeled as a shop, due to special nature of items # Bomb Shop not currently modeled as a shop, due to special nature of items
} }
# region, [item] # region, [item]

View File

@@ -267,7 +267,7 @@ def set_up_take_anys(world, player):
entrance = world.get_region(reg, player).entrances[0] entrance = world.get_region(reg, player).entrances[0]
connect_entrance(world, entrance, old_man_take_any, player) connect_entrance(world, entrance, old_man_take_any, player)
entrance.target = 0x58 entrance.target = 0x58
old_man_take_any.shop = Shop(old_man_take_any, 0x0112, None, ShopType.TakeAny, 0xE2, True) old_man_take_any.shop = Shop(old_man_take_any, 0x0112, ShopType.TakeAny, 0xE2, True)
world.shops.append(old_man_take_any.shop) world.shops.append(old_man_take_any.shop)
old_man_take_any.shop.active = True old_man_take_any.shop.active = True
@@ -290,7 +290,7 @@ def set_up_take_anys(world, player):
entrance = world.get_region(reg, player).entrances[0] entrance = world.get_region(reg, player).entrances[0]
connect_entrance(world, entrance, take_any, player) connect_entrance(world, entrance, take_any, player)
entrance.target = target entrance.target = target
take_any.shop = Shop(take_any, room_id, None, ShopType.TakeAny, 0xE3, True) take_any.shop = Shop(take_any, room_id, ShopType.TakeAny, 0xE3, True)
world.shops.append(take_any.shop) world.shops.append(take_any.shop)
take_any.shop.active = True take_any.shop.active = True
take_any.shop.add_inventory(0, 'Blue Potion', 0, 0) take_any.shop.add_inventory(0, 'Blue Potion', 0, 0)

View File

@@ -118,9 +118,9 @@ def analyze_dungeon(key_layout, world, player):
raw_avail = chest_keys + len(key_counter.key_only_locations) raw_avail = chest_keys + len(key_counter.key_only_locations)
available = raw_avail - key_counter.used_keys available = raw_avail - key_counter.used_keys
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop) possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
avail_bigs = count_unique_big_doors(key_counter) avail_bigs = exist_relevant_big_doors(key_counter, key_layout)
if not key_counter.big_key_opened: if not key_counter.big_key_opened:
if chest_keys == count_locations_big_optional(key_counter.free_locations) and available <= possible_smalls and avail_bigs == 0: if chest_keys == count_locations_big_optional(key_counter.free_locations) and available <= possible_smalls and not avail_bigs:
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations)) key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
if not key_counter.big_key_opened and big_chest_in_locations(key_counter.free_locations): if not key_counter.big_key_opened and big_chest_in_locations(key_counter.free_locations):
key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations)) key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations))
@@ -318,6 +318,7 @@ def create_rule(key_counter, prev_counter, key_layout, world):
available = raw_avail - key_counter.used_keys available = raw_avail - key_counter.used_keys
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop) possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
required_keys = min(available, possible_smalls) + key_counter.used_keys required_keys = min(available, possible_smalls) + key_counter.used_keys
# required_keys = key_counter.used_keys + 1 # this sometimes makes more sense
# if prev_avail < required_keys: # if prev_avail < required_keys:
# required_keys = prev_avail + prev_counter.used_keys # required_keys = prev_avail + prev_counter.used_keys
# return DoorRules(required_keys) # return DoorRules(required_keys)
@@ -487,15 +488,16 @@ def count_unique_small_doors(key_counter, proposal):
return cnt return cnt
def count_unique_big_doors(key_counter): def exist_relevant_big_doors(key_counter, key_layout):
cnt = 0 bk_counter = find_counter(key_counter.open_doors, True, key_layout, False)
counted = set() if bk_counter is not None:
for door in key_counter.child_doors: diff = dict_difference(bk_counter.free_locations, key_counter.free_locations)
if door.bigKey and door not in counted: if len(diff) > 0:
cnt += 1 return True
counted.add(door) diff = dict_difference(bk_counter.key_only_locations, key_counter.key_only_locations)
counted.add(door.dest) if len(diff) > 0:
return cnt return True
return False
def count_locations_big_optional(locations, bk=False): def count_locations_big_optional(locations, bk=False):
@@ -788,7 +790,7 @@ def state_id(state, flat_proposal):
return s_id return s_id
def find_counter(opened_doors, bk_hint, key_layout): def find_counter(opened_doors, bk_hint, key_layout, raise_on_error=True):
counter = find_counter_hint(opened_doors, bk_hint, key_layout) counter = find_counter_hint(opened_doors, bk_hint, key_layout)
if counter is not None: if counter is not None:
return counter return counter
@@ -801,7 +803,9 @@ def find_counter(opened_doors, bk_hint, key_layout):
counter = find_counter_hint(dict.fromkeys(more_doors), bk_hint, key_layout) counter = find_counter_hint(dict.fromkeys(more_doors), bk_hint, key_layout)
if counter is not None: if counter is not None:
return counter return counter
raise Exception('Unable to find door permutation. Init CID: %s' % counter_id(opened_doors, bk_hint, key_layout.flat_prop)) if raise_on_error:
raise Exception('Unable to find door permutation. Init CID: %s' % counter_id(opened_doors, bk_hint, key_layout.flat_prop))
return None
def find_counter_hint(opened_doors, bk_hint, key_layout): def find_counter_hint(opened_doors, bk_hint, key_layout):

View File

@@ -290,7 +290,7 @@ def copy_dynamic_regions_and_locations(world, ret):
# Note: ideally exits should be copied here, but the current use case (Take anys) do not require this # Note: ideally exits should be copied here, but the current use case (Take anys) do not require this
if region.shop: if region.shop:
new_reg.shop = Shop(new_reg, region.shop.room_id, region.shop.default_door_id, region.shop.type, region.shop.shopkeeper_config, region.shop.replaceable) new_reg.shop = Shop(new_reg, region.shop.room_id, region.shop.type, region.shop.shopkeeper_config, region.shop.replaceable)
ret.shops.append(new_reg.shop) ret.shops.append(new_reg.shop)
for location in world.dynamic_locations: for location in world.dynamic_locations:
@@ -325,6 +325,7 @@ def create_playthrough(world):
while sphere_candidates: while sphere_candidates:
if not world.keysanity: if not world.keysanity:
state.sweep_for_events(key_only=True) state.sweep_for_events(key_only=True)
state.sweep_for_crystal_access()
sphere = [] sphere = []
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres # build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
@@ -379,6 +380,7 @@ def create_playthrough(world):
while required_locations: while required_locations:
if not world.keysanity: if not world.keysanity:
state.sweep_for_events(key_only=True) state.sweep_for_events(key_only=True)
state.sweep_for_crystal_access()
sphere = list(filter(lambda loc: state.can_reach(loc) and state.not_flooding_a_key(world, loc), required_locations)) sphere = list(filter(lambda loc: state.can_reach(loc) and state.not_flooding_a_key(world, loc), required_locations))

View File

@@ -705,16 +705,16 @@ def create_regions(world, player):
create_dungeon_region(player, 'GT Agahnim 2', 'Ganon\'s Tower', ['Agahnim 2'], ['GT Agahnim 2 SW']) create_dungeon_region(player, 'GT Agahnim 2', 'Ganon\'s Tower', ['Agahnim 2'], ['GT Agahnim 2 SW'])
] ]
for region_name, (room_id, default_door_id, shopkeeper, replaceable) in shop_table.items(): for region_name, (room_id, shopkeeper, replaceable) in shop_table.items():
region = world.get_region(region_name, player) region = world.get_region(region_name, player)
shop = Shop(region, room_id, default_door_id, ShopType.Shop, shopkeeper, replaceable) shop = Shop(region, room_id, ShopType.Shop, shopkeeper, replaceable)
region.shop = shop region.shop = shop
world.shops.append(shop) world.shops.append(shop)
for index, (item, price) in enumerate(default_shop_contents[region_name]): for index, (item, price) in enumerate(default_shop_contents[region_name]):
shop.add_inventory(index, item, price) shop.add_inventory(index, item, price)
region = world.get_region('Capacity Upgrade', player) region = world.get_region('Capacity Upgrade', player)
shop = Shop(region, 0x0115, 0x5D, ShopType.UpgradeShop, 0x04, True) shop = Shop(region, 0x0115, ShopType.UpgradeShop, 0x04, True)
region.shop = shop region.shop = shop
world.shops.append(shop) world.shops.append(shop)
shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7) shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7)
@@ -780,18 +780,18 @@ def mark_light_world_regions(world):
seen.add(exit.connected_region) seen.add(exit.connected_region)
queue.append(exit.connected_region) queue.append(exit.connected_region)
# (room_id, default_door_id, shopkeeper, replaceable) # (room_id, shopkeeper, replaceable)
shop_table = { shop_table = {
'Cave Shop (Dark Death Mountain)': (0x0112, 0x6E, 0xC1, True), 'Cave Shop (Dark Death Mountain)': (0x0112, 0xC1, True),
'Red Shield Shop': (0x0110, 0x75, 0xC1, True), 'Red Shield Shop': (0x0110, 0xC1, True),
'Dark Lake Hylia Shop': (0x010F, 0x74, 0xC1, True), 'Dark Lake Hylia Shop': (0x010F, 0xC1, True),
'Dark World Lumberjack Shop': (0x010F, 0x57, 0xC1, True), 'Dark World Lumberjack Shop': (0x010F, 0xC1, True),
'Village of Outcasts Shop': (0x010F, 0x60, 0xC1, True), 'Village of Outcasts Shop': (0x010F, 0xC1, True),
'Dark World Potion Shop': (0x010F, 0x6F, 0xC1, True), 'Dark World Potion Shop': (0x010F, 0xC1, True),
'Light World Death Mountain Shop': (0x00FF, None, 0xA0, True), 'Light World Death Mountain Shop': (0x00FF, 0xA0, True),
'Kakariko Shop': (0x011F, 0x46, 0xA0, True), 'Kakariko Shop': (0x011F, 0xA0, True),
'Cave Shop (Lake Hylia)': (0x0112, 0x58, 0xA0, True), 'Cave Shop (Lake Hylia)': (0x0112, 0xA0, True),
'Potion Shop': (0x0109, 0x4C, 0xFF, False), 'Potion Shop': (0x0109, 0xFF, False),
# Bomb Shop not currently modeled as a shop, due to special nature of items # Bomb Shop not currently modeled as a shop, due to special nature of items
} }
# region, [item] # region, [item]

View File

@@ -423,6 +423,55 @@ def global_rules(world, player):
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player)) set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))
add_key_logic_rules(world, player) add_key_logic_rules(world, player)
# crystal switch rules
set_rule(world.get_entrance('PoD Arena Crystal Path', player), lambda state: state.can_reach_blue(world.get_region('PoD Arena Crystal', player), player))
set_rule(world.get_entrance('Swamp Trench 2 Pots Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Swamp Trench 2 Pots', player), player))
set_rule(world.get_entrance('Swamp Shortcut Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Swamp Shortcut', player), player))
set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_blue(world.get_region('Thieves Attic', player), player))
set_rule(world.get_entrance('Thieves Hellway Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Thieves Hellway', player), player))
set_rule(world.get_entrance('Thieves Hellway Crystal Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Thieves Hellway N Crystal', player), player))
set_rule(world.get_entrance('Thieves Triple Bypass SE', player), lambda state: state.can_reach_blue(world.get_region('Thieves Triple Bypass', player), player))
set_rule(world.get_entrance('Thieves Triple Bypass WN', player), lambda state: state.can_reach_blue(world.get_region('Thieves Triple Bypass', player), player))
set_rule(world.get_entrance('Thieves Triple Bypass EN', player), lambda state: state.can_reach_blue(world.get_region('Thieves Triple Bypass', player), player))
set_rule(world.get_entrance('Ice Crystal Right Blue Hole', player), lambda state: state.can_reach_blue(world.get_region('Ice Crystal Right', player), player))
set_rule(world.get_entrance('Ice Crystal Left Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Ice Crystal Left', player), player))
set_rule(world.get_entrance('Ice Backwards Room Hole', player), lambda state: state.can_reach_blue(world.get_region('Ice Backwards Room', player), player))
set_rule(world.get_entrance('Mire Hub Upper Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub', player), player))
set_rule(world.get_entrance('Mire Hub Lower Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub', player), player))
set_rule(world.get_entrance('Mire Hub Right Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Right', player), player))
set_rule(world.get_entrance('Mire Hub Top Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Top', player), player))
set_rule(world.get_entrance('Mire Map Spike Side Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Map Spike Side', player), player))
set_rule(world.get_entrance('Mire Map Spot Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Map Spot', player), player))
set_rule(world.get_entrance('Mire Crystal Dead End Left Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Dead End', player), player))
set_rule(world.get_entrance('Mire Crystal Dead End Right Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Dead End', player), player))
set_rule(world.get_entrance('Mire South Fish Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire South Fish', player), player))
set_rule(world.get_entrance('Mire Compass Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Compass Room', player), player))
set_rule(world.get_entrance('Mire Crystal Mid Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Mid', player), player))
set_rule(world.get_entrance('Mire Crystal Left Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Left', player), player))
set_rule(world.get_entrance('TR Crystal Maze Blue Path', player), lambda state: state.can_reach_blue(world.get_region('TR Crystal Maze End', player), player))
set_rule(world.get_entrance('GT Hookshot Entry Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Hookshot South Entry', player), player))
set_rule(world.get_entrance('GT Double Switch Key Blue Path', player), lambda state: state.can_reach_blue(world.get_region('GT Double Switch Key Spot', player), player))
set_rule(world.get_entrance('GT Double Switch Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Double Switch Switches', player), player))
set_rule(world.get_entrance('GT Double Switch Transition Blue', player), lambda state: state.can_reach_blue(world.get_region('GT Double Switch Transition', player), player))
set_rule(world.get_entrance('Swamp Barrier Ledge - Orange', player), lambda state: state.can_reach_orange(world.get_region('Swamp Barrier Ledge', player), player))
set_rule(world.get_entrance('Swamp Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('Swamp Barrier', player), player))
set_rule(world.get_entrance('Thieves Hellway Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Thieves Hellway', player), player))
set_rule(world.get_entrance('Thieves Hellway Crystal Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Thieves Hellway S Crystal', player), player))
set_rule(world.get_entrance('Ice Bomb Jump Ledge Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Bomb Jump Ledge', player), player))
set_rule(world.get_entrance('Ice Bomb Jump Catwalk Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Bomb Jump Catwalk', player), player))
set_rule(world.get_entrance('Ice Crystal Right Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Crystal Right', player), player))
set_rule(world.get_entrance('Ice Crystal Left Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Crystal Left', player), player))
set_rule(world.get_entrance('Mire Crystal Right Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Crystal Right', player), player))
set_rule(world.get_entrance('Mire Crystal Mid Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Crystal Mid', player), player))
set_rule(world.get_entrance('Mire Firesnake Skip Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Firesnake Skip', player), player))
set_rule(world.get_entrance('Mire Antechamber Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Antechamber', player), player))
set_rule(world.get_entrance('GT Double Switch Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Entry', player), player))
set_rule(world.get_entrance('GT Double Switch Orange Barrier 2', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Entry', player), player))
set_rule(world.get_entrance('GT Double Switch Orange Path', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Switches', player), player))
set_rule(world.get_entrance('GT Double Switch Key Orange Path', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Key Spot', player), player))
# End of door rando rules. # End of door rando rules.
add_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player)) add_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player))