From edd656b90341f1d3496de096f7c8c239389b42c1 Mon Sep 17 00:00:00 2001 From: compiling <8335770+compiling@users.noreply.github.com> Date: Fri, 3 Jan 2020 08:27:22 +1100 Subject: [PATCH 1/3] Swap implementation of Take Any fix to match Multiworld. --- BaseClasses.py | 11 +++++------ InvertedRegions.py | 28 ++++++++++++++-------------- ItemList.py | 4 ++-- Main.py | 2 +- Regions.py | 28 ++++++++++++++-------------- 5 files changed, 36 insertions(+), 37 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 62547746..4f04236f 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -3,6 +3,8 @@ from enum import Enum, unique import logging import json from collections import OrderedDict + +from EntranceShuffle import door_addresses from _vendor.collections_extended import bag from Utils import int16_as_bytes @@ -871,10 +873,9 @@ class ShopType(Enum): UpgradeShop = 2 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.room_id = room_id - self.default_door_id = default_door_id self.type = type self.inventory = [None, None, None] self.shopkeeper_config = shopkeeper_config @@ -892,10 +893,8 @@ class Shop(object): # [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index] entrances = self.region.entrances config = self.item_count - if len(entrances) == 1 and entrances[0].addresses: - door_id = entrances[0].addresses+1 - elif self.default_door_id is not None: - door_id = self.default_door_id + if len(entrances) == 1 and entrances[0].name in door_addresses: + door_id = door_addresses[entrances[0].name][0]+1 else: door_id = 0 config |= 0x40 # ignore door id diff --git a/InvertedRegions.py b/InvertedRegions.py index 19602871..7623b398 100644 --- a/InvertedRegions.py +++ b/InvertedRegions.py @@ -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']) ] - 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) - shop = Shop(region, room_id, default_door_id, ShopType.Shop, shopkeeper, replaceable) + shop = Shop(region, room_id, ShopType.Shop, shopkeeper, replaceable) region.shop = shop world.shops.append(shop) for index, (item, price) in enumerate(default_shop_contents[region_name]): shop.add_inventory(index, item, price) 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 world.shops.append(shop) shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7) @@ -373,18 +373,18 @@ def mark_dark_world_regions(world): seen.add(exit.connected_region) queue.append(exit.connected_region) -# (room_id, default_door_id, shopkeeper, replaceable) +# (room_id, shopkeeper, replaceable) shop_table = { - 'Cave Shop (Dark Death Mountain)': (0x0112, 0x6E, 0xC1, True), - 'Red Shield Shop': (0x0110, 0x75, 0xC1, True), - 'Dark Lake Hylia Shop': (0x010F, 0x74, 0xC1, True), - 'Dark World Lumberjack Shop': (0x010F, 0x57, 0xC1, True), - 'Village of Outcasts Shop': (0x010F, 0x60, 0xC1, True), - 'Dark World Potion Shop': (0x010F, 0x6F, 0xC1, True), - 'Light World Death Mountain Shop': (0x00FF, None, 0xA0, True), - 'Kakariko Shop': (0x011F, 0x46, 0xA0, True), - 'Cave Shop (Lake Hylia)': (0x0112, 0x58, 0xA0, True), - 'Potion Shop': (0x0109, 0x4C, 0xFF, False), + 'Cave Shop (Dark Death Mountain)': (0x0112, 0xC1, True), + 'Red Shield Shop': (0x0110, 0xC1, True), + 'Dark Lake Hylia Shop': (0x010F, 0xC1, True), + 'Dark World Lumberjack Shop': (0x010F, 0xC1, True), + 'Village of Outcasts Shop': (0x010F, 0xC1, True), + 'Dark World Potion Shop': (0x010F, 0xC1, True), + 'Light World Death Mountain Shop': (0x00FF, 0xA0, True), + 'Kakariko Shop': (0x011F, 0xA0, True), + 'Cave Shop (Lake Hylia)': (0x0112, 0xA0, True), + 'Potion Shop': (0x0109, 0xFF, False), # Bomb Shop not currently modeled as a shop, due to special nature of items } # region, [item] diff --git a/ItemList.py b/ItemList.py index 1da7d8b3..6da66e68 100644 --- a/ItemList.py +++ b/ItemList.py @@ -246,7 +246,7 @@ def set_up_take_anys(world, player): entrance = world.get_region(reg, player).entrances[0] connect_entrance(world, entrance, old_man_take_any, player) 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) old_man_take_any.shop.active = True @@ -269,7 +269,7 @@ def set_up_take_anys(world, player): entrance = world.get_region(reg, player).entrances[0] connect_entrance(world, entrance, take_any, player) 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) take_any.shop.active = True take_any.shop.add_inventory(0, 'Blue Potion', 0, 0) diff --git a/Main.py b/Main.py index 2a621f18..1e3860b6 100644 --- a/Main.py +++ b/Main.py @@ -270,7 +270,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 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) for location in world.dynamic_locations: diff --git a/Regions.py b/Regions.py index 3e14b395..70ae3b0b 100644 --- a/Regions.py +++ b/Regions.py @@ -293,16 +293,16 @@ def create_regions(world, player): create_dw_region(player, 'Pyramid Ledge', None, ['Pyramid Entrance', 'Pyramid Drop']) ] - 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) - shop = Shop(region, room_id, default_door_id, ShopType.Shop, shopkeeper, replaceable) + shop = Shop(region, room_id, ShopType.Shop, shopkeeper, replaceable) region.shop = shop world.shops.append(shop) for index, (item, price) in enumerate(default_shop_contents[region_name]): shop.add_inventory(index, item, price) 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 world.shops.append(shop) shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7) @@ -364,18 +364,18 @@ def mark_light_world_regions(world): seen.add(exit.connected_region) queue.append(exit.connected_region) -# (room_id, default_door_id, shopkeeper, replaceable) +# (room_id, shopkeeper, replaceable) shop_table = { - 'Cave Shop (Dark Death Mountain)': (0x0112, 0x6E, 0xC1, True), - 'Red Shield Shop': (0x0110, 0x75, 0xC1, True), - 'Dark Lake Hylia Shop': (0x010F, 0x74, 0xC1, True), - 'Dark World Lumberjack Shop': (0x010F, 0x57, 0xC1, True), - 'Village of Outcasts Shop': (0x010F, 0x60, 0xC1, True), - 'Dark World Potion Shop': (0x010F, 0x6F, 0xC1, True), - 'Light World Death Mountain Shop': (0x00FF, None, 0xA0, True), - 'Kakariko Shop': (0x011F, 0x46, 0xA0, True), - 'Cave Shop (Lake Hylia)': (0x0112, 0x58, 0xA0, True), - 'Potion Shop': (0x0109, 0x4C, 0xFF, False), + 'Cave Shop (Dark Death Mountain)': (0x0112, 0xC1, True), + 'Red Shield Shop': (0x0110, 0xC1, True), + 'Dark Lake Hylia Shop': (0x010F, 0xC1, True), + 'Dark World Lumberjack Shop': (0x010F, 0xC1, True), + 'Village of Outcasts Shop': (0x010F, 0xC1, True), + 'Dark World Potion Shop': (0x010F, 0xC1, True), + 'Light World Death Mountain Shop': (0x00FF, 0xA0, True), + 'Kakariko Shop': (0x011F, 0xA0, True), + 'Cave Shop (Lake Hylia)': (0x0112, 0xA0, True), + 'Potion Shop': (0x0109, 0xFF, False), # Bomb Shop not currently modeled as a shop, due to special nature of items } # region, [item] From 733f6b1a6182ec22159a88b8b71b5a0de9b02a24 Mon Sep 17 00:00:00 2001 From: AmazingAmpharos Date: Wed, 23 Oct 2019 20:46:09 -0500 Subject: [PATCH 2/3] Update GUI The GUI was dysfunctional after the latest round of updates as it failed to address several new v31 features. The GUI should work again with this update and should allow proper interaction with all relevant options. Some aesthetic rework was necessary to account for the new large number of dropdowns. --- Gui.py | 114 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 29 deletions(-) diff --git a/Gui.py b/Gui.py index e02457d1..da8550df 100755 --- a/Gui.py +++ b/Gui.py @@ -90,6 +90,34 @@ def guiMain(args=None): 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) baseRomLabel = Label(romDialogFrame, text='Base Rom') romVar = StringVar() @@ -144,7 +172,7 @@ def guiMain(args=None): modeVar.set('open') modeOptionMenu = OptionMenu(modeFrame, modeVar, 'standard', 'open', 'inverted') modeOptionMenu.pack(side=RIGHT) - modeLabel = Label(modeFrame, text='Game Mode') + modeLabel = Label(modeFrame, text='Game mode') modeLabel.pack(side=LEFT) logicFrame = Frame(drowDownFrame) @@ -163,14 +191,46 @@ def guiMain(args=None): goalLabel = Label(goalFrame, text='Game goal') goalLabel.pack(side=LEFT) + crystalsGTFrame = Frame(drowDownFrame) + crystalsGTVar = StringVar() + crystalsGTVar.set('7') + crystalsGTOptionMenu = OptionMenu(crystalsGTFrame, crystalsGTVar, '0', '1', '2', '3', '4', '5', '6', '7', 'random') + crystalsGTOptionMenu.pack(side=RIGHT) + crystalsGTLabel = Label(crystalsGTFrame, text='Crystals to open Ganon\'s Tower') + crystalsGTLabel.pack(side=LEFT) + + crystalsGanonFrame = Frame(drowDownFrame) + crystalsGanonVar = StringVar() + crystalsGanonVar.set('7') + crystalsGanonOptionMenu = OptionMenu(crystalsGanonFrame, crystalsGanonVar, '0', '1', '2', '3', '4', '5', '6', '7', 'random') + crystalsGanonOptionMenu.pack(side=RIGHT) + crystalsGanonLabel = Label(crystalsGanonFrame, text='Crystals to fight Ganon') + crystalsGanonLabel.pack(side=LEFT) + + swordFrame = Frame(drowDownFrame) + swordVar = StringVar() + swordVar.set('random') + swordOptionMenu = OptionMenu(swordFrame, swordVar, 'random', 'assured', 'swordless', 'vanilla') + swordOptionMenu.pack(side=RIGHT) + swordLabel = Label(swordFrame, text='Sword availability') + swordLabel.pack(side=LEFT) + difficultyFrame = Frame(drowDownFrame) difficultyVar = StringVar() difficultyVar.set('normal') difficultyOptionMenu = OptionMenu(difficultyFrame, difficultyVar, 'normal', 'hard', 'expert') difficultyOptionMenu.pack(side=RIGHT) - difficultyLabel = Label(difficultyFrame, text='Game difficulty') + difficultyLabel = Label(difficultyFrame, text='Difficulty: item pool') difficultyLabel.pack(side=LEFT) + itemfunctionFrame = Frame(drowDownFrame) + itemfunctionVar = StringVar() + itemfunctionVar.set('normal') + itemfunctionOptionMenu = OptionMenu(itemfunctionFrame, itemfunctionVar, 'normal', 'hard', 'expert') + itemfunctionOptionMenu.pack(side=RIGHT) + itemfunctionLabel = Label(itemfunctionFrame, text='Difficulty: item functionality') + itemfunctionLabel.pack(side=LEFT) + timerFrame = Frame(drowDownFrame) timerVar = StringVar() timerVar.set('none') @@ -187,6 +247,14 @@ def guiMain(args=None): progressiveLabel = Label(progressiveFrame, text='Progressive equipment') progressiveLabel.pack(side=LEFT) + accessibilityFrame = Frame(drowDownFrame) + accessibilityVar = StringVar() + accessibilityVar.set('items') + accessibilityOptionMenu = OptionMenu(accessibilityFrame, accessibilityVar, 'items', 'locations', 'none') + accessibilityOptionMenu.pack(side=RIGHT) + accessibilityLabel = Label(accessibilityFrame, text='Item accessibility') + accessibilityLabel.pack(side=LEFT) + algorithmFrame = Frame(drowDownFrame) algorithmVar = StringVar() algorithmVar.set('balanced') @@ -203,41 +271,19 @@ def guiMain(args=None): shuffleLabel = Label(shuffleFrame, text='Entrance shuffle algorithm') shuffleLabel.pack(side=LEFT) - heartbeepFrame = Frame(drowDownFrame) - 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) - - heartcolorFrame = Frame(drowDownFrame) - 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) - - fastMenuFrame = Frame(drowDownFrame) - 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) - modeFrame.pack(expand=True, anchor=E) logicFrame.pack(expand=True, anchor=E) goalFrame.pack(expand=True, anchor=E) + crystalsGTFrame.pack(expand=True, anchor=E) + crystalsGanonFrame.pack(expand=True, anchor=E) + swordFrame.pack(expand=True, anchor=E) difficultyFrame.pack(expand=True, anchor=E) + itemfunctionFrame.pack(expand=True, anchor=E) timerFrame.pack(expand=True, anchor=E) progressiveFrame.pack(expand=True, anchor=E) + accessibilityFrame.pack(expand=True, anchor=E) algorithmFrame.pack(expand=True, anchor=E) shuffleFrame.pack(expand=True, anchor=E) - heartbeepFrame.pack(expand=True, anchor=E) - heartcolorFrame.pack(expand=True, anchor=E) - fastMenuFrame.pack(expand=True, anchor=E) enemizerFrame = LabelFrame(randomizerWindow, text="Enemizer", padx=5, pady=5) enemizerFrame.columnconfigure(0, weight=1) @@ -315,9 +361,14 @@ def guiMain(args=None): guiargs.mode = modeVar.get() guiargs.logic = logicVar.get() guiargs.goal = goalVar.get() + guiargs.crystals_gt = crystalsGTVar.get() + guiargs.crystals_ganon = crystalsGanonVar.get() + guiargs.swords = swordVar.get() guiargs.difficulty = difficultyVar.get() + guiargs.item_functionality = itemfunctionVar.get() guiargs.timer = timerVar.get() guiargs.progressive = progressiveVar.get() + guiargs.accessibility = accessibilityVar.get() guiargs.algorithm = algorithmVar.get() guiargs.shuffle = shuffleVar.get() guiargs.heartbeep = heartbeepVar.get() @@ -1074,10 +1125,15 @@ def guiMain(args=None): if args.seed: seedVar.set(str(args.seed)) modeVar.set(args.mode) + swordVar.set(args.swords) difficultyVar.set(args.difficulty) + itemfunctionVar.set(args.item_functionality) timerVar.set(args.timer) progressiveVar.set(args.progressive) + accessibilityVar.set(args.accessibility) goalVar.set(args.goal) + crystalsGTVar.set(args.crystals_gt) + crystalsGanonVar.set(args.crystals_ganon) algorithmVar.set(args.algorithm) shuffleVar.set(args.shuffle) heartbeepVar.set(args.heartbeep) From f26c83e852c5bc064dddf4e45d735f46259b3ebc Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 6 Jan 2020 15:32:33 -0700 Subject: [PATCH 3/3] Made the CollectionState crystal switch aware and added crystal rules Small adjustment to big key forbidding --- BaseClasses.py | 67 +++++++++++++++++++++++++++++++++++++++++++++-- DoorShuffle.py | 2 +- Doors.py | 4 ++- KeyDoorShuffle.py | 30 ++++++++++++--------- Main.py | 2 ++ Rules.py | 49 ++++++++++++++++++++++++++++++++++ 6 files changed, 137 insertions(+), 17 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 7c87f119..c4c7047a 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2,7 +2,7 @@ import copy from enum import Enum, unique import logging import json -from collections import OrderedDict +from collections import OrderedDict, deque from _vendor.collections_extended import bag from Utils import int16_as_bytes from Tables import normal_offset_table, spiral_offset_table @@ -355,6 +355,7 @@ class CollectionState(object): self.prog_items = bag() self.world = parent 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.path = {} self.locations_checked = set() @@ -366,6 +367,7 @@ class CollectionState(object): player_regions = [region for region in self.world.regions if region.player == player] self.stale[player] = False rrp = self.reachable_regions[player] + ccr = self.colored_regions[player] new_regions = True reachable_regions_count = len(rrp) while new_regions: @@ -373,13 +375,53 @@ class CollectionState(object): for candidate in possible: if candidate.can_reach_private(self): 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 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): ret = CollectionState(self.world) 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.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.path = copy.copy(self.path) ret.locations_checked = copy.copy(self.locations_checked) @@ -400,6 +442,15 @@ class CollectionState(object): 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): # this may need improvement new_locations = True @@ -415,6 +466,18 @@ class CollectionState(object): self.collect(event.item, True, event) new_locations = len(reachable_events) > checked_locations 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): adjusted_checks = list(reachable_events) @@ -699,7 +762,7 @@ class CollectionState(object): class RegionType(Enum): LightWorld = 1 DarkWorld = 2 - Cave = 3 # Also includes Houses + Cave = 3 # Also includes Houses Dungeon = 4 @property diff --git a/DoorShuffle.py b/DoorShuffle.py index 5c7e7a18..22267f2a 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1034,7 +1034,7 @@ def reassign_key_doors(builder, proposal, world, player): room.delete(d.doorListPos) else: 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: room.delete(d.doorListPos) d.smallKey = False diff --git a/Doors.py b/Doors.py index 8835f54c..a17e102a 100644 --- a/Doors.py +++ b/Doors.py @@ -1084,11 +1084,12 @@ def create_doors(world, player): 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 Bonk Path', 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) # 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) 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 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 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 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 Blue Barrier', player).barrier(CrystalBarrier.Blue) diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 217a6e9f..4bd57642 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -118,9 +118,9 @@ def analyze_dungeon(key_layout, world, player): raw_avail = chest_keys + len(key_counter.key_only_locations) available = raw_avail - key_counter.used_keys 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 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)) 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)) @@ -318,6 +318,7 @@ def create_rule(key_counter, prev_counter, key_layout, world): available = raw_avail - key_counter.used_keys possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop) 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: # required_keys = prev_avail + prev_counter.used_keys # return DoorRules(required_keys) @@ -487,15 +488,16 @@ def count_unique_small_doors(key_counter, proposal): return cnt -def count_unique_big_doors(key_counter): - cnt = 0 - counted = set() - for door in key_counter.child_doors: - if door.bigKey and door not in counted: - cnt += 1 - counted.add(door) - counted.add(door.dest) - return cnt +def exist_relevant_big_doors(key_counter, key_layout): + bk_counter = find_counter(key_counter.open_doors, True, key_layout, False) + if bk_counter is not None: + diff = dict_difference(bk_counter.free_locations, key_counter.free_locations) + if len(diff) > 0: + return True + diff = dict_difference(bk_counter.key_only_locations, key_counter.key_only_locations) + if len(diff) > 0: + return True + return False def count_locations_big_optional(locations, bk=False): @@ -788,7 +790,7 @@ def state_id(state, flat_proposal): 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) if counter is not None: 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) if counter is not None: 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): diff --git a/Main.py b/Main.py index 9ed5efc6..7630bfb5 100644 --- a/Main.py +++ b/Main.py @@ -325,6 +325,7 @@ def create_playthrough(world): while sphere_candidates: if not world.keysanity: state.sweep_for_events(key_only=True) + state.sweep_for_crystal_access() sphere = [] # 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: if not world.keysanity: 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)) diff --git a/Rules.py b/Rules.py index 39647bbb..fd93c9f3 100644 --- a/Rules.py +++ b/Rules.py @@ -423,6 +423,55 @@ def global_rules(world, player): set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', 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. add_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player))